Limit auto-extending to click on first move of PV
[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 && origIndex - startPV < 5);
5684         return TRUE;
5685 }
5686
5687 char *
5688 PvToSAN (char *pv)
5689 {
5690         static char buf[10*MSG_SIZ];
5691         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5692         *buf = NULLCHAR;
5693         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5694         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5695         for(i = forwardMostMove; i<endPV; i++){
5696             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5697             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5698             k += strlen(buf+k);
5699         }
5700         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5701         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5702         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5703         endPV = savedEnd;
5704         return buf;
5705 }
5706
5707 Boolean
5708 LoadPV (int x, int y)
5709 { // called on right mouse click to load PV
5710   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5711   lastX = x; lastY = y;
5712   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5713   extendGame = FALSE;
5714   return TRUE;
5715 }
5716
5717 void
5718 UnLoadPV ()
5719 {
5720   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5721   if(endPV < 0) return;
5722   if(appData.autoCopyPV) CopyFENToClipboard();
5723   endPV = -1;
5724   if(extendGame && currentMove > forwardMostMove) {
5725         Boolean saveAnimate = appData.animate;
5726         if(pushed) {
5727             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5728                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5729             } else storedGames--; // abandon shelved tail of original game
5730         }
5731         pushed = FALSE;
5732         forwardMostMove = currentMove;
5733         currentMove = oldFMM;
5734         appData.animate = FALSE;
5735         ToNrEvent(forwardMostMove);
5736         appData.animate = saveAnimate;
5737   }
5738   currentMove = forwardMostMove;
5739   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5740   ClearPremoveHighlights();
5741   DrawPosition(TRUE, boards[currentMove]);
5742 }
5743
5744 void
5745 MovePV (int x, int y, int h)
5746 { // step through PV based on mouse coordinates (called on mouse move)
5747   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5748
5749   // we must somehow check if right button is still down (might be released off board!)
5750   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5751   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5752   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5753   if(!step) return;
5754   lastX = x; lastY = y;
5755
5756   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5757   if(endPV < 0) return;
5758   if(y < margin) step = 1; else
5759   if(y > h - margin) step = -1;
5760   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5761   currentMove += step;
5762   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5763   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5764                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5765   DrawPosition(FALSE, boards[currentMove]);
5766 }
5767
5768
5769 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5770 // All positions will have equal probability, but the current method will not provide a unique
5771 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5772 #define DARK 1
5773 #define LITE 2
5774 #define ANY 3
5775
5776 int squaresLeft[4];
5777 int piecesLeft[(int)BlackPawn];
5778 int seed, nrOfShuffles;
5779
5780 void
5781 GetPositionNumber ()
5782 {       // sets global variable seed
5783         int i;
5784
5785         seed = appData.defaultFrcPosition;
5786         if(seed < 0) { // randomize based on time for negative FRC position numbers
5787                 for(i=0; i<50; i++) seed += random();
5788                 seed = random() ^ random() >> 8 ^ random() << 8;
5789                 if(seed<0) seed = -seed;
5790         }
5791 }
5792
5793 int
5794 put (Board board, int pieceType, int rank, int n, int shade)
5795 // put the piece on the (n-1)-th empty squares of the given shade
5796 {
5797         int i;
5798
5799         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5800                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5801                         board[rank][i] = (ChessSquare) pieceType;
5802                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5803                         squaresLeft[ANY]--;
5804                         piecesLeft[pieceType]--;
5805                         return i;
5806                 }
5807         }
5808         return -1;
5809 }
5810
5811
5812 void
5813 AddOnePiece (Board board, int pieceType, int rank, int shade)
5814 // calculate where the next piece goes, (any empty square), and put it there
5815 {
5816         int i;
5817
5818         i = seed % squaresLeft[shade];
5819         nrOfShuffles *= squaresLeft[shade];
5820         seed /= squaresLeft[shade];
5821         put(board, pieceType, rank, i, shade);
5822 }
5823
5824 void
5825 AddTwoPieces (Board board, int pieceType, int rank)
5826 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5827 {
5828         int i, n=squaresLeft[ANY], j=n-1, k;
5829
5830         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5831         i = seed % k;  // pick one
5832         nrOfShuffles *= k;
5833         seed /= k;
5834         while(i >= j) i -= j--;
5835         j = n - 1 - j; i += j;
5836         put(board, pieceType, rank, j, ANY);
5837         put(board, pieceType, rank, i, ANY);
5838 }
5839
5840 void
5841 SetUpShuffle (Board board, int number)
5842 {
5843         int i, p, first=1;
5844
5845         GetPositionNumber(); nrOfShuffles = 1;
5846
5847         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5848         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5849         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5850
5851         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5852
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5854             p = (int) board[0][i];
5855             if(p < (int) BlackPawn) piecesLeft[p] ++;
5856             board[0][i] = EmptySquare;
5857         }
5858
5859         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5860             // shuffles restricted to allow normal castling put KRR first
5861             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5862                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5863             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5864                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5865             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5866                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5867             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5868                 put(board, WhiteRook, 0, 0, ANY);
5869             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5870         }
5871
5872         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5873             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5874             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5875                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5876                 while(piecesLeft[p] >= 2) {
5877                     AddOnePiece(board, p, 0, LITE);
5878                     AddOnePiece(board, p, 0, DARK);
5879                 }
5880                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5881             }
5882
5883         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5884             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5885             // but we leave King and Rooks for last, to possibly obey FRC restriction
5886             if(p == (int)WhiteRook) continue;
5887             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5888             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5889         }
5890
5891         // now everything is placed, except perhaps King (Unicorn) and Rooks
5892
5893         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5894             // Last King gets castling rights
5895             while(piecesLeft[(int)WhiteUnicorn]) {
5896                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900             while(piecesLeft[(int)WhiteKing]) {
5901                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5902                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5903             }
5904
5905
5906         } else {
5907             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5908             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5909         }
5910
5911         // Only Rooks can be left; simply place them all
5912         while(piecesLeft[(int)WhiteRook]) {
5913                 i = put(board, WhiteRook, 0, 0, ANY);
5914                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5915                         if(first) {
5916                                 first=0;
5917                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5918                         }
5919                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5920                 }
5921         }
5922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5923             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5924         }
5925
5926         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5927 }
5928
5929 int
5930 SetCharTable (char *table, const char * map)
5931 /* [HGM] moved here from winboard.c because of its general usefulness */
5932 /*       Basically a safe strcpy that uses the last character as King */
5933 {
5934     int result = FALSE; int NrPieces;
5935
5936     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5937                     && NrPieces >= 12 && !(NrPieces&1)) {
5938         int i; /* [HGM] Accept even length from 12 to 34 */
5939
5940         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5941         for( i=0; i<NrPieces/2-1; i++ ) {
5942             table[i] = map[i];
5943             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5944         }
5945         table[(int) WhiteKing]  = map[NrPieces/2-1];
5946         table[(int) BlackKing]  = map[NrPieces-1];
5947
5948         result = TRUE;
5949     }
5950
5951     return result;
5952 }
5953
5954 void
5955 Prelude (Board board)
5956 {       // [HGM] superchess: random selection of exo-pieces
5957         int i, j, k; ChessSquare p;
5958         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5959
5960         GetPositionNumber(); // use FRC position number
5961
5962         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5963             SetCharTable(pieceToChar, appData.pieceToCharTable);
5964             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5965                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5966         }
5967
5968         j = seed%4;                 seed /= 4;
5969         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5970         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5971         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5972         j = seed%3 + (seed%3 >= j); seed /= 3;
5973         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5974         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5975         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5976         j = seed%3;                 seed /= 3;
5977         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5978         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5979         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5980         j = seed%2 + (seed%2 >= j); seed /= 2;
5981         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5984         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5985         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5986         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5987         put(board, exoPieces[0],    0, 0, ANY);
5988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5989 }
5990
5991 void
5992 InitPosition (int redraw)
5993 {
5994     ChessSquare (* pieces)[BOARD_FILES];
5995     int i, j, pawnRow=1, pieceRows=1, overrule,
5996     oldx = gameInfo.boardWidth,
5997     oldy = gameInfo.boardHeight,
5998     oldh = gameInfo.holdingsWidth;
5999     static int oldv;
6000
6001     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6002
6003     /* [AS] Initialize pv info list [HGM] and game status */
6004     {
6005         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6006             pvInfoList[i].depth = 0;
6007             boards[i][EP_STATUS] = EP_NONE;
6008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6009         }
6010
6011         initialRulePlies = 0; /* 50-move counter start */
6012
6013         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6014         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6015     }
6016
6017
6018     /* [HGM] logic here is completely changed. In stead of full positions */
6019     /* the initialized data only consist of the two backranks. The switch */
6020     /* selects which one we will use, which is than copied to the Board   */
6021     /* initialPosition, which for the rest is initialized by Pawns and    */
6022     /* empty squares. This initial position is then copied to boards[0],  */
6023     /* possibly after shuffling, so that it remains available.            */
6024
6025     gameInfo.holdingsWidth = 0; /* default board sizes */
6026     gameInfo.boardWidth    = 8;
6027     gameInfo.boardHeight   = 8;
6028     gameInfo.holdingsSize  = 0;
6029     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6030     for(i=0; i<BOARD_FILES-2; i++)
6031       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6032     initialPosition[EP_STATUS] = EP_NONE;
6033     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6034     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6035          SetCharTable(pieceNickName, appData.pieceNickNames);
6036     else SetCharTable(pieceNickName, "............");
6037     pieces = FIDEArray;
6038
6039     switch (gameInfo.variant) {
6040     case VariantFischeRandom:
6041       shuffleOpenings = TRUE;
6042       appData.fischerCastling = TRUE;
6043     default:
6044       break;
6045     case VariantShatranj:
6046       pieces = ShatranjArray;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6049       break;
6050     case VariantMakruk:
6051       pieces = makrukArray;
6052       nrCastlingRights = 0;
6053       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6054       break;
6055     case VariantASEAN:
6056       pieces = aseanArray;
6057       nrCastlingRights = 0;
6058       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6059       break;
6060     case VariantTwoKings:
6061       pieces = twoKingsArray;
6062       break;
6063     case VariantGrand:
6064       pieces = GrandArray;
6065       nrCastlingRights = 0;
6066       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6067       gameInfo.boardWidth = 10;
6068       gameInfo.boardHeight = 10;
6069       gameInfo.holdingsSize = 7;
6070       break;
6071     case VariantCapaRandom:
6072       shuffleOpenings = TRUE;
6073       appData.fischerCastling = TRUE;
6074     case VariantCapablanca:
6075       pieces = CapablancaArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6078       break;
6079     case VariantGothic:
6080       pieces = GothicArray;
6081       gameInfo.boardWidth = 10;
6082       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6083       break;
6084     case VariantSChess:
6085       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6086       gameInfo.holdingsSize = 7;
6087       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6088       break;
6089     case VariantJanus:
6090       pieces = JanusArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6093       nrCastlingRights = 6;
6094         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6095         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6096         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6097         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6098         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6099         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6100       break;
6101     case VariantFalcon:
6102       pieces = FalconArray;
6103       gameInfo.boardWidth = 10;
6104       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6105       break;
6106     case VariantXiangqi:
6107       pieces = XiangqiArray;
6108       gameInfo.boardWidth  = 9;
6109       gameInfo.boardHeight = 10;
6110       nrCastlingRights = 0;
6111       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6112       break;
6113     case VariantShogi:
6114       pieces = ShogiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 9;
6117       gameInfo.holdingsSize = 7;
6118       nrCastlingRights = 0;
6119       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6120       break;
6121     case VariantChu:
6122       pieces = ChuArray; pieceRows = 3;
6123       gameInfo.boardWidth  = 12;
6124       gameInfo.boardHeight = 12;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6127                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6128       break;
6129     case VariantCourier:
6130       pieces = CourierArray;
6131       gameInfo.boardWidth  = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6134       break;
6135     case VariantKnightmate:
6136       pieces = KnightmateArray;
6137       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6138       break;
6139     case VariantSpartan:
6140       pieces = SpartanArray;
6141       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6142       break;
6143     case VariantLion:
6144       pieces = lionArray;
6145       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6146       break;
6147     case VariantChuChess:
6148       pieces = ChuChessArray;
6149       gameInfo.boardWidth = 10;
6150       gameInfo.boardHeight = 10;
6151       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6152       break;
6153     case VariantFairy:
6154       pieces = fairyArray;
6155       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6156       break;
6157     case VariantGreat:
6158       pieces = GreatArray;
6159       gameInfo.boardWidth = 10;
6160       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6161       gameInfo.holdingsSize = 8;
6162       break;
6163     case VariantSuper:
6164       pieces = FIDEArray;
6165       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6166       gameInfo.holdingsSize = 8;
6167       startedFromSetupPosition = TRUE;
6168       break;
6169     case VariantCrazyhouse:
6170     case VariantBughouse:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6173       gameInfo.holdingsSize = 5;
6174       break;
6175     case VariantWildCastle:
6176       pieces = FIDEArray;
6177       /* !!?shuffle with kings guaranteed to be on d or e file */
6178       shuffleOpenings = 1;
6179       break;
6180     case VariantNoCastle:
6181       pieces = FIDEArray;
6182       nrCastlingRights = 0;
6183       /* !!?unconstrained back-rank shuffle */
6184       shuffleOpenings = 1;
6185       break;
6186     }
6187
6188     overrule = 0;
6189     if(appData.NrFiles >= 0) {
6190         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6191         gameInfo.boardWidth = appData.NrFiles;
6192     }
6193     if(appData.NrRanks >= 0) {
6194         gameInfo.boardHeight = appData.NrRanks;
6195     }
6196     if(appData.holdingsSize >= 0) {
6197         i = appData.holdingsSize;
6198         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6199         gameInfo.holdingsSize = i;
6200     }
6201     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6202     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6203         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6204
6205     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6206     if(pawnRow < 1) pawnRow = 1;
6207     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6208        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6209     if(gameInfo.variant == VariantChu) pawnRow = 3;
6210
6211     /* User pieceToChar list overrules defaults */
6212     if(appData.pieceToCharTable != NULL)
6213         SetCharTable(pieceToChar, appData.pieceToCharTable);
6214
6215     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6216
6217         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6218             s = (ChessSquare) 0; /* account holding counts in guard band */
6219         for( i=0; i<BOARD_HEIGHT; i++ )
6220             initialPosition[i][j] = s;
6221
6222         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6223         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6224         initialPosition[pawnRow][j] = WhitePawn;
6225         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6226         if(gameInfo.variant == VariantXiangqi) {
6227             if(j&1) {
6228                 initialPosition[pawnRow][j] =
6229                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6230                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6231                    initialPosition[2][j] = WhiteCannon;
6232                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6233                 }
6234             }
6235         }
6236         if(gameInfo.variant == VariantChu) {
6237              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6238                initialPosition[pawnRow+1][j] = WhiteCobra,
6239                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6240              for(i=1; i<pieceRows; i++) {
6241                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6242                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6243              }
6244         }
6245         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6246             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6247                initialPosition[0][j] = WhiteRook;
6248                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6249             }
6250         }
6251         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6252     }
6253     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6254     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6255
6256             j=BOARD_LEFT+1;
6257             initialPosition[1][j] = WhiteBishop;
6258             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6259             j=BOARD_RGHT-2;
6260             initialPosition[1][j] = WhiteRook;
6261             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6262     }
6263
6264     if( nrCastlingRights == -1) {
6265         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6266         /*       This sets default castling rights from none to normal corners   */
6267         /* Variants with other castling rights must set them themselves above    */
6268         nrCastlingRights = 6;
6269
6270         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6271         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6272         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6273         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6274         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6275         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6276      }
6277
6278      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6279      if(gameInfo.variant == VariantGreat) { // promotion commoners
6280         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6282         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6284      }
6285      if( gameInfo.variant == VariantSChess ) {
6286       initialPosition[1][0] = BlackMarshall;
6287       initialPosition[2][0] = BlackAngel;
6288       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6289       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6290       initialPosition[1][1] = initialPosition[2][1] =
6291       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6292      }
6293   if (appData.debugMode) {
6294     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6295   }
6296     if(shuffleOpenings) {
6297         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6298         startedFromSetupPosition = TRUE;
6299     }
6300     if(startedFromPositionFile) {
6301       /* [HGM] loadPos: use PositionFile for every new game */
6302       CopyBoard(initialPosition, filePosition);
6303       for(i=0; i<nrCastlingRights; i++)
6304           initialRights[i] = filePosition[CASTLING][i];
6305       startedFromSetupPosition = TRUE;
6306     }
6307
6308     CopyBoard(boards[0], initialPosition);
6309
6310     if(oldx != gameInfo.boardWidth ||
6311        oldy != gameInfo.boardHeight ||
6312        oldv != gameInfo.variant ||
6313        oldh != gameInfo.holdingsWidth
6314                                          )
6315             InitDrawingSizes(-2 ,0);
6316
6317     oldv = gameInfo.variant;
6318     if (redraw)
6319       DrawPosition(TRUE, boards[currentMove]);
6320 }
6321
6322 void
6323 SendBoard (ChessProgramState *cps, int moveNum)
6324 {
6325     char message[MSG_SIZ];
6326
6327     if (cps->useSetboard) {
6328       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6329       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6330       SendToProgram(message, cps);
6331       free(fen);
6332
6333     } else {
6334       ChessSquare *bp;
6335       int i, j, left=0, right=BOARD_WIDTH;
6336       /* Kludge to set black to move, avoiding the troublesome and now
6337        * deprecated "black" command.
6338        */
6339       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6340         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6341
6342       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6343
6344       SendToProgram("edit\n", cps);
6345       SendToProgram("#\n", cps);
6346       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6347         bp = &boards[moveNum][i][left];
6348         for (j = left; j < right; j++, bp++) {
6349           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6350           if ((int) *bp < (int) BlackPawn) {
6351             if(j == BOARD_RGHT+1)
6352                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6353             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6354             if(message[0] == '+' || message[0] == '~') {
6355               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6356                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6357                         AAA + j, ONE + i);
6358             }
6359             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6360                 message[1] = BOARD_RGHT   - 1 - j + '1';
6361                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6362             }
6363             SendToProgram(message, cps);
6364           }
6365         }
6366       }
6367
6368       SendToProgram("c\n", cps);
6369       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6370         bp = &boards[moveNum][i][left];
6371         for (j = left; j < right; j++, bp++) {
6372           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6373           if (((int) *bp != (int) EmptySquare)
6374               && ((int) *bp >= (int) BlackPawn)) {
6375             if(j == BOARD_LEFT-2)
6376                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6377             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6378                     AAA + j, ONE + i);
6379             if(message[0] == '+' || message[0] == '~') {
6380               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6381                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6382                         AAA + j, ONE + i);
6383             }
6384             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6385                 message[1] = BOARD_RGHT   - 1 - j + '1';
6386                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6387             }
6388             SendToProgram(message, cps);
6389           }
6390         }
6391       }
6392
6393       SendToProgram(".\n", cps);
6394     }
6395     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6396 }
6397
6398 char exclusionHeader[MSG_SIZ];
6399 int exCnt, excludePtr;
6400 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6401 static Exclusion excluTab[200];
6402 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6403
6404 static void
6405 WriteMap (int s)
6406 {
6407     int j;
6408     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6409     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6410 }
6411
6412 static void
6413 ClearMap ()
6414 {
6415     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6416     excludePtr = 24; exCnt = 0;
6417     WriteMap(0);
6418 }
6419
6420 static void
6421 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6422 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6423     char buf[2*MOVE_LEN], *p;
6424     Exclusion *e = excluTab;
6425     int i;
6426     for(i=0; i<exCnt; i++)
6427         if(e[i].ff == fromX && e[i].fr == fromY &&
6428            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6429     if(i == exCnt) { // was not in exclude list; add it
6430         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6431         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6432             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6433             return; // abort
6434         }
6435         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6436         excludePtr++; e[i].mark = excludePtr++;
6437         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6438         exCnt++;
6439     }
6440     exclusionHeader[e[i].mark] = state;
6441 }
6442
6443 static int
6444 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6445 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6446     char buf[MSG_SIZ];
6447     int j, k;
6448     ChessMove moveType;
6449     if((signed char)promoChar == -1) { // kludge to indicate best move
6450         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6451             return 1; // if unparsable, abort
6452     }
6453     // update exclusion map (resolving toggle by consulting existing state)
6454     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6455     j = k%8; k >>= 3;
6456     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6457     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6458          excludeMap[k] |=   1<<j;
6459     else excludeMap[k] &= ~(1<<j);
6460     // update header
6461     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6462     // inform engine
6463     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6465     SendToBoth(buf);
6466     return (state == '+');
6467 }
6468
6469 static void
6470 ExcludeClick (int index)
6471 {
6472     int i, j;
6473     Exclusion *e = excluTab;
6474     if(index < 25) { // none, best or tail clicked
6475         if(index < 13) { // none: include all
6476             WriteMap(0); // clear map
6477             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6478             SendToBoth("include all\n"); // and inform engine
6479         } else if(index > 18) { // tail
6480             if(exclusionHeader[19] == '-') { // tail was excluded
6481                 SendToBoth("include all\n");
6482                 WriteMap(0); // clear map completely
6483                 // now re-exclude selected moves
6484                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6485                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6486             } else { // tail was included or in mixed state
6487                 SendToBoth("exclude all\n");
6488                 WriteMap(0xFF); // fill map completely
6489                 // now re-include selected moves
6490                 j = 0; // count them
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6493                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6494             }
6495         } else { // best
6496             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6497         }
6498     } else {
6499         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6500             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6501             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6502             break;
6503         }
6504     }
6505 }
6506
6507 ChessSquare
6508 DefaultPromoChoice (int white)
6509 {
6510     ChessSquare result;
6511     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6512        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6513         result = WhiteFerz; // no choice
6514     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6515         result= WhiteKing; // in Suicide Q is the last thing we want
6516     else if(gameInfo.variant == VariantSpartan)
6517         result = white ? WhiteQueen : WhiteAngel;
6518     else result = WhiteQueen;
6519     if(!white) result = WHITE_TO_BLACK result;
6520     return result;
6521 }
6522
6523 static int autoQueen; // [HGM] oneclick
6524
6525 int
6526 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6527 {
6528     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6529     /* [HGM] add Shogi promotions */
6530     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6531     ChessSquare piece, partner;
6532     ChessMove moveType;
6533     Boolean premove;
6534
6535     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6536     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6537
6538     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6539       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6540         return FALSE;
6541
6542     piece = boards[currentMove][fromY][fromX];
6543     if(gameInfo.variant == VariantChu) {
6544         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6545         promotionZoneSize = BOARD_HEIGHT/3;
6546         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6547     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6548         promotionZoneSize = BOARD_HEIGHT/3;
6549         highestPromotingPiece = (int)WhiteAlfil;
6550     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6551         promotionZoneSize = 3;
6552     }
6553
6554     // Treat Lance as Pawn when it is not representing Amazon or Lance
6555     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6556         if(piece == WhiteLance) piece = WhitePawn; else
6557         if(piece == BlackLance) piece = BlackPawn;
6558     }
6559
6560     // next weed out all moves that do not touch the promotion zone at all
6561     if((int)piece >= BlackPawn) {
6562         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6563              return FALSE;
6564         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6565         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6566     } else {
6567         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6568            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6569         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6570              return FALSE;
6571     }
6572
6573     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6574
6575     // weed out mandatory Shogi promotions
6576     if(gameInfo.variant == VariantShogi) {
6577         if(piece >= BlackPawn) {
6578             if(toY == 0 && piece == BlackPawn ||
6579                toY == 0 && piece == BlackQueen ||
6580                toY <= 1 && piece == BlackKnight) {
6581                 *promoChoice = '+';
6582                 return FALSE;
6583             }
6584         } else {
6585             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6586                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6587                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         }
6592     }
6593
6594     // weed out obviously illegal Pawn moves
6595     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6596         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6597         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6598         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6599         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6600         // note we are not allowed to test for valid (non-)capture, due to premove
6601     }
6602
6603     // we either have a choice what to promote to, or (in Shogi) whether to promote
6604     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6605        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6606         ChessSquare p=BlackFerz;  // no choice
6607         while(p < EmptySquare) {  //but make sure we use piece that exists
6608             *promoChoice = PieceToChar(p++);
6609             if(*promoChoice != '.') break;
6610         }
6611         return FALSE;
6612     }
6613     // no sense asking what we must promote to if it is going to explode...
6614     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6615         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6616         return FALSE;
6617     }
6618     // give caller the default choice even if we will not make it
6619     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6620     partner = piece; // pieces can promote if the pieceToCharTable says so
6621     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6622     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6623     if(        sweepSelect && gameInfo.variant != VariantGreat
6624                            && gameInfo.variant != VariantGrand
6625                            && gameInfo.variant != VariantSuper) return FALSE;
6626     if(autoQueen) return FALSE; // predetermined
6627
6628     // suppress promotion popup on illegal moves that are not premoves
6629     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6630               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6631     if(appData.testLegality && !premove) {
6632         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6633                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6634         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6635         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6636             return FALSE;
6637     }
6638
6639     return TRUE;
6640 }
6641
6642 int
6643 InPalace (int row, int column)
6644 {   /* [HGM] for Xiangqi */
6645     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6646          column < (BOARD_WIDTH + 4)/2 &&
6647          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6648     return FALSE;
6649 }
6650
6651 int
6652 PieceForSquare (int x, int y)
6653 {
6654   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6655      return -1;
6656   else
6657      return boards[currentMove][y][x];
6658 }
6659
6660 int
6661 OKToStartUserMove (int x, int y)
6662 {
6663     ChessSquare from_piece;
6664     int white_piece;
6665
6666     if (matchMode) return FALSE;
6667     if (gameMode == EditPosition) return TRUE;
6668
6669     if (x >= 0 && y >= 0)
6670       from_piece = boards[currentMove][y][x];
6671     else
6672       from_piece = EmptySquare;
6673
6674     if (from_piece == EmptySquare) return FALSE;
6675
6676     white_piece = (int)from_piece >= (int)WhitePawn &&
6677       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6678
6679     switch (gameMode) {
6680       case AnalyzeFile:
6681       case TwoMachinesPlay:
6682       case EndOfGame:
6683         return FALSE;
6684
6685       case IcsObserving:
6686       case IcsIdle:
6687         return FALSE;
6688
6689       case MachinePlaysWhite:
6690       case IcsPlayingBlack:
6691         if (appData.zippyPlay) return FALSE;
6692         if (white_piece) {
6693             DisplayMoveError(_("You are playing Black"));
6694             return FALSE;
6695         }
6696         break;
6697
6698       case MachinePlaysBlack:
6699       case IcsPlayingWhite:
6700         if (appData.zippyPlay) return FALSE;
6701         if (!white_piece) {
6702             DisplayMoveError(_("You are playing White"));
6703             return FALSE;
6704         }
6705         break;
6706
6707       case PlayFromGameFile:
6708             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6709       case EditGame:
6710         if (!white_piece && WhiteOnMove(currentMove)) {
6711             DisplayMoveError(_("It is White's turn"));
6712             return FALSE;
6713         }
6714         if (white_piece && !WhiteOnMove(currentMove)) {
6715             DisplayMoveError(_("It is Black's turn"));
6716             return FALSE;
6717         }
6718         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6719             /* Editing correspondence game history */
6720             /* Could disallow this or prompt for confirmation */
6721             cmailOldMove = -1;
6722         }
6723         break;
6724
6725       case BeginningOfGame:
6726         if (appData.icsActive) return FALSE;
6727         if (!appData.noChessProgram) {
6728             if (!white_piece) {
6729                 DisplayMoveError(_("You are playing White"));
6730                 return FALSE;
6731             }
6732         }
6733         break;
6734
6735       case Training:
6736         if (!white_piece && WhiteOnMove(currentMove)) {
6737             DisplayMoveError(_("It is White's turn"));
6738             return FALSE;
6739         }
6740         if (white_piece && !WhiteOnMove(currentMove)) {
6741             DisplayMoveError(_("It is Black's turn"));
6742             return FALSE;
6743         }
6744         break;
6745
6746       default:
6747       case IcsExamining:
6748         break;
6749     }
6750     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6751         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6752         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6753         && gameMode != AnalyzeFile && gameMode != Training) {
6754         DisplayMoveError(_("Displayed position is not current"));
6755         return FALSE;
6756     }
6757     return TRUE;
6758 }
6759
6760 Boolean
6761 OnlyMove (int *x, int *y, Boolean captures)
6762 {
6763     DisambiguateClosure cl;
6764     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6765     switch(gameMode) {
6766       case MachinePlaysBlack:
6767       case IcsPlayingWhite:
6768       case BeginningOfGame:
6769         if(!WhiteOnMove(currentMove)) return FALSE;
6770         break;
6771       case MachinePlaysWhite:
6772       case IcsPlayingBlack:
6773         if(WhiteOnMove(currentMove)) return FALSE;
6774         break;
6775       case EditGame:
6776         break;
6777       default:
6778         return FALSE;
6779     }
6780     cl.pieceIn = EmptySquare;
6781     cl.rfIn = *y;
6782     cl.ffIn = *x;
6783     cl.rtIn = -1;
6784     cl.ftIn = -1;
6785     cl.promoCharIn = NULLCHAR;
6786     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6787     if( cl.kind == NormalMove ||
6788         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6789         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6790         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6791       fromX = cl.ff;
6792       fromY = cl.rf;
6793       *x = cl.ft;
6794       *y = cl.rt;
6795       return TRUE;
6796     }
6797     if(cl.kind != ImpossibleMove) return FALSE;
6798     cl.pieceIn = EmptySquare;
6799     cl.rfIn = -1;
6800     cl.ffIn = -1;
6801     cl.rtIn = *y;
6802     cl.ftIn = *x;
6803     cl.promoCharIn = NULLCHAR;
6804     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6805     if( cl.kind == NormalMove ||
6806         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6807         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6809       fromX = cl.ff;
6810       fromY = cl.rf;
6811       *x = cl.ft;
6812       *y = cl.rt;
6813       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6814       return TRUE;
6815     }
6816     return FALSE;
6817 }
6818
6819 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6820 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6821 int lastLoadGameUseList = FALSE;
6822 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6823 ChessMove lastLoadGameStart = EndOfFile;
6824 int doubleClick;
6825 Boolean addToBookFlag;
6826
6827 void
6828 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6829 {
6830     ChessMove moveType;
6831     ChessSquare pup;
6832     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6833
6834     /* Check if the user is playing in turn.  This is complicated because we
6835        let the user "pick up" a piece before it is his turn.  So the piece he
6836        tried to pick up may have been captured by the time he puts it down!
6837        Therefore we use the color the user is supposed to be playing in this
6838        test, not the color of the piece that is currently on the starting
6839        square---except in EditGame mode, where the user is playing both
6840        sides; fortunately there the capture race can't happen.  (It can
6841        now happen in IcsExamining mode, but that's just too bad.  The user
6842        will get a somewhat confusing message in that case.)
6843        */
6844
6845     switch (gameMode) {
6846       case AnalyzeFile:
6847       case TwoMachinesPlay:
6848       case EndOfGame:
6849       case IcsObserving:
6850       case IcsIdle:
6851         /* We switched into a game mode where moves are not accepted,
6852            perhaps while the mouse button was down. */
6853         return;
6854
6855       case MachinePlaysWhite:
6856         /* User is moving for Black */
6857         if (WhiteOnMove(currentMove)) {
6858             DisplayMoveError(_("It is White's turn"));
6859             return;
6860         }
6861         break;
6862
6863       case MachinePlaysBlack:
6864         /* User is moving for White */
6865         if (!WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is Black's turn"));
6867             return;
6868         }
6869         break;
6870
6871       case PlayFromGameFile:
6872             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6873       case EditGame:
6874       case IcsExamining:
6875       case BeginningOfGame:
6876       case AnalyzeMode:
6877       case Training:
6878         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6879         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6880             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6881             /* User is moving for Black */
6882             if (WhiteOnMove(currentMove)) {
6883                 DisplayMoveError(_("It is White's turn"));
6884                 return;
6885             }
6886         } else {
6887             /* User is moving for White */
6888             if (!WhiteOnMove(currentMove)) {
6889                 DisplayMoveError(_("It is Black's turn"));
6890                 return;
6891             }
6892         }
6893         break;
6894
6895       case IcsPlayingBlack:
6896         /* User is moving for Black */
6897         if (WhiteOnMove(currentMove)) {
6898             if (!appData.premove) {
6899                 DisplayMoveError(_("It is White's turn"));
6900             } else if (toX >= 0 && toY >= 0) {
6901                 premoveToX = toX;
6902                 premoveToY = toY;
6903                 premoveFromX = fromX;
6904                 premoveFromY = fromY;
6905                 premovePromoChar = promoChar;
6906                 gotPremove = 1;
6907                 if (appData.debugMode)
6908                     fprintf(debugFP, "Got premove: fromX %d,"
6909                             "fromY %d, toX %d, toY %d\n",
6910                             fromX, fromY, toX, toY);
6911             }
6912             return;
6913         }
6914         break;
6915
6916       case IcsPlayingWhite:
6917         /* User is moving for White */
6918         if (!WhiteOnMove(currentMove)) {
6919             if (!appData.premove) {
6920                 DisplayMoveError(_("It is Black's turn"));
6921             } else if (toX >= 0 && toY >= 0) {
6922                 premoveToX = toX;
6923                 premoveToY = toY;
6924                 premoveFromX = fromX;
6925                 premoveFromY = fromY;
6926                 premovePromoChar = promoChar;
6927                 gotPremove = 1;
6928                 if (appData.debugMode)
6929                     fprintf(debugFP, "Got premove: fromX %d,"
6930                             "fromY %d, toX %d, toY %d\n",
6931                             fromX, fromY, toX, toY);
6932             }
6933             return;
6934         }
6935         break;
6936
6937       default:
6938         break;
6939
6940       case EditPosition:
6941         /* EditPosition, empty square, or different color piece;
6942            click-click move is possible */
6943         if (toX == -2 || toY == -2) {
6944             boards[0][fromY][fromX] = EmptySquare;
6945             DrawPosition(FALSE, boards[currentMove]);
6946             return;
6947         } else if (toX >= 0 && toY >= 0) {
6948             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6949                 ChessSquare q, p = boards[0][rf][ff];
6950                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6951                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6952                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6953                 if(PieceToChar(q) == '+') gatingPiece = p;
6954             }
6955             boards[0][toY][toX] = boards[0][fromY][fromX];
6956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6957                 if(boards[0][fromY][0] != EmptySquare) {
6958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6960                 }
6961             } else
6962             if(fromX == BOARD_RGHT+1) {
6963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6966                 }
6967             } else
6968             boards[0][fromY][fromX] = gatingPiece;
6969             DrawPosition(FALSE, boards[currentMove]);
6970             return;
6971         }
6972         return;
6973     }
6974
6975     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6976     pup = boards[currentMove][toY][toX];
6977
6978     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6979     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6980          if( pup != EmptySquare ) return;
6981          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6982            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6983                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6984            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6985            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6986            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6987            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6988          fromY = DROP_RANK;
6989     }
6990
6991     /* [HGM] always test for legality, to get promotion info */
6992     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6993                                          fromY, fromX, toY, toX, promoChar);
6994
6995     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6996
6997     /* [HGM] but possibly ignore an IllegalMove result */
6998     if (appData.testLegality) {
6999         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7000             DisplayMoveError(_("Illegal move"));
7001             return;
7002         }
7003     }
7004
7005     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7006         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7007              ClearPremoveHighlights(); // was included
7008         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7009         return;
7010     }
7011
7012     if(addToBookFlag) { // adding moves to book
7013         char buf[MSG_SIZ], move[MSG_SIZ];
7014         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7015         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7016         AddBookMove(buf);
7017         addToBookFlag = FALSE;
7018         ClearHighlights();
7019         return;
7020     }
7021
7022     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7023 }
7024
7025 /* Common tail of UserMoveEvent and DropMenuEvent */
7026 int
7027 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7028 {
7029     char *bookHit = 0;
7030
7031     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7032         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7033         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7034         if(WhiteOnMove(currentMove)) {
7035             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7036         } else {
7037             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7038         }
7039     }
7040
7041     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7042        move type in caller when we know the move is a legal promotion */
7043     if(moveType == NormalMove && promoChar)
7044         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7045
7046     /* [HGM] <popupFix> The following if has been moved here from
7047        UserMoveEvent(). Because it seemed to belong here (why not allow
7048        piece drops in training games?), and because it can only be
7049        performed after it is known to what we promote. */
7050     if (gameMode == Training) {
7051       /* compare the move played on the board to the next move in the
7052        * game. If they match, display the move and the opponent's response.
7053        * If they don't match, display an error message.
7054        */
7055       int saveAnimate;
7056       Board testBoard;
7057       CopyBoard(testBoard, boards[currentMove]);
7058       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7059
7060       if (CompareBoards(testBoard, boards[currentMove+1])) {
7061         ForwardInner(currentMove+1);
7062
7063         /* Autoplay the opponent's response.
7064          * if appData.animate was TRUE when Training mode was entered,
7065          * the response will be animated.
7066          */
7067         saveAnimate = appData.animate;
7068         appData.animate = animateTraining;
7069         ForwardInner(currentMove+1);
7070         appData.animate = saveAnimate;
7071
7072         /* check for the end of the game */
7073         if (currentMove >= forwardMostMove) {
7074           gameMode = PlayFromGameFile;
7075           ModeHighlight();
7076           SetTrainingModeOff();
7077           DisplayInformation(_("End of game"));
7078         }
7079       } else {
7080         DisplayError(_("Incorrect move"), 0);
7081       }
7082       return 1;
7083     }
7084
7085   /* Ok, now we know that the move is good, so we can kill
7086      the previous line in Analysis Mode */
7087   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7088                                 && currentMove < forwardMostMove) {
7089     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7090     else forwardMostMove = currentMove;
7091   }
7092
7093   ClearMap();
7094
7095   /* If we need the chess program but it's dead, restart it */
7096   ResurrectChessProgram();
7097
7098   /* A user move restarts a paused game*/
7099   if (pausing)
7100     PauseEvent();
7101
7102   thinkOutput[0] = NULLCHAR;
7103
7104   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7105
7106   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7107     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7108     return 1;
7109   }
7110
7111   if (gameMode == BeginningOfGame) {
7112     if (appData.noChessProgram) {
7113       gameMode = EditGame;
7114       SetGameInfo();
7115     } else {
7116       char buf[MSG_SIZ];
7117       gameMode = MachinePlaysBlack;
7118       StartClocks();
7119       SetGameInfo();
7120       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7121       DisplayTitle(buf);
7122       if (first.sendName) {
7123         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7124         SendToProgram(buf, &first);
7125       }
7126       StartClocks();
7127     }
7128     ModeHighlight();
7129   }
7130
7131   /* Relay move to ICS or chess engine */
7132   if (appData.icsActive) {
7133     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7134         gameMode == IcsExamining) {
7135       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7136         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7137         SendToICS("draw ");
7138         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7139       }
7140       // also send plain move, in case ICS does not understand atomic claims
7141       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7142       ics_user_moved = 1;
7143     }
7144   } else {
7145     if (first.sendTime && (gameMode == BeginningOfGame ||
7146                            gameMode == MachinePlaysWhite ||
7147                            gameMode == MachinePlaysBlack)) {
7148       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7149     }
7150     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7151          // [HGM] book: if program might be playing, let it use book
7152         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7153         first.maybeThinking = TRUE;
7154     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7155         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7156         SendBoard(&first, currentMove+1);
7157         if(second.analyzing) {
7158             if(!second.useSetboard) SendToProgram("undo\n", &second);
7159             SendBoard(&second, currentMove+1);
7160         }
7161     } else {
7162         SendMoveToProgram(forwardMostMove-1, &first);
7163         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7164     }
7165     if (currentMove == cmailOldMove + 1) {
7166       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7167     }
7168   }
7169
7170   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7171
7172   switch (gameMode) {
7173   case EditGame:
7174     if(appData.testLegality)
7175     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7176     case MT_NONE:
7177     case MT_CHECK:
7178       break;
7179     case MT_CHECKMATE:
7180     case MT_STAINMATE:
7181       if (WhiteOnMove(currentMove)) {
7182         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7183       } else {
7184         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7185       }
7186       break;
7187     case MT_STALEMATE:
7188       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7189       break;
7190     }
7191     break;
7192
7193   case MachinePlaysBlack:
7194   case MachinePlaysWhite:
7195     /* disable certain menu options while machine is thinking */
7196     SetMachineThinkingEnables();
7197     break;
7198
7199   default:
7200     break;
7201   }
7202
7203   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7204   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7205
7206   if(bookHit) { // [HGM] book: simulate book reply
7207         static char bookMove[MSG_SIZ]; // a bit generous?
7208
7209         programStats.nodes = programStats.depth = programStats.time =
7210         programStats.score = programStats.got_only_move = 0;
7211         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7212
7213         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7214         strcat(bookMove, bookHit);
7215         HandleMachineMove(bookMove, &first);
7216   }
7217   return 1;
7218 }
7219
7220 void
7221 MarkByFEN(char *fen)
7222 {
7223         int r, f;
7224         if(!appData.markers || !appData.highlightDragging) return;
7225         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7226         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7227         while(*fen) {
7228             int s = 0;
7229             marker[r][f] = 0;
7230             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7231             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7232             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7233             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7234             if(*fen == 'T') marker[r][f++] = 0; else
7235             if(*fen == 'Y') marker[r][f++] = 1; else
7236             if(*fen == 'G') marker[r][f++] = 3; else
7237             if(*fen == 'B') marker[r][f++] = 4; else
7238             if(*fen == 'C') marker[r][f++] = 5; else
7239             if(*fen == 'M') marker[r][f++] = 6; else
7240             if(*fen == 'W') marker[r][f++] = 7; else
7241             if(*fen == 'D') marker[r][f++] = 8; else
7242             if(*fen == 'R') marker[r][f++] = 2; else {
7243                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7244               f += s; fen -= s>0;
7245             }
7246             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7247             if(r < 0) break;
7248             fen++;
7249         }
7250         DrawPosition(TRUE, NULL);
7251 }
7252
7253 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7254
7255 void
7256 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7257 {
7258     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7259     Markers *m = (Markers *) closure;
7260     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7261         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7262                          || kind == WhiteCapturesEnPassant
7263                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7264     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7265 }
7266
7267 static int hoverSavedValid;
7268
7269 void
7270 MarkTargetSquares (int clear)
7271 {
7272   int x, y, sum=0;
7273   if(clear) { // no reason to ever suppress clearing
7274     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7275     hoverSavedValid = 0;
7276     if(!sum) return; // nothing was cleared,no redraw needed
7277   } else {
7278     int capt = 0;
7279     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7280        !appData.testLegality || gameMode == EditPosition) return;
7281     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7282     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7283       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7284       if(capt)
7285       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7286     }
7287   }
7288   DrawPosition(FALSE, NULL);
7289 }
7290
7291 int
7292 Explode (Board board, int fromX, int fromY, int toX, int toY)
7293 {
7294     if(gameInfo.variant == VariantAtomic &&
7295        (board[toY][toX] != EmptySquare ||                     // capture?
7296         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7297                          board[fromY][fromX] == BlackPawn   )
7298       )) {
7299         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7300         return TRUE;
7301     }
7302     return FALSE;
7303 }
7304
7305 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7306
7307 int
7308 CanPromote (ChessSquare piece, int y)
7309 {
7310         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7311         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7312         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7313         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7314            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7315            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7316          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7317         return (piece == BlackPawn && y <= zone ||
7318                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7319                 piece == BlackLance && y == 1 ||
7320                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7321 }
7322
7323 void
7324 HoverEvent (int xPix, int yPix, int x, int y)
7325 {
7326         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7327         int r, f;
7328         if(!first.highlight) return;
7329         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7330         if(x == oldX && y == oldY) return; // only do something if we enter new square
7331         oldFromX = fromX; oldFromY = fromY;
7332         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7333           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7334             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7335           hoverSavedValid = 1;
7336         } else if(oldX != x || oldY != y) {
7337           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7338           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7339           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7340             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7341           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7342             char buf[MSG_SIZ];
7343             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7344             SendToProgram(buf, &first);
7345           }
7346           oldX = x; oldY = y;
7347 //        SetHighlights(fromX, fromY, x, y);
7348         }
7349 }
7350
7351 void ReportClick(char *action, int x, int y)
7352 {
7353         char buf[MSG_SIZ]; // Inform engine of what user does
7354         int r, f;
7355         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7356           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7357         if(!first.highlight || gameMode == EditPosition) return;
7358         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7359         SendToProgram(buf, &first);
7360 }
7361
7362 void
7363 LeftClick (ClickType clickType, int xPix, int yPix)
7364 {
7365     int x, y;
7366     Boolean saveAnimate;
7367     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7368     char promoChoice = NULLCHAR;
7369     ChessSquare piece;
7370     static TimeMark lastClickTime, prevClickTime;
7371
7372     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7373
7374     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7375
7376     if (clickType == Press) ErrorPopDown();
7377     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7378
7379     x = EventToSquare(xPix, BOARD_WIDTH);
7380     y = EventToSquare(yPix, BOARD_HEIGHT);
7381     if (!flipView && y >= 0) {
7382         y = BOARD_HEIGHT - 1 - y;
7383     }
7384     if (flipView && x >= 0) {
7385         x = BOARD_WIDTH - 1 - x;
7386     }
7387
7388     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7389         defaultPromoChoice = promoSweep;
7390         promoSweep = EmptySquare;   // terminate sweep
7391         promoDefaultAltered = TRUE;
7392         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7393     }
7394
7395     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7396         if(clickType == Release) return; // ignore upclick of click-click destination
7397         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7398         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7399         if(gameInfo.holdingsWidth &&
7400                 (WhiteOnMove(currentMove)
7401                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7402                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7403             // click in right holdings, for determining promotion piece
7404             ChessSquare p = boards[currentMove][y][x];
7405             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7406             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7407             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7408                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7409                 fromX = fromY = -1;
7410                 return;
7411             }
7412         }
7413         DrawPosition(FALSE, boards[currentMove]);
7414         return;
7415     }
7416
7417     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7418     if(clickType == Press
7419             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7420               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7421               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7422         return;
7423
7424     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7425         // could be static click on premove from-square: abort premove
7426         gotPremove = 0;
7427         ClearPremoveHighlights();
7428     }
7429
7430     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7431         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7432
7433     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7434         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7435                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7436         defaultPromoChoice = DefaultPromoChoice(side);
7437     }
7438
7439     autoQueen = appData.alwaysPromoteToQueen;
7440
7441     if (fromX == -1) {
7442       int originalY = y;
7443       gatingPiece = EmptySquare;
7444       if (clickType != Press) {
7445         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7446             DragPieceEnd(xPix, yPix); dragging = 0;
7447             DrawPosition(FALSE, NULL);
7448         }
7449         return;
7450       }
7451       doubleClick = FALSE;
7452       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7453         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7454       }
7455       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7456       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7457          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7458          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7459             /* First square */
7460             if (OKToStartUserMove(fromX, fromY)) {
7461                 second = 0;
7462                 ReportClick("lift", x, y);
7463                 MarkTargetSquares(0);
7464                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7465                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7466                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7467                     promoSweep = defaultPromoChoice;
7468                     selectFlag = 0; lastX = xPix; lastY = yPix;
7469                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7470                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7471                 }
7472                 if (appData.highlightDragging) {
7473                     SetHighlights(fromX, fromY, -1, -1);
7474                 } else {
7475                     ClearHighlights();
7476                 }
7477             } else fromX = fromY = -1;
7478             return;
7479         }
7480     }
7481
7482     /* fromX != -1 */
7483     if (clickType == Press && gameMode != EditPosition) {
7484         ChessSquare fromP;
7485         ChessSquare toP;
7486         int frc;
7487
7488         // ignore off-board to clicks
7489         if(y < 0 || x < 0) return;
7490
7491         /* Check if clicking again on the same color piece */
7492         fromP = boards[currentMove][fromY][fromX];
7493         toP = boards[currentMove][y][x];
7494         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7495         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7496            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7497              WhitePawn <= toP && toP <= WhiteKing &&
7498              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7499              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7500             (BlackPawn <= fromP && fromP <= BlackKing &&
7501              BlackPawn <= toP && toP <= BlackKing &&
7502              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7503              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7504             /* Clicked again on same color piece -- changed his mind */
7505             second = (x == fromX && y == fromY);
7506             killX = killY = -1;
7507             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7508                 second = FALSE; // first double-click rather than scond click
7509                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7510             }
7511             promoDefaultAltered = FALSE;
7512             MarkTargetSquares(1);
7513            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7514             if (appData.highlightDragging) {
7515                 SetHighlights(x, y, -1, -1);
7516             } else {
7517                 ClearHighlights();
7518             }
7519             if (OKToStartUserMove(x, y)) {
7520                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7521                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7522                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7523                  gatingPiece = boards[currentMove][fromY][fromX];
7524                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7525                 fromX = x;
7526                 fromY = y; dragging = 1;
7527                 ReportClick("lift", x, y);
7528                 MarkTargetSquares(0);
7529                 DragPieceBegin(xPix, yPix, FALSE);
7530                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7531                     promoSweep = defaultPromoChoice;
7532                     selectFlag = 0; lastX = xPix; lastY = yPix;
7533                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7534                 }
7535             }
7536            }
7537            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7538            second = FALSE;
7539         }
7540         // ignore clicks on holdings
7541         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7542     }
7543
7544     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7545         DragPieceEnd(xPix, yPix); dragging = 0;
7546         if(clearFlag) {
7547             // a deferred attempt to click-click move an empty square on top of a piece
7548             boards[currentMove][y][x] = EmptySquare;
7549             ClearHighlights();
7550             DrawPosition(FALSE, boards[currentMove]);
7551             fromX = fromY = -1; clearFlag = 0;
7552             return;
7553         }
7554         if (appData.animateDragging) {
7555             /* Undo animation damage if any */
7556             DrawPosition(FALSE, NULL);
7557         }
7558         if (second || sweepSelecting) {
7559             /* Second up/down in same square; just abort move */
7560             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7561             second = sweepSelecting = 0;
7562             fromX = fromY = -1;
7563             gatingPiece = EmptySquare;
7564             MarkTargetSquares(1);
7565             ClearHighlights();
7566             gotPremove = 0;
7567             ClearPremoveHighlights();
7568         } else {
7569             /* First upclick in same square; start click-click mode */
7570             SetHighlights(x, y, -1, -1);
7571         }
7572         return;
7573     }
7574
7575     clearFlag = 0;
7576
7577     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7578         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7579         DisplayMessage(_("only marked squares are legal"),"");
7580         DrawPosition(TRUE, NULL);
7581         return; // ignore to-click
7582     }
7583
7584     /* we now have a different from- and (possibly off-board) to-square */
7585     /* Completed move */
7586     if(!sweepSelecting) {
7587         toX = x;
7588         toY = y;
7589     }
7590
7591     piece = boards[currentMove][fromY][fromX];
7592
7593     saveAnimate = appData.animate;
7594     if (clickType == Press) {
7595         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7596         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7597             // must be Edit Position mode with empty-square selected
7598             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7599             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7600             return;
7601         }
7602         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7603             return;
7604         }
7605         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7606             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7607         } else
7608         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7609         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7610           if(appData.sweepSelect) {
7611             promoSweep = defaultPromoChoice;
7612             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7613             selectFlag = 0; lastX = xPix; lastY = yPix;
7614             Sweep(0); // Pawn that is going to promote: preview promotion piece
7615             sweepSelecting = 1;
7616             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7617             MarkTargetSquares(1);
7618           }
7619           return; // promo popup appears on up-click
7620         }
7621         /* Finish clickclick move */
7622         if (appData.animate || appData.highlightLastMove) {
7623             SetHighlights(fromX, fromY, toX, toY);
7624         } else {
7625             ClearHighlights();
7626         }
7627     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7628         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7629         if (appData.animate || appData.highlightLastMove) {
7630             SetHighlights(fromX, fromY, toX, toY);
7631         } else {
7632             ClearHighlights();
7633         }
7634     } else {
7635 #if 0
7636 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7637         /* Finish drag move */
7638         if (appData.highlightLastMove) {
7639             SetHighlights(fromX, fromY, toX, toY);
7640         } else {
7641             ClearHighlights();
7642         }
7643 #endif
7644         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7645         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7646           dragging *= 2;            // flag button-less dragging if we are dragging
7647           MarkTargetSquares(1);
7648           if(x == killX && y == killY) killX = killY = -1; else {
7649             killX = x; killY = y;     //remeber this square as intermediate
7650             ReportClick("put", x, y); // and inform engine
7651             ReportClick("lift", x, y);
7652             MarkTargetSquares(0);
7653             return;
7654           }
7655         }
7656         DragPieceEnd(xPix, yPix); dragging = 0;
7657         /* Don't animate move and drag both */
7658         appData.animate = FALSE;
7659     }
7660
7661     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7662     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7663         ChessSquare piece = boards[currentMove][fromY][fromX];
7664         if(gameMode == EditPosition && piece != EmptySquare &&
7665            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7666             int n;
7667
7668             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7669                 n = PieceToNumber(piece - (int)BlackPawn);
7670                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7671                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7672                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7673             } else
7674             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7675                 n = PieceToNumber(piece);
7676                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7677                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7678                 boards[currentMove][n][BOARD_WIDTH-2]++;
7679             }
7680             boards[currentMove][fromY][fromX] = EmptySquare;
7681         }
7682         ClearHighlights();
7683         fromX = fromY = -1;
7684         MarkTargetSquares(1);
7685         DrawPosition(TRUE, boards[currentMove]);
7686         return;
7687     }
7688
7689     // off-board moves should not be highlighted
7690     if(x < 0 || y < 0) ClearHighlights();
7691     else ReportClick("put", x, y);
7692
7693     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7694
7695     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7696         SetHighlights(fromX, fromY, toX, toY);
7697         MarkTargetSquares(1);
7698         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7699             // [HGM] super: promotion to captured piece selected from holdings
7700             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7701             promotionChoice = TRUE;
7702             // kludge follows to temporarily execute move on display, without promoting yet
7703             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7704             boards[currentMove][toY][toX] = p;
7705             DrawPosition(FALSE, boards[currentMove]);
7706             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7707             boards[currentMove][toY][toX] = q;
7708             DisplayMessage("Click in holdings to choose piece", "");
7709             return;
7710         }
7711         PromotionPopUp(promoChoice);
7712     } else {
7713         int oldMove = currentMove;
7714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7717         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7718            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7719             DrawPosition(TRUE, boards[currentMove]);
7720         MarkTargetSquares(1);
7721         fromX = fromY = -1;
7722     }
7723     appData.animate = saveAnimate;
7724     if (appData.animate || appData.animateDragging) {
7725         /* Undo animation damage if needed */
7726         DrawPosition(FALSE, NULL);
7727     }
7728 }
7729
7730 int
7731 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7732 {   // front-end-free part taken out of PieceMenuPopup
7733     int whichMenu; int xSqr, ySqr;
7734
7735     if(seekGraphUp) { // [HGM] seekgraph
7736         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7737         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7738         return -2;
7739     }
7740
7741     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7742          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7743         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7744         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7745         if(action == Press)   {
7746             originalFlip = flipView;
7747             flipView = !flipView; // temporarily flip board to see game from partners perspective
7748             DrawPosition(TRUE, partnerBoard);
7749             DisplayMessage(partnerStatus, "");
7750             partnerUp = TRUE;
7751         } else if(action == Release) {
7752             flipView = originalFlip;
7753             DrawPosition(TRUE, boards[currentMove]);
7754             partnerUp = FALSE;
7755         }
7756         return -2;
7757     }
7758
7759     xSqr = EventToSquare(x, BOARD_WIDTH);
7760     ySqr = EventToSquare(y, BOARD_HEIGHT);
7761     if (action == Release) {
7762         if(pieceSweep != EmptySquare) {
7763             EditPositionMenuEvent(pieceSweep, toX, toY);
7764             pieceSweep = EmptySquare;
7765         } else UnLoadPV(); // [HGM] pv
7766     }
7767     if (action != Press) return -2; // return code to be ignored
7768     switch (gameMode) {
7769       case IcsExamining:
7770         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7771       case EditPosition:
7772         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7773         if (xSqr < 0 || ySqr < 0) return -1;
7774         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7775         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7776         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7777         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7778         NextPiece(0);
7779         return 2; // grab
7780       case IcsObserving:
7781         if(!appData.icsEngineAnalyze) return -1;
7782       case IcsPlayingWhite:
7783       case IcsPlayingBlack:
7784         if(!appData.zippyPlay) goto noZip;
7785       case AnalyzeMode:
7786       case AnalyzeFile:
7787       case MachinePlaysWhite:
7788       case MachinePlaysBlack:
7789       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7790         if (!appData.dropMenu) {
7791           LoadPV(x, y);
7792           return 2; // flag front-end to grab mouse events
7793         }
7794         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7795            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7796       case EditGame:
7797       noZip:
7798         if (xSqr < 0 || ySqr < 0) return -1;
7799         if (!appData.dropMenu || appData.testLegality &&
7800             gameInfo.variant != VariantBughouse &&
7801             gameInfo.variant != VariantCrazyhouse) return -1;
7802         whichMenu = 1; // drop menu
7803         break;
7804       default:
7805         return -1;
7806     }
7807
7808     if (((*fromX = xSqr) < 0) ||
7809         ((*fromY = ySqr) < 0)) {
7810         *fromX = *fromY = -1;
7811         return -1;
7812     }
7813     if (flipView)
7814       *fromX = BOARD_WIDTH - 1 - *fromX;
7815     else
7816       *fromY = BOARD_HEIGHT - 1 - *fromY;
7817
7818     return whichMenu;
7819 }
7820
7821 void
7822 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7823 {
7824 //    char * hint = lastHint;
7825     FrontEndProgramStats stats;
7826
7827     stats.which = cps == &first ? 0 : 1;
7828     stats.depth = cpstats->depth;
7829     stats.nodes = cpstats->nodes;
7830     stats.score = cpstats->score;
7831     stats.time = cpstats->time;
7832     stats.pv = cpstats->movelist;
7833     stats.hint = lastHint;
7834     stats.an_move_index = 0;
7835     stats.an_move_count = 0;
7836
7837     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7838         stats.hint = cpstats->move_name;
7839         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7840         stats.an_move_count = cpstats->nr_moves;
7841     }
7842
7843     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7844
7845     SetProgramStats( &stats );
7846 }
7847
7848 void
7849 ClearEngineOutputPane (int which)
7850 {
7851     static FrontEndProgramStats dummyStats;
7852     dummyStats.which = which;
7853     dummyStats.pv = "#";
7854     SetProgramStats( &dummyStats );
7855 }
7856
7857 #define MAXPLAYERS 500
7858
7859 char *
7860 TourneyStandings (int display)
7861 {
7862     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7863     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7864     char result, *p, *names[MAXPLAYERS];
7865
7866     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7867         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7868     names[0] = p = strdup(appData.participants);
7869     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7870
7871     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7872
7873     while(result = appData.results[nr]) {
7874         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7875         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7876         wScore = bScore = 0;
7877         switch(result) {
7878           case '+': wScore = 2; break;
7879           case '-': bScore = 2; break;
7880           case '=': wScore = bScore = 1; break;
7881           case ' ':
7882           case '*': return strdup("busy"); // tourney not finished
7883         }
7884         score[w] += wScore;
7885         score[b] += bScore;
7886         games[w]++;
7887         games[b]++;
7888         nr++;
7889     }
7890     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7891     for(w=0; w<nPlayers; w++) {
7892         bScore = -1;
7893         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7894         ranking[w] = b; points[w] = bScore; score[b] = -2;
7895     }
7896     p = malloc(nPlayers*34+1);
7897     for(w=0; w<nPlayers && w<display; w++)
7898         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7899     free(names[0]);
7900     return p;
7901 }
7902
7903 void
7904 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7905 {       // count all piece types
7906         int p, f, r;
7907         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7908         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7909         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7910                 p = board[r][f];
7911                 pCnt[p]++;
7912                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7913                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7914                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7915                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7916                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7917                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7918         }
7919 }
7920
7921 int
7922 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7923 {
7924         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7925         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7926
7927         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7928         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7929         if(myPawns == 2 && nMine == 3) // KPP
7930             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7931         if(myPawns == 1 && nMine == 2) // KP
7932             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7933         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7935         if(myPawns) return FALSE;
7936         if(pCnt[WhiteRook+side])
7937             return pCnt[BlackRook-side] ||
7938                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7939                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7940                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7941         if(pCnt[WhiteCannon+side]) {
7942             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7943             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7944         }
7945         if(pCnt[WhiteKnight+side])
7946             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7947         return FALSE;
7948 }
7949
7950 int
7951 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7952 {
7953         VariantClass v = gameInfo.variant;
7954
7955         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7956         if(v == VariantShatranj) return TRUE; // always winnable through baring
7957         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7958         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7959
7960         if(v == VariantXiangqi) {
7961                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7962
7963                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7964                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7965                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7966                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7967                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7968                 if(stale) // we have at least one last-rank P plus perhaps C
7969                     return majors // KPKX
7970                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7971                 else // KCA*E*
7972                     return pCnt[WhiteFerz+side] // KCAK
7973                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7974                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7975                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7976
7977         } else if(v == VariantKnightmate) {
7978                 if(nMine == 1) return FALSE;
7979                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7980         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7981                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7982
7983                 if(nMine == 1) return FALSE; // bare King
7984                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7985                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7986                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7987                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7988                 if(pCnt[WhiteKnight+side])
7989                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7990                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7991                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7992                 if(nBishops)
7993                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7994                 if(pCnt[WhiteAlfil+side])
7995                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7996                 if(pCnt[WhiteWazir+side])
7997                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7998         }
7999
8000         return TRUE;
8001 }
8002
8003 int
8004 CompareWithRights (Board b1, Board b2)
8005 {
8006     int rights = 0;
8007     if(!CompareBoards(b1, b2)) return FALSE;
8008     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8009     /* compare castling rights */
8010     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8011            rights++; /* King lost rights, while rook still had them */
8012     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8013         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8014            rights++; /* but at least one rook lost them */
8015     }
8016     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8017            rights++;
8018     if( b1[CASTLING][5] != NoRights ) {
8019         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8020            rights++;
8021     }
8022     return rights == 0;
8023 }
8024
8025 int
8026 Adjudicate (ChessProgramState *cps)
8027 {       // [HGM] some adjudications useful with buggy engines
8028         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8029         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8030         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8031         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8032         int k, drop, count = 0; static int bare = 1;
8033         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8034         Boolean canAdjudicate = !appData.icsActive;
8035
8036         // most tests only when we understand the game, i.e. legality-checking on
8037             if( appData.testLegality )
8038             {   /* [HGM] Some more adjudications for obstinate engines */
8039                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8040                 static int moveCount = 6;
8041                 ChessMove result;
8042                 char *reason = NULL;
8043
8044                 /* Count what is on board. */
8045                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8046
8047                 /* Some material-based adjudications that have to be made before stalemate test */
8048                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8049                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8050                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8051                      if(canAdjudicate && appData.checkMates) {
8052                          if(engineOpponent)
8053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8054                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8055                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8056                          return 1;
8057                      }
8058                 }
8059
8060                 /* Bare King in Shatranj (loses) or Losers (wins) */
8061                 if( nrW == 1 || nrB == 1) {
8062                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8063                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8064                      if(canAdjudicate && appData.checkMates) {
8065                          if(engineOpponent)
8066                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8067                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8069                          return 1;
8070                      }
8071                   } else
8072                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8073                   {    /* bare King */
8074                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8075                         if(canAdjudicate && appData.checkMates) {
8076                             /* but only adjudicate if adjudication enabled */
8077                             if(engineOpponent)
8078                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8079                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8080                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8081                             return 1;
8082                         }
8083                   }
8084                 } else bare = 1;
8085
8086
8087             // don't wait for engine to announce game end if we can judge ourselves
8088             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8089               case MT_CHECK:
8090                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8091                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8092                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8093                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8094                             checkCnt++;
8095                         if(checkCnt >= 2) {
8096                             reason = "Xboard adjudication: 3rd check";
8097                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8098                             break;
8099                         }
8100                     }
8101                 }
8102               case MT_NONE:
8103               default:
8104                 break;
8105               case MT_STEALMATE:
8106               case MT_STALEMATE:
8107               case MT_STAINMATE:
8108                 reason = "Xboard adjudication: Stalemate";
8109                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8110                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8111                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8112                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8113                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8114                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8115                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8116                                                                         EP_CHECKMATE : EP_WINS);
8117                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8118                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8119                 }
8120                 break;
8121               case MT_CHECKMATE:
8122                 reason = "Xboard adjudication: Checkmate";
8123                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8124                 if(gameInfo.variant == VariantShogi) {
8125                     if(forwardMostMove > backwardMostMove
8126                        && moveList[forwardMostMove-1][1] == '@'
8127                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8128                         reason = "XBoard adjudication: pawn-drop mate";
8129                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8130                     }
8131                 }
8132                 break;
8133             }
8134
8135                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8136                     case EP_STALEMATE:
8137                         result = GameIsDrawn; break;
8138                     case EP_CHECKMATE:
8139                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8140                     case EP_WINS:
8141                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8142                     default:
8143                         result = EndOfFile;
8144                 }
8145                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8146                     if(engineOpponent)
8147                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8148                     GameEnds( result, reason, GE_XBOARD );
8149                     return 1;
8150                 }
8151
8152                 /* Next absolutely insufficient mating material. */
8153                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8154                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8155                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8156
8157                      /* always flag draws, for judging claims */
8158                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8159
8160                      if(canAdjudicate && appData.materialDraws) {
8161                          /* but only adjudicate them if adjudication enabled */
8162                          if(engineOpponent) {
8163                            SendToProgram("force\n", engineOpponent); // suppress reply
8164                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8165                          }
8166                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8167                          return 1;
8168                      }
8169                 }
8170
8171                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8172                 if(gameInfo.variant == VariantXiangqi ?
8173                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8174                  : nrW + nrB == 4 &&
8175                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8176                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8177                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8178                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8179                    ) ) {
8180                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8181                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8182                           if(engineOpponent) {
8183                             SendToProgram("force\n", engineOpponent); // suppress reply
8184                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8185                           }
8186                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8187                           return 1;
8188                      }
8189                 } else moveCount = 6;
8190             }
8191
8192         // Repetition draws and 50-move rule can be applied independently of legality testing
8193
8194                 /* Check for rep-draws */
8195                 count = 0;
8196                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8197                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8198                 for(k = forwardMostMove-2;
8199                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8200                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8201                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8202                     k-=2)
8203                 {   int rights=0;
8204                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8205                         /* compare castling rights */
8206                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8207                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8208                                 rights++; /* King lost rights, while rook still had them */
8209                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8210                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8211                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8212                                    rights++; /* but at least one rook lost them */
8213                         }
8214                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8215                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8216                                 rights++;
8217                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8218                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8219                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8220                                    rights++;
8221                         }
8222                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8223                             && appData.drawRepeats > 1) {
8224                              /* adjudicate after user-specified nr of repeats */
8225                              int result = GameIsDrawn;
8226                              char *details = "XBoard adjudication: repetition draw";
8227                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8228                                 // [HGM] xiangqi: check for forbidden perpetuals
8229                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8230                                 for(m=forwardMostMove; m>k; m-=2) {
8231                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8232                                         ourPerpetual = 0; // the current mover did not always check
8233                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8234                                         hisPerpetual = 0; // the opponent did not always check
8235                                 }
8236                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8237                                                                         ourPerpetual, hisPerpetual);
8238                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8239                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8240                                     details = "Xboard adjudication: perpetual checking";
8241                                 } else
8242                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8243                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8244                                 } else
8245                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8246                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8247                                         result = BlackWins;
8248                                         details = "Xboard adjudication: repetition";
8249                                     }
8250                                 } else // it must be XQ
8251                                 // Now check for perpetual chases
8252                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8253                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8254                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8255                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8256                                         static char resdet[MSG_SIZ];
8257                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8258                                         details = resdet;
8259                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8260                                     } else
8261                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8262                                         break; // Abort repetition-checking loop.
8263                                 }
8264                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8265                              }
8266                              if(engineOpponent) {
8267                                SendToProgram("force\n", engineOpponent); // suppress reply
8268                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8269                              }
8270                              GameEnds( result, details, GE_XBOARD );
8271                              return 1;
8272                         }
8273                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8274                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8275                     }
8276                 }
8277
8278                 /* Now we test for 50-move draws. Determine ply count */
8279                 count = forwardMostMove;
8280                 /* look for last irreversble move */
8281                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8282                     count--;
8283                 /* if we hit starting position, add initial plies */
8284                 if( count == backwardMostMove )
8285                     count -= initialRulePlies;
8286                 count = forwardMostMove - count;
8287                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8288                         // adjust reversible move counter for checks in Xiangqi
8289                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8290                         if(i < backwardMostMove) i = backwardMostMove;
8291                         while(i <= forwardMostMove) {
8292                                 lastCheck = inCheck; // check evasion does not count
8293                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8294                                 if(inCheck || lastCheck) count--; // check does not count
8295                                 i++;
8296                         }
8297                 }
8298                 if( count >= 100)
8299                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8300                          /* this is used to judge if draw claims are legal */
8301                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8302                          if(engineOpponent) {
8303                            SendToProgram("force\n", engineOpponent); // suppress reply
8304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8305                          }
8306                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8307                          return 1;
8308                 }
8309
8310                 /* if draw offer is pending, treat it as a draw claim
8311                  * when draw condition present, to allow engines a way to
8312                  * claim draws before making their move to avoid a race
8313                  * condition occurring after their move
8314                  */
8315                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8316                          char *p = NULL;
8317                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8318                              p = "Draw claim: 50-move rule";
8319                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8320                              p = "Draw claim: 3-fold repetition";
8321                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8322                              p = "Draw claim: insufficient mating material";
8323                          if( p != NULL && canAdjudicate) {
8324                              if(engineOpponent) {
8325                                SendToProgram("force\n", engineOpponent); // suppress reply
8326                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8327                              }
8328                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8329                              return 1;
8330                          }
8331                 }
8332
8333                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8334                     if(engineOpponent) {
8335                       SendToProgram("force\n", engineOpponent); // suppress reply
8336                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                     }
8338                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8339                     return 1;
8340                 }
8341         return 0;
8342 }
8343
8344 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8345 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8346 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8347
8348 static int
8349 BitbaseProbe ()
8350 {
8351     int pieces[10], squares[10], cnt=0, r, f, res;
8352     static int loaded;
8353     static PPROBE_EGBB probeBB;
8354     if(!appData.testLegality) return 10;
8355     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8356     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8357     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8358     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8359         ChessSquare piece = boards[forwardMostMove][r][f];
8360         int black = (piece >= BlackPawn);
8361         int type = piece - black*BlackPawn;
8362         if(piece == EmptySquare) continue;
8363         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8364         if(type == WhiteKing) type = WhiteQueen + 1;
8365         type = egbbCode[type];
8366         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8367         pieces[cnt] = type + black*6;
8368         if(++cnt > 5) return 11;
8369     }
8370     pieces[cnt] = squares[cnt] = 0;
8371     // probe EGBB
8372     if(loaded == 2) return 13; // loading failed before
8373     if(loaded == 0) {
8374         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8375         HMODULE lib;
8376         PLOAD_EGBB loadBB;
8377         loaded = 2; // prepare for failure
8378         if(!path) return 13; // no egbb installed
8379         strncpy(buf, path + 8, MSG_SIZ);
8380         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8381         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8382         lib = LoadLibrary(buf);
8383         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8384         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8385         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8386         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8387         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8388         loaded = 1; // success!
8389     }
8390     res = probeBB(forwardMostMove & 1, pieces, squares);
8391     return res > 0 ? 1 : res < 0 ? -1 : 0;
8392 }
8393
8394 char *
8395 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8396 {   // [HGM] book: this routine intercepts moves to simulate book replies
8397     char *bookHit = NULL;
8398
8399     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8400         char buf[MSG_SIZ];
8401         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8402         SendToProgram(buf, cps);
8403     }
8404     //first determine if the incoming move brings opponent into his book
8405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8408     if(bookHit != NULL && !cps->bookSuspend) {
8409         // make sure opponent is not going to reply after receiving move to book position
8410         SendToProgram("force\n", cps);
8411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8412     }
8413     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8414     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8415     // now arrange restart after book miss
8416     if(bookHit) {
8417         // after a book hit we never send 'go', and the code after the call to this routine
8418         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8419         char buf[MSG_SIZ], *move = bookHit;
8420         if(cps->useSAN) {
8421             int fromX, fromY, toX, toY;
8422             char promoChar;
8423             ChessMove moveType;
8424             move = buf + 30;
8425             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8426                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8427                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8428                                     PosFlags(forwardMostMove),
8429                                     fromY, fromX, toY, toX, promoChar, move);
8430             } else {
8431                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8432                 bookHit = NULL;
8433             }
8434         }
8435         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8436         SendToProgram(buf, cps);
8437         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8438     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8439         SendToProgram("go\n", cps);
8440         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8441     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8442         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8443             SendToProgram("go\n", cps);
8444         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8445     }
8446     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8447 }
8448
8449 int
8450 LoadError (char *errmess, ChessProgramState *cps)
8451 {   // unloads engine and switches back to -ncp mode if it was first
8452     if(cps->initDone) return FALSE;
8453     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8454     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8455     cps->pr = NoProc;
8456     if(cps == &first) {
8457         appData.noChessProgram = TRUE;
8458         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8459         gameMode = BeginningOfGame; ModeHighlight();
8460         SetNCPMode();
8461     }
8462     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8463     DisplayMessage("", ""); // erase waiting message
8464     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8465     return TRUE;
8466 }
8467
8468 char *savedMessage;
8469 ChessProgramState *savedState;
8470 void
8471 DeferredBookMove (void)
8472 {
8473         if(savedState->lastPing != savedState->lastPong)
8474                     ScheduleDelayedEvent(DeferredBookMove, 10);
8475         else
8476         HandleMachineMove(savedMessage, savedState);
8477 }
8478
8479 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8480 static ChessProgramState *stalledEngine;
8481 static char stashedInputMove[MSG_SIZ];
8482
8483 void
8484 HandleMachineMove (char *message, ChessProgramState *cps)
8485 {
8486     static char firstLeg[20];
8487     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8488     char realname[MSG_SIZ];
8489     int fromX, fromY, toX, toY;
8490     ChessMove moveType;
8491     char promoChar, roar;
8492     char *p, *pv=buf1;
8493     int machineWhite, oldError;
8494     char *bookHit;
8495
8496     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8497         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8498         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8499             DisplayError(_("Invalid pairing from pairing engine"), 0);
8500             return;
8501         }
8502         pairingReceived = 1;
8503         NextMatchGame();
8504         return; // Skim the pairing messages here.
8505     }
8506
8507     oldError = cps->userError; cps->userError = 0;
8508
8509 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8510     /*
8511      * Kludge to ignore BEL characters
8512      */
8513     while (*message == '\007') message++;
8514
8515     /*
8516      * [HGM] engine debug message: ignore lines starting with '#' character
8517      */
8518     if(cps->debug && *message == '#') return;
8519
8520     /*
8521      * Look for book output
8522      */
8523     if (cps == &first && bookRequested) {
8524         if (message[0] == '\t' || message[0] == ' ') {
8525             /* Part of the book output is here; append it */
8526             strcat(bookOutput, message);
8527             strcat(bookOutput, "  \n");
8528             return;
8529         } else if (bookOutput[0] != NULLCHAR) {
8530             /* All of book output has arrived; display it */
8531             char *p = bookOutput;
8532             while (*p != NULLCHAR) {
8533                 if (*p == '\t') *p = ' ';
8534                 p++;
8535             }
8536             DisplayInformation(bookOutput);
8537             bookRequested = FALSE;
8538             /* Fall through to parse the current output */
8539         }
8540     }
8541
8542     /*
8543      * Look for machine move.
8544      */
8545     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8546         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8547     {
8548         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8549             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8550             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8551             stalledEngine = cps;
8552             if(appData.ponderNextMove) { // bring opponent out of ponder
8553                 if(gameMode == TwoMachinesPlay) {
8554                     if(cps->other->pause)
8555                         PauseEngine(cps->other);
8556                     else
8557                         SendToProgram("easy\n", cps->other);
8558                 }
8559             }
8560             StopClocks();
8561             return;
8562         }
8563
8564         /* This method is only useful on engines that support ping */
8565         if (cps->lastPing != cps->lastPong) {
8566           if (gameMode == BeginningOfGame) {
8567             /* Extra move from before last new; ignore */
8568             if (appData.debugMode) {
8569                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8570             }
8571           } else {
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8574                         cps->which, gameMode);
8575             }
8576
8577             SendToProgram("undo\n", cps);
8578           }
8579           return;
8580         }
8581
8582         switch (gameMode) {
8583           case BeginningOfGame:
8584             /* Extra move from before last reset; ignore */
8585             if (appData.debugMode) {
8586                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8587             }
8588             return;
8589
8590           case EndOfGame:
8591           case IcsIdle:
8592           default:
8593             /* Extra move after we tried to stop.  The mode test is
8594                not a reliable way of detecting this problem, but it's
8595                the best we can do on engines that don't support ping.
8596             */
8597             if (appData.debugMode) {
8598                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8599                         cps->which, gameMode);
8600             }
8601             SendToProgram("undo\n", cps);
8602             return;
8603
8604           case MachinePlaysWhite:
8605           case IcsPlayingWhite:
8606             machineWhite = TRUE;
8607             break;
8608
8609           case MachinePlaysBlack:
8610           case IcsPlayingBlack:
8611             machineWhite = FALSE;
8612             break;
8613
8614           case TwoMachinesPlay:
8615             machineWhite = (cps->twoMachinesColor[0] == 'w');
8616             break;
8617         }
8618         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8619             if (appData.debugMode) {
8620                 fprintf(debugFP,
8621                         "Ignoring move out of turn by %s, gameMode %d"
8622                         ", forwardMost %d\n",
8623                         cps->which, gameMode, forwardMostMove);
8624             }
8625             return;
8626         }
8627
8628         if(cps->alphaRank) AlphaRank(machineMove, 4);
8629
8630         // [HGM] lion: (some very limited) support for Alien protocol
8631         killX = killY = -1;
8632         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8633             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8634             return;
8635         } else if(firstLeg[0]) { // there was a previous leg;
8636             // only support case where same piece makes two step (and don't even test that!)
8637             char buf[20], *p = machineMove+1, *q = buf+1, f;
8638             safeStrCpy(buf, machineMove, 20);
8639             while(isdigit(*q)) q++; // find start of to-square
8640             safeStrCpy(machineMove, firstLeg, 20);
8641             while(isdigit(*p)) p++;
8642             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8643             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8644             firstLeg[0] = NULLCHAR;
8645         }
8646
8647         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8648                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8649             /* Machine move could not be parsed; ignore it. */
8650           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8651                     machineMove, _(cps->which));
8652             DisplayMoveError(buf1);
8653             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8654                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8655             if (gameMode == TwoMachinesPlay) {
8656               GameEnds(machineWhite ? BlackWins : WhiteWins,
8657                        buf1, GE_XBOARD);
8658             }
8659             return;
8660         }
8661
8662         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8663         /* So we have to redo legality test with true e.p. status here,  */
8664         /* to make sure an illegal e.p. capture does not slip through,   */
8665         /* to cause a forfeit on a justified illegal-move complaint      */
8666         /* of the opponent.                                              */
8667         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8668            ChessMove moveType;
8669            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8670                              fromY, fromX, toY, toX, promoChar);
8671             if(moveType == IllegalMove) {
8672               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8673                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8674                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8675                            buf1, GE_XBOARD);
8676                 return;
8677            } else if(!appData.fischerCastling)
8678            /* [HGM] Kludge to handle engines that send FRC-style castling
8679               when they shouldn't (like TSCP-Gothic) */
8680            switch(moveType) {
8681              case WhiteASideCastleFR:
8682              case BlackASideCastleFR:
8683                toX+=2;
8684                currentMoveString[2]++;
8685                break;
8686              case WhiteHSideCastleFR:
8687              case BlackHSideCastleFR:
8688                toX--;
8689                currentMoveString[2]--;
8690                break;
8691              default: ; // nothing to do, but suppresses warning of pedantic compilers
8692            }
8693         }
8694         hintRequested = FALSE;
8695         lastHint[0] = NULLCHAR;
8696         bookRequested = FALSE;
8697         /* Program may be pondering now */
8698         cps->maybeThinking = TRUE;
8699         if (cps->sendTime == 2) cps->sendTime = 1;
8700         if (cps->offeredDraw) cps->offeredDraw--;
8701
8702         /* [AS] Save move info*/
8703         pvInfoList[ forwardMostMove ].score = programStats.score;
8704         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8705         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8706
8707         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8708
8709         /* Test suites abort the 'game' after one move */
8710         if(*appData.finger) {
8711            static FILE *f;
8712            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8713            if(!f) f = fopen(appData.finger, "w");
8714            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8715            else { DisplayFatalError("Bad output file", errno, 0); return; }
8716            free(fen);
8717            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8718         }
8719
8720         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8721         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8722             int count = 0;
8723
8724             while( count < adjudicateLossPlies ) {
8725                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8726
8727                 if( count & 1 ) {
8728                     score = -score; /* Flip score for winning side */
8729                 }
8730 printf("score=%d count=%d\n",score,count);
8731                 if( score > appData.adjudicateLossThreshold ) {
8732                     break;
8733                 }
8734
8735                 count++;
8736             }
8737
8738             if( count >= adjudicateLossPlies ) {
8739                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8740
8741                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8742                     "Xboard adjudication",
8743                     GE_XBOARD );
8744
8745                 return;
8746             }
8747         }
8748
8749         if(Adjudicate(cps)) {
8750             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8751             return; // [HGM] adjudicate: for all automatic game ends
8752         }
8753
8754 #if ZIPPY
8755         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8756             first.initDone) {
8757           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8758                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8759                 SendToICS("draw ");
8760                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8761           }
8762           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8763           ics_user_moved = 1;
8764           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8765                 char buf[3*MSG_SIZ];
8766
8767                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8768                         programStats.score / 100.,
8769                         programStats.depth,
8770                         programStats.time / 100.,
8771                         (unsigned int)programStats.nodes,
8772                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8773                         programStats.movelist);
8774                 SendToICS(buf);
8775           }
8776         }
8777 #endif
8778
8779         /* [AS] Clear stats for next move */
8780         ClearProgramStats();
8781         thinkOutput[0] = NULLCHAR;
8782         hiddenThinkOutputState = 0;
8783
8784         bookHit = NULL;
8785         if (gameMode == TwoMachinesPlay) {
8786             /* [HGM] relaying draw offers moved to after reception of move */
8787             /* and interpreting offer as claim if it brings draw condition */
8788             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8789                 SendToProgram("draw\n", cps->other);
8790             }
8791             if (cps->other->sendTime) {
8792                 SendTimeRemaining(cps->other,
8793                                   cps->other->twoMachinesColor[0] == 'w');
8794             }
8795             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8796             if (firstMove && !bookHit) {
8797                 firstMove = FALSE;
8798                 if (cps->other->useColors) {
8799                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8800                 }
8801                 SendToProgram("go\n", cps->other);
8802             }
8803             cps->other->maybeThinking = TRUE;
8804         }
8805
8806         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8807
8808         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8809
8810         if (!pausing && appData.ringBellAfterMoves) {
8811             if(!roar) RingBell();
8812         }
8813
8814         /*
8815          * Reenable menu items that were disabled while
8816          * machine was thinking
8817          */
8818         if (gameMode != TwoMachinesPlay)
8819             SetUserThinkingEnables();
8820
8821         // [HGM] book: after book hit opponent has received move and is now in force mode
8822         // force the book reply into it, and then fake that it outputted this move by jumping
8823         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8824         if(bookHit) {
8825                 static char bookMove[MSG_SIZ]; // a bit generous?
8826
8827                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8828                 strcat(bookMove, bookHit);
8829                 message = bookMove;
8830                 cps = cps->other;
8831                 programStats.nodes = programStats.depth = programStats.time =
8832                 programStats.score = programStats.got_only_move = 0;
8833                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8834
8835                 if(cps->lastPing != cps->lastPong) {
8836                     savedMessage = message; // args for deferred call
8837                     savedState = cps;
8838                     ScheduleDelayedEvent(DeferredBookMove, 10);
8839                     return;
8840                 }
8841                 goto FakeBookMove;
8842         }
8843
8844         return;
8845     }
8846
8847     /* Set special modes for chess engines.  Later something general
8848      *  could be added here; for now there is just one kludge feature,
8849      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8850      *  when "xboard" is given as an interactive command.
8851      */
8852     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8853         cps->useSigint = FALSE;
8854         cps->useSigterm = FALSE;
8855     }
8856     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8857       ParseFeatures(message+8, cps);
8858       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8859     }
8860
8861     if (!strncmp(message, "setup ", 6) && 
8862         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8863           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8864                                         ) { // [HGM] allow first engine to define opening position
8865       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8866       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8867       *buf = NULLCHAR;
8868       if(sscanf(message, "setup (%s", buf) == 1) {
8869         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8870         ASSIGN(appData.pieceToCharTable, buf);
8871       }
8872       if(startedFromSetupPosition) return;
8873       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8874       if(dummy >= 3) {
8875         while(message[s] && message[s++] != ' ');
8876         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8877            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8878             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8879             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8880           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8881           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8882         }
8883       }
8884       ParseFEN(boards[0], &dummy, message+s, FALSE);
8885       DrawPosition(TRUE, boards[0]);
8886       startedFromSetupPosition = TRUE;
8887       return;
8888     }
8889     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8890      * want this, I was asked to put it in, and obliged.
8891      */
8892     if (!strncmp(message, "setboard ", 9)) {
8893         Board initial_position;
8894
8895         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8896
8897         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8898             DisplayError(_("Bad FEN received from engine"), 0);
8899             return ;
8900         } else {
8901            Reset(TRUE, FALSE);
8902            CopyBoard(boards[0], initial_position);
8903            initialRulePlies = FENrulePlies;
8904            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8905            else gameMode = MachinePlaysBlack;
8906            DrawPosition(FALSE, boards[currentMove]);
8907         }
8908         return;
8909     }
8910
8911     /*
8912      * Look for communication commands
8913      */
8914     if (!strncmp(message, "telluser ", 9)) {
8915         if(message[9] == '\\' && message[10] == '\\')
8916             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8917         PlayTellSound();
8918         DisplayNote(message + 9);
8919         return;
8920     }
8921     if (!strncmp(message, "tellusererror ", 14)) {
8922         cps->userError = 1;
8923         if(message[14] == '\\' && message[15] == '\\')
8924             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8925         PlayTellSound();
8926         DisplayError(message + 14, 0);
8927         return;
8928     }
8929     if (!strncmp(message, "tellopponent ", 13)) {
8930       if (appData.icsActive) {
8931         if (loggedOn) {
8932           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8933           SendToICS(buf1);
8934         }
8935       } else {
8936         DisplayNote(message + 13);
8937       }
8938       return;
8939     }
8940     if (!strncmp(message, "tellothers ", 11)) {
8941       if (appData.icsActive) {
8942         if (loggedOn) {
8943           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8944           SendToICS(buf1);
8945         }
8946       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8947       return;
8948     }
8949     if (!strncmp(message, "tellall ", 8)) {
8950       if (appData.icsActive) {
8951         if (loggedOn) {
8952           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8953           SendToICS(buf1);
8954         }
8955       } else {
8956         DisplayNote(message + 8);
8957       }
8958       return;
8959     }
8960     if (strncmp(message, "warning", 7) == 0) {
8961         /* Undocumented feature, use tellusererror in new code */
8962         DisplayError(message, 0);
8963         return;
8964     }
8965     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8966         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8967         strcat(realname, " query");
8968         AskQuestion(realname, buf2, buf1, cps->pr);
8969         return;
8970     }
8971     /* Commands from the engine directly to ICS.  We don't allow these to be
8972      *  sent until we are logged on. Crafty kibitzes have been known to
8973      *  interfere with the login process.
8974      */
8975     if (loggedOn) {
8976         if (!strncmp(message, "tellics ", 8)) {
8977             SendToICS(message + 8);
8978             SendToICS("\n");
8979             return;
8980         }
8981         if (!strncmp(message, "tellicsnoalias ", 15)) {
8982             SendToICS(ics_prefix);
8983             SendToICS(message + 15);
8984             SendToICS("\n");
8985             return;
8986         }
8987         /* The following are for backward compatibility only */
8988         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8989             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8990             SendToICS(ics_prefix);
8991             SendToICS(message);
8992             SendToICS("\n");
8993             return;
8994         }
8995     }
8996     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8997         if(initPing == cps->lastPong) {
8998             if(gameInfo.variant == VariantUnknown) {
8999                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9000                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9001                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9002             }
9003             initPing = -1;
9004         }
9005         return;
9006     }
9007     if(!strncmp(message, "highlight ", 10)) {
9008         if(appData.testLegality && appData.markers) return;
9009         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9010         return;
9011     }
9012     if(!strncmp(message, "click ", 6)) {
9013         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9014         if(appData.testLegality || !appData.oneClick) return;
9015         sscanf(message+6, "%c%d%c", &f, &y, &c);
9016         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9017         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9018         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9019         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9020         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9021         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9022             LeftClick(Release, lastLeftX, lastLeftY);
9023         controlKey  = (c == ',');
9024         LeftClick(Press, x, y);
9025         LeftClick(Release, x, y);
9026         first.highlight = f;
9027         return;
9028     }
9029     /*
9030      * If the move is illegal, cancel it and redraw the board.
9031      * Also deal with other error cases.  Matching is rather loose
9032      * here to accommodate engines written before the spec.
9033      */
9034     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9035         strncmp(message, "Error", 5) == 0) {
9036         if (StrStr(message, "name") ||
9037             StrStr(message, "rating") || StrStr(message, "?") ||
9038             StrStr(message, "result") || StrStr(message, "board") ||
9039             StrStr(message, "bk") || StrStr(message, "computer") ||
9040             StrStr(message, "variant") || StrStr(message, "hint") ||
9041             StrStr(message, "random") || StrStr(message, "depth") ||
9042             StrStr(message, "accepted")) {
9043             return;
9044         }
9045         if (StrStr(message, "protover")) {
9046           /* Program is responding to input, so it's apparently done
9047              initializing, and this error message indicates it is
9048              protocol version 1.  So we don't need to wait any longer
9049              for it to initialize and send feature commands. */
9050           FeatureDone(cps, 1);
9051           cps->protocolVersion = 1;
9052           return;
9053         }
9054         cps->maybeThinking = FALSE;
9055
9056         if (StrStr(message, "draw")) {
9057             /* Program doesn't have "draw" command */
9058             cps->sendDrawOffers = 0;
9059             return;
9060         }
9061         if (cps->sendTime != 1 &&
9062             (StrStr(message, "time") || StrStr(message, "otim"))) {
9063           /* Program apparently doesn't have "time" or "otim" command */
9064           cps->sendTime = 0;
9065           return;
9066         }
9067         if (StrStr(message, "analyze")) {
9068             cps->analysisSupport = FALSE;
9069             cps->analyzing = FALSE;
9070 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9071             EditGameEvent(); // [HGM] try to preserve loaded game
9072             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9073             DisplayError(buf2, 0);
9074             return;
9075         }
9076         if (StrStr(message, "(no matching move)st")) {
9077           /* Special kludge for GNU Chess 4 only */
9078           cps->stKludge = TRUE;
9079           SendTimeControl(cps, movesPerSession, timeControl,
9080                           timeIncrement, appData.searchDepth,
9081                           searchTime);
9082           return;
9083         }
9084         if (StrStr(message, "(no matching move)sd")) {
9085           /* Special kludge for GNU Chess 4 only */
9086           cps->sdKludge = TRUE;
9087           SendTimeControl(cps, movesPerSession, timeControl,
9088                           timeIncrement, appData.searchDepth,
9089                           searchTime);
9090           return;
9091         }
9092         if (!StrStr(message, "llegal")) {
9093             return;
9094         }
9095         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9096             gameMode == IcsIdle) return;
9097         if (forwardMostMove <= backwardMostMove) return;
9098         if (pausing) PauseEvent();
9099       if(appData.forceIllegal) {
9100             // [HGM] illegal: machine refused move; force position after move into it
9101           SendToProgram("force\n", cps);
9102           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9103                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9104                 // when black is to move, while there might be nothing on a2 or black
9105                 // might already have the move. So send the board as if white has the move.
9106                 // But first we must change the stm of the engine, as it refused the last move
9107                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9108                 if(WhiteOnMove(forwardMostMove)) {
9109                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9110                     SendBoard(cps, forwardMostMove); // kludgeless board
9111                 } else {
9112                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9113                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9114                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9115                 }
9116           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9117             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9118                  gameMode == TwoMachinesPlay)
9119               SendToProgram("go\n", cps);
9120             return;
9121       } else
9122         if (gameMode == PlayFromGameFile) {
9123             /* Stop reading this game file */
9124             gameMode = EditGame;
9125             ModeHighlight();
9126         }
9127         /* [HGM] illegal-move claim should forfeit game when Xboard */
9128         /* only passes fully legal moves                            */
9129         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9130             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9131                                 "False illegal-move claim", GE_XBOARD );
9132             return; // do not take back move we tested as valid
9133         }
9134         currentMove = forwardMostMove-1;
9135         DisplayMove(currentMove-1); /* before DisplayMoveError */
9136         SwitchClocks(forwardMostMove-1); // [HGM] race
9137         DisplayBothClocks();
9138         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9139                 parseList[currentMove], _(cps->which));
9140         DisplayMoveError(buf1);
9141         DrawPosition(FALSE, boards[currentMove]);
9142
9143         SetUserThinkingEnables();
9144         return;
9145     }
9146     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9147         /* Program has a broken "time" command that
9148            outputs a string not ending in newline.
9149            Don't use it. */
9150         cps->sendTime = 0;
9151     }
9152     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9153         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9154             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9155     }
9156
9157     /*
9158      * If chess program startup fails, exit with an error message.
9159      * Attempts to recover here are futile. [HGM] Well, we try anyway
9160      */
9161     if ((StrStr(message, "unknown host") != NULL)
9162         || (StrStr(message, "No remote directory") != NULL)
9163         || (StrStr(message, "not found") != NULL)
9164         || (StrStr(message, "No such file") != NULL)
9165         || (StrStr(message, "can't alloc") != NULL)
9166         || (StrStr(message, "Permission denied") != NULL)) {
9167
9168         cps->maybeThinking = FALSE;
9169         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9170                 _(cps->which), cps->program, cps->host, message);
9171         RemoveInputSource(cps->isr);
9172         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9173             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9174             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9175         }
9176         return;
9177     }
9178
9179     /*
9180      * Look for hint output
9181      */
9182     if (sscanf(message, "Hint: %s", buf1) == 1) {
9183         if (cps == &first && hintRequested) {
9184             hintRequested = FALSE;
9185             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9186                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9187                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9188                                     PosFlags(forwardMostMove),
9189                                     fromY, fromX, toY, toX, promoChar, buf1);
9190                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9191                 DisplayInformation(buf2);
9192             } else {
9193                 /* Hint move could not be parsed!? */
9194               snprintf(buf2, sizeof(buf2),
9195                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9196                         buf1, _(cps->which));
9197                 DisplayError(buf2, 0);
9198             }
9199         } else {
9200           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9201         }
9202         return;
9203     }
9204
9205     /*
9206      * Ignore other messages if game is not in progress
9207      */
9208     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9209         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9210
9211     /*
9212      * look for win, lose, draw, or draw offer
9213      */
9214     if (strncmp(message, "1-0", 3) == 0) {
9215         char *p, *q, *r = "";
9216         p = strchr(message, '{');
9217         if (p) {
9218             q = strchr(p, '}');
9219             if (q) {
9220                 *q = NULLCHAR;
9221                 r = p + 1;
9222             }
9223         }
9224         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9225         return;
9226     } else if (strncmp(message, "0-1", 3) == 0) {
9227         char *p, *q, *r = "";
9228         p = strchr(message, '{');
9229         if (p) {
9230             q = strchr(p, '}');
9231             if (q) {
9232                 *q = NULLCHAR;
9233                 r = p + 1;
9234             }
9235         }
9236         /* Kludge for Arasan 4.1 bug */
9237         if (strcmp(r, "Black resigns") == 0) {
9238             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9239             return;
9240         }
9241         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9242         return;
9243     } else if (strncmp(message, "1/2", 3) == 0) {
9244         char *p, *q, *r = "";
9245         p = strchr(message, '{');
9246         if (p) {
9247             q = strchr(p, '}');
9248             if (q) {
9249                 *q = NULLCHAR;
9250                 r = p + 1;
9251             }
9252         }
9253
9254         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9255         return;
9256
9257     } else if (strncmp(message, "White resign", 12) == 0) {
9258         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9259         return;
9260     } else if (strncmp(message, "Black resign", 12) == 0) {
9261         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262         return;
9263     } else if (strncmp(message, "White matches", 13) == 0 ||
9264                strncmp(message, "Black matches", 13) == 0   ) {
9265         /* [HGM] ignore GNUShogi noises */
9266         return;
9267     } else if (strncmp(message, "White", 5) == 0 &&
9268                message[5] != '(' &&
9269                StrStr(message, "Black") == NULL) {
9270         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9271         return;
9272     } else if (strncmp(message, "Black", 5) == 0 &&
9273                message[5] != '(') {
9274         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strcmp(message, "resign") == 0 ||
9277                strcmp(message, "computer resigns") == 0) {
9278         switch (gameMode) {
9279           case MachinePlaysBlack:
9280           case IcsPlayingBlack:
9281             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9282             break;
9283           case MachinePlaysWhite:
9284           case IcsPlayingWhite:
9285             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9286             break;
9287           case TwoMachinesPlay:
9288             if (cps->twoMachinesColor[0] == 'w')
9289               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9290             else
9291               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9292             break;
9293           default:
9294             /* can't happen */
9295             break;
9296         }
9297         return;
9298     } else if (strncmp(message, "opponent mates", 14) == 0) {
9299         switch (gameMode) {
9300           case MachinePlaysBlack:
9301           case IcsPlayingBlack:
9302             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9303             break;
9304           case MachinePlaysWhite:
9305           case IcsPlayingWhite:
9306             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9307             break;
9308           case TwoMachinesPlay:
9309             if (cps->twoMachinesColor[0] == 'w')
9310               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9311             else
9312               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9313             break;
9314           default:
9315             /* can't happen */
9316             break;
9317         }
9318         return;
9319     } else if (strncmp(message, "computer mates", 14) == 0) {
9320         switch (gameMode) {
9321           case MachinePlaysBlack:
9322           case IcsPlayingBlack:
9323             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9324             break;
9325           case MachinePlaysWhite:
9326           case IcsPlayingWhite:
9327             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9328             break;
9329           case TwoMachinesPlay:
9330             if (cps->twoMachinesColor[0] == 'w')
9331               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9332             else
9333               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9334             break;
9335           default:
9336             /* can't happen */
9337             break;
9338         }
9339         return;
9340     } else if (strncmp(message, "checkmate", 9) == 0) {
9341         if (WhiteOnMove(forwardMostMove)) {
9342             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9343         } else {
9344             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9345         }
9346         return;
9347     } else if (strstr(message, "Draw") != NULL ||
9348                strstr(message, "game is a draw") != NULL) {
9349         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9350         return;
9351     } else if (strstr(message, "offer") != NULL &&
9352                strstr(message, "draw") != NULL) {
9353 #if ZIPPY
9354         if (appData.zippyPlay && first.initDone) {
9355             /* Relay offer to ICS */
9356             SendToICS(ics_prefix);
9357             SendToICS("draw\n");
9358         }
9359 #endif
9360         cps->offeredDraw = 2; /* valid until this engine moves twice */
9361         if (gameMode == TwoMachinesPlay) {
9362             if (cps->other->offeredDraw) {
9363                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9364             /* [HGM] in two-machine mode we delay relaying draw offer      */
9365             /* until after we also have move, to see if it is really claim */
9366             }
9367         } else if (gameMode == MachinePlaysWhite ||
9368                    gameMode == MachinePlaysBlack) {
9369           if (userOfferedDraw) {
9370             DisplayInformation(_("Machine accepts your draw offer"));
9371             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9372           } else {
9373             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9374           }
9375         }
9376     }
9377
9378
9379     /*
9380      * Look for thinking output
9381      */
9382     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9383           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9384                                 ) {
9385         int plylev, mvleft, mvtot, curscore, time;
9386         char mvname[MOVE_LEN];
9387         u64 nodes; // [DM]
9388         char plyext;
9389         int ignore = FALSE;
9390         int prefixHint = FALSE;
9391         mvname[0] = NULLCHAR;
9392
9393         switch (gameMode) {
9394           case MachinePlaysBlack:
9395           case IcsPlayingBlack:
9396             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9397             break;
9398           case MachinePlaysWhite:
9399           case IcsPlayingWhite:
9400             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9401             break;
9402           case AnalyzeMode:
9403           case AnalyzeFile:
9404             break;
9405           case IcsObserving: /* [DM] icsEngineAnalyze */
9406             if (!appData.icsEngineAnalyze) ignore = TRUE;
9407             break;
9408           case TwoMachinesPlay:
9409             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9410                 ignore = TRUE;
9411             }
9412             break;
9413           default:
9414             ignore = TRUE;
9415             break;
9416         }
9417
9418         if (!ignore) {
9419             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9420             buf1[0] = NULLCHAR;
9421             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9422                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9423
9424                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9425                     nodes += u64Const(0x100000000);
9426
9427                 if (plyext != ' ' && plyext != '\t') {
9428                     time *= 100;
9429                 }
9430
9431                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9432                 if( cps->scoreIsAbsolute &&
9433                     ( gameMode == MachinePlaysBlack ||
9434                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9435                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9436                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9437                      !WhiteOnMove(currentMove)
9438                     ) )
9439                 {
9440                     curscore = -curscore;
9441                 }
9442
9443                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9444
9445                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9446                         char buf[MSG_SIZ];
9447                         FILE *f;
9448                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9449                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9450                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9451                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9452                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9453                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9454                                 fclose(f);
9455                         }
9456                         else
9457                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9458                           DisplayError(_("failed writing PV"), 0);
9459                 }
9460
9461                 tempStats.depth = plylev;
9462                 tempStats.nodes = nodes;
9463                 tempStats.time = time;
9464                 tempStats.score = curscore;
9465                 tempStats.got_only_move = 0;
9466
9467                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9468                         int ticklen;
9469
9470                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9471                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9472                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9473                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9474                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9475                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9476                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9477                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9478                 }
9479
9480                 /* Buffer overflow protection */
9481                 if (pv[0] != NULLCHAR) {
9482                     if (strlen(pv) >= sizeof(tempStats.movelist)
9483                         && appData.debugMode) {
9484                         fprintf(debugFP,
9485                                 "PV is too long; using the first %u bytes.\n",
9486                                 (unsigned) sizeof(tempStats.movelist) - 1);
9487                     }
9488
9489                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9490                 } else {
9491                     sprintf(tempStats.movelist, " no PV\n");
9492                 }
9493
9494                 if (tempStats.seen_stat) {
9495                     tempStats.ok_to_send = 1;
9496                 }
9497
9498                 if (strchr(tempStats.movelist, '(') != NULL) {
9499                     tempStats.line_is_book = 1;
9500                     tempStats.nr_moves = 0;
9501                     tempStats.moves_left = 0;
9502                 } else {
9503                     tempStats.line_is_book = 0;
9504                 }
9505
9506                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9507                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9508
9509                 SendProgramStatsToFrontend( cps, &tempStats );
9510
9511                 /*
9512                     [AS] Protect the thinkOutput buffer from overflow... this
9513                     is only useful if buf1 hasn't overflowed first!
9514                 */
9515                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9516                          plylev,
9517                          (gameMode == TwoMachinesPlay ?
9518                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9519                          ((double) curscore) / 100.0,
9520                          prefixHint ? lastHint : "",
9521                          prefixHint ? " " : "" );
9522
9523                 if( buf1[0] != NULLCHAR ) {
9524                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9525
9526                     if( strlen(pv) > max_len ) {
9527                         if( appData.debugMode) {
9528                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9529                         }
9530                         pv[max_len+1] = '\0';
9531                     }
9532
9533                     strcat( thinkOutput, pv);
9534                 }
9535
9536                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9537                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9538                     DisplayMove(currentMove - 1);
9539                 }
9540                 return;
9541
9542             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9543                 /* crafty (9.25+) says "(only move) <move>"
9544                  * if there is only 1 legal move
9545                  */
9546                 sscanf(p, "(only move) %s", buf1);
9547                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9548                 sprintf(programStats.movelist, "%s (only move)", buf1);
9549                 programStats.depth = 1;
9550                 programStats.nr_moves = 1;
9551                 programStats.moves_left = 1;
9552                 programStats.nodes = 1;
9553                 programStats.time = 1;
9554                 programStats.got_only_move = 1;
9555
9556                 /* Not really, but we also use this member to
9557                    mean "line isn't going to change" (Crafty
9558                    isn't searching, so stats won't change) */
9559                 programStats.line_is_book = 1;
9560
9561                 SendProgramStatsToFrontend( cps, &programStats );
9562
9563                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9564                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9565                     DisplayMove(currentMove - 1);
9566                 }
9567                 return;
9568             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9569                               &time, &nodes, &plylev, &mvleft,
9570                               &mvtot, mvname) >= 5) {
9571                 /* The stat01: line is from Crafty (9.29+) in response
9572                    to the "." command */
9573                 programStats.seen_stat = 1;
9574                 cps->maybeThinking = TRUE;
9575
9576                 if (programStats.got_only_move || !appData.periodicUpdates)
9577                   return;
9578
9579                 programStats.depth = plylev;
9580                 programStats.time = time;
9581                 programStats.nodes = nodes;
9582                 programStats.moves_left = mvleft;
9583                 programStats.nr_moves = mvtot;
9584                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9585                 programStats.ok_to_send = 1;
9586                 programStats.movelist[0] = '\0';
9587
9588                 SendProgramStatsToFrontend( cps, &programStats );
9589
9590                 return;
9591
9592             } else if (strncmp(message,"++",2) == 0) {
9593                 /* Crafty 9.29+ outputs this */
9594                 programStats.got_fail = 2;
9595                 return;
9596
9597             } else if (strncmp(message,"--",2) == 0) {
9598                 /* Crafty 9.29+ outputs this */
9599                 programStats.got_fail = 1;
9600                 return;
9601
9602             } else if (thinkOutput[0] != NULLCHAR &&
9603                        strncmp(message, "    ", 4) == 0) {
9604                 unsigned message_len;
9605
9606                 p = message;
9607                 while (*p && *p == ' ') p++;
9608
9609                 message_len = strlen( p );
9610
9611                 /* [AS] Avoid buffer overflow */
9612                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9613                     strcat(thinkOutput, " ");
9614                     strcat(thinkOutput, p);
9615                 }
9616
9617                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9618                     strcat(programStats.movelist, " ");
9619                     strcat(programStats.movelist, p);
9620                 }
9621
9622                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9623                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9624                     DisplayMove(currentMove - 1);
9625                 }
9626                 return;
9627             }
9628         }
9629         else {
9630             buf1[0] = NULLCHAR;
9631
9632             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9633                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9634             {
9635                 ChessProgramStats cpstats;
9636
9637                 if (plyext != ' ' && plyext != '\t') {
9638                     time *= 100;
9639                 }
9640
9641                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9642                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9643                     curscore = -curscore;
9644                 }
9645
9646                 cpstats.depth = plylev;
9647                 cpstats.nodes = nodes;
9648                 cpstats.time = time;
9649                 cpstats.score = curscore;
9650                 cpstats.got_only_move = 0;
9651                 cpstats.movelist[0] = '\0';
9652
9653                 if (buf1[0] != NULLCHAR) {
9654                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9655                 }
9656
9657                 cpstats.ok_to_send = 0;
9658                 cpstats.line_is_book = 0;
9659                 cpstats.nr_moves = 0;
9660                 cpstats.moves_left = 0;
9661
9662                 SendProgramStatsToFrontend( cps, &cpstats );
9663             }
9664         }
9665     }
9666 }
9667
9668
9669 /* Parse a game score from the character string "game", and
9670    record it as the history of the current game.  The game
9671    score is NOT assumed to start from the standard position.
9672    The display is not updated in any way.
9673    */
9674 void
9675 ParseGameHistory (char *game)
9676 {
9677     ChessMove moveType;
9678     int fromX, fromY, toX, toY, boardIndex;
9679     char promoChar;
9680     char *p, *q;
9681     char buf[MSG_SIZ];
9682
9683     if (appData.debugMode)
9684       fprintf(debugFP, "Parsing game history: %s\n", game);
9685
9686     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9687     gameInfo.site = StrSave(appData.icsHost);
9688     gameInfo.date = PGNDate();
9689     gameInfo.round = StrSave("-");
9690
9691     /* Parse out names of players */
9692     while (*game == ' ') game++;
9693     p = buf;
9694     while (*game != ' ') *p++ = *game++;
9695     *p = NULLCHAR;
9696     gameInfo.white = StrSave(buf);
9697     while (*game == ' ') game++;
9698     p = buf;
9699     while (*game != ' ' && *game != '\n') *p++ = *game++;
9700     *p = NULLCHAR;
9701     gameInfo.black = StrSave(buf);
9702
9703     /* Parse moves */
9704     boardIndex = blackPlaysFirst ? 1 : 0;
9705     yynewstr(game);
9706     for (;;) {
9707         yyboardindex = boardIndex;
9708         moveType = (ChessMove) Myylex();
9709         switch (moveType) {
9710           case IllegalMove:             /* maybe suicide chess, etc. */
9711   if (appData.debugMode) {
9712     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9713     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9714     setbuf(debugFP, NULL);
9715   }
9716           case WhitePromotion:
9717           case BlackPromotion:
9718           case WhiteNonPromotion:
9719           case BlackNonPromotion:
9720           case NormalMove:
9721           case FirstLeg:
9722           case WhiteCapturesEnPassant:
9723           case BlackCapturesEnPassant:
9724           case WhiteKingSideCastle:
9725           case WhiteQueenSideCastle:
9726           case BlackKingSideCastle:
9727           case BlackQueenSideCastle:
9728           case WhiteKingSideCastleWild:
9729           case WhiteQueenSideCastleWild:
9730           case BlackKingSideCastleWild:
9731           case BlackQueenSideCastleWild:
9732           /* PUSH Fabien */
9733           case WhiteHSideCastleFR:
9734           case WhiteASideCastleFR:
9735           case BlackHSideCastleFR:
9736           case BlackASideCastleFR:
9737           /* POP Fabien */
9738             fromX = currentMoveString[0] - AAA;
9739             fromY = currentMoveString[1] - ONE;
9740             toX = currentMoveString[2] - AAA;
9741             toY = currentMoveString[3] - ONE;
9742             promoChar = currentMoveString[4];
9743             break;
9744           case WhiteDrop:
9745           case BlackDrop:
9746             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9747             fromX = moveType == WhiteDrop ?
9748               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9749             (int) CharToPiece(ToLower(currentMoveString[0]));
9750             fromY = DROP_RANK;
9751             toX = currentMoveString[2] - AAA;
9752             toY = currentMoveString[3] - ONE;
9753             promoChar = NULLCHAR;
9754             break;
9755           case AmbiguousMove:
9756             /* bug? */
9757             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9758   if (appData.debugMode) {
9759     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9761     setbuf(debugFP, NULL);
9762   }
9763             DisplayError(buf, 0);
9764             return;
9765           case ImpossibleMove:
9766             /* bug? */
9767             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9768   if (appData.debugMode) {
9769     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9770     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9771     setbuf(debugFP, NULL);
9772   }
9773             DisplayError(buf, 0);
9774             return;
9775           case EndOfFile:
9776             if (boardIndex < backwardMostMove) {
9777                 /* Oops, gap.  How did that happen? */
9778                 DisplayError(_("Gap in move list"), 0);
9779                 return;
9780             }
9781             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9782             if (boardIndex > forwardMostMove) {
9783                 forwardMostMove = boardIndex;
9784             }
9785             return;
9786           case ElapsedTime:
9787             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9788                 strcat(parseList[boardIndex-1], " ");
9789                 strcat(parseList[boardIndex-1], yy_text);
9790             }
9791             continue;
9792           case Comment:
9793           case PGNTag:
9794           case NAG:
9795           default:
9796             /* ignore */
9797             continue;
9798           case WhiteWins:
9799           case BlackWins:
9800           case GameIsDrawn:
9801           case GameUnfinished:
9802             if (gameMode == IcsExamining) {
9803                 if (boardIndex < backwardMostMove) {
9804                     /* Oops, gap.  How did that happen? */
9805                     return;
9806                 }
9807                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9808                 return;
9809             }
9810             gameInfo.result = moveType;
9811             p = strchr(yy_text, '{');
9812             if (p == NULL) p = strchr(yy_text, '(');
9813             if (p == NULL) {
9814                 p = yy_text;
9815                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9816             } else {
9817                 q = strchr(p, *p == '{' ? '}' : ')');
9818                 if (q != NULL) *q = NULLCHAR;
9819                 p++;
9820             }
9821             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9822             gameInfo.resultDetails = StrSave(p);
9823             continue;
9824         }
9825         if (boardIndex >= forwardMostMove &&
9826             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9827             backwardMostMove = blackPlaysFirst ? 1 : 0;
9828             return;
9829         }
9830         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9831                                  fromY, fromX, toY, toX, promoChar,
9832                                  parseList[boardIndex]);
9833         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9834         /* currentMoveString is set as a side-effect of yylex */
9835         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9836         strcat(moveList[boardIndex], "\n");
9837         boardIndex++;
9838         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9839         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9840           case MT_NONE:
9841           case MT_STALEMATE:
9842           default:
9843             break;
9844           case MT_CHECK:
9845             if(!IS_SHOGI(gameInfo.variant))
9846                 strcat(parseList[boardIndex - 1], "+");
9847             break;
9848           case MT_CHECKMATE:
9849           case MT_STAINMATE:
9850             strcat(parseList[boardIndex - 1], "#");
9851             break;
9852         }
9853     }
9854 }
9855
9856
9857 /* Apply a move to the given board  */
9858 void
9859 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9860 {
9861   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9862   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9863
9864     /* [HGM] compute & store e.p. status and castling rights for new position */
9865     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9866
9867       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9868       oldEP = (signed char)board[EP_STATUS];
9869       board[EP_STATUS] = EP_NONE;
9870
9871   if (fromY == DROP_RANK) {
9872         /* must be first */
9873         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9874             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9875             return;
9876         }
9877         piece = board[toY][toX] = (ChessSquare) fromX;
9878   } else {
9879 //      ChessSquare victim;
9880       int i;
9881
9882       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9883 //           victim = board[killY][killX],
9884            board[killY][killX] = EmptySquare,
9885            board[EP_STATUS] = EP_CAPTURE;
9886
9887       if( board[toY][toX] != EmptySquare ) {
9888            board[EP_STATUS] = EP_CAPTURE;
9889            if( (fromX != toX || fromY != toY) && // not igui!
9890                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9891                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9892                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9893            }
9894       }
9895
9896       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9897            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9898                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9899       } else
9900       if( board[fromY][fromX] == WhitePawn ) {
9901            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9902                board[EP_STATUS] = EP_PAWN_MOVE;
9903            if( toY-fromY==2) {
9904                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9905                         gameInfo.variant != VariantBerolina || toX < fromX)
9906                       board[EP_STATUS] = toX | berolina;
9907                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9908                         gameInfo.variant != VariantBerolina || toX > fromX)
9909                       board[EP_STATUS] = toX;
9910            }
9911       } else
9912       if( board[fromY][fromX] == BlackPawn ) {
9913            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9914                board[EP_STATUS] = EP_PAWN_MOVE;
9915            if( toY-fromY== -2) {
9916                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9917                         gameInfo.variant != VariantBerolina || toX < fromX)
9918                       board[EP_STATUS] = toX | berolina;
9919                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9920                         gameInfo.variant != VariantBerolina || toX > fromX)
9921                       board[EP_STATUS] = toX;
9922            }
9923        }
9924
9925        for(i=0; i<nrCastlingRights; i++) {
9926            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9927               board[CASTLING][i] == toX   && castlingRank[i] == toY
9928              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9929        }
9930
9931        if(gameInfo.variant == VariantSChess) { // update virginity
9932            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9933            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9934            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9935            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9936        }
9937
9938      if (fromX == toX && fromY == toY) return;
9939
9940      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9941      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9942      if(gameInfo.variant == VariantKnightmate)
9943          king += (int) WhiteUnicorn - (int) WhiteKing;
9944
9945     /* Code added by Tord: */
9946     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9947     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9948         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9949       board[fromY][fromX] = EmptySquare;
9950       board[toY][toX] = EmptySquare;
9951       if((toX > fromX) != (piece == WhiteRook)) {
9952         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9953       } else {
9954         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9955       }
9956     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9957                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9958       board[fromY][fromX] = EmptySquare;
9959       board[toY][toX] = EmptySquare;
9960       if((toX > fromX) != (piece == BlackRook)) {
9961         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9962       } else {
9963         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9964       }
9965     /* End of code added by Tord */
9966
9967     } else if (board[fromY][fromX] == king
9968         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9969         && toY == fromY && toX > fromX+1) {
9970         board[fromY][fromX] = EmptySquare;
9971         board[toY][toX] = king;
9972         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9973         board[fromY][BOARD_RGHT-1] = EmptySquare;
9974     } else if (board[fromY][fromX] == king
9975         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9976                && toY == fromY && toX < fromX-1) {
9977         board[fromY][fromX] = EmptySquare;
9978         board[toY][toX] = king;
9979         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9980         board[fromY][BOARD_LEFT] = EmptySquare;
9981     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9982                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9983                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9984                ) {
9985         /* white pawn promotion */
9986         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9987         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9988             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9989         board[fromY][fromX] = EmptySquare;
9990     } else if ((fromY >= BOARD_HEIGHT>>1)
9991                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9992                && (toX != fromX)
9993                && gameInfo.variant != VariantXiangqi
9994                && gameInfo.variant != VariantBerolina
9995                && (board[fromY][fromX] == WhitePawn)
9996                && (board[toY][toX] == EmptySquare)) {
9997         board[fromY][fromX] = EmptySquare;
9998         board[toY][toX] = WhitePawn;
9999         captured = board[toY - 1][toX];
10000         board[toY - 1][toX] = EmptySquare;
10001     } else if ((fromY == BOARD_HEIGHT-4)
10002                && (toX == fromX)
10003                && gameInfo.variant == VariantBerolina
10004                && (board[fromY][fromX] == WhitePawn)
10005                && (board[toY][toX] == EmptySquare)) {
10006         board[fromY][fromX] = EmptySquare;
10007         board[toY][toX] = WhitePawn;
10008         if(oldEP & EP_BEROLIN_A) {
10009                 captured = board[fromY][fromX-1];
10010                 board[fromY][fromX-1] = EmptySquare;
10011         }else{  captured = board[fromY][fromX+1];
10012                 board[fromY][fromX+1] = EmptySquare;
10013         }
10014     } else if (board[fromY][fromX] == king
10015         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10016                && toY == fromY && toX > fromX+1) {
10017         board[fromY][fromX] = EmptySquare;
10018         board[toY][toX] = king;
10019         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10020         board[fromY][BOARD_RGHT-1] = EmptySquare;
10021     } else if (board[fromY][fromX] == king
10022         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10023                && toY == fromY && toX < fromX-1) {
10024         board[fromY][fromX] = EmptySquare;
10025         board[toY][toX] = king;
10026         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10027         board[fromY][BOARD_LEFT] = EmptySquare;
10028     } else if (fromY == 7 && fromX == 3
10029                && board[fromY][fromX] == BlackKing
10030                && toY == 7 && toX == 5) {
10031         board[fromY][fromX] = EmptySquare;
10032         board[toY][toX] = BlackKing;
10033         board[fromY][7] = EmptySquare;
10034         board[toY][4] = BlackRook;
10035     } else if (fromY == 7 && fromX == 3
10036                && board[fromY][fromX] == BlackKing
10037                && toY == 7 && toX == 1) {
10038         board[fromY][fromX] = EmptySquare;
10039         board[toY][toX] = BlackKing;
10040         board[fromY][0] = EmptySquare;
10041         board[toY][2] = BlackRook;
10042     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10043                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10044                && toY < promoRank && promoChar
10045                ) {
10046         /* black pawn promotion */
10047         board[toY][toX] = CharToPiece(ToLower(promoChar));
10048         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10049             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10050         board[fromY][fromX] = EmptySquare;
10051     } else if ((fromY < BOARD_HEIGHT>>1)
10052                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10053                && (toX != fromX)
10054                && gameInfo.variant != VariantXiangqi
10055                && gameInfo.variant != VariantBerolina
10056                && (board[fromY][fromX] == BlackPawn)
10057                && (board[toY][toX] == EmptySquare)) {
10058         board[fromY][fromX] = EmptySquare;
10059         board[toY][toX] = BlackPawn;
10060         captured = board[toY + 1][toX];
10061         board[toY + 1][toX] = EmptySquare;
10062     } else if ((fromY == 3)
10063                && (toX == fromX)
10064                && gameInfo.variant == VariantBerolina
10065                && (board[fromY][fromX] == BlackPawn)
10066                && (board[toY][toX] == EmptySquare)) {
10067         board[fromY][fromX] = EmptySquare;
10068         board[toY][toX] = BlackPawn;
10069         if(oldEP & EP_BEROLIN_A) {
10070                 captured = board[fromY][fromX-1];
10071                 board[fromY][fromX-1] = EmptySquare;
10072         }else{  captured = board[fromY][fromX+1];
10073                 board[fromY][fromX+1] = EmptySquare;
10074         }
10075     } else {
10076         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10077         board[fromY][fromX] = EmptySquare;
10078         board[toY][toX] = piece;
10079     }
10080   }
10081
10082     if (gameInfo.holdingsWidth != 0) {
10083
10084       /* !!A lot more code needs to be written to support holdings  */
10085       /* [HGM] OK, so I have written it. Holdings are stored in the */
10086       /* penultimate board files, so they are automaticlly stored   */
10087       /* in the game history.                                       */
10088       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10089                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10090         /* Delete from holdings, by decreasing count */
10091         /* and erasing image if necessary            */
10092         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10093         if(p < (int) BlackPawn) { /* white drop */
10094              p -= (int)WhitePawn;
10095                  p = PieceToNumber((ChessSquare)p);
10096              if(p >= gameInfo.holdingsSize) p = 0;
10097              if(--board[p][BOARD_WIDTH-2] <= 0)
10098                   board[p][BOARD_WIDTH-1] = EmptySquare;
10099              if((int)board[p][BOARD_WIDTH-2] < 0)
10100                         board[p][BOARD_WIDTH-2] = 0;
10101         } else {                  /* black drop */
10102              p -= (int)BlackPawn;
10103                  p = PieceToNumber((ChessSquare)p);
10104              if(p >= gameInfo.holdingsSize) p = 0;
10105              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10106                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10107              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10108                         board[BOARD_HEIGHT-1-p][1] = 0;
10109         }
10110       }
10111       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10112           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10113         /* [HGM] holdings: Add to holdings, if holdings exist */
10114         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10115                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10116                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10117         }
10118         p = (int) captured;
10119         if (p >= (int) BlackPawn) {
10120           p -= (int)BlackPawn;
10121           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10122                   /* in Shogi restore piece to its original  first */
10123                   captured = (ChessSquare) (DEMOTED captured);
10124                   p = DEMOTED p;
10125           }
10126           p = PieceToNumber((ChessSquare)p);
10127           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10128           board[p][BOARD_WIDTH-2]++;
10129           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10130         } else {
10131           p -= (int)WhitePawn;
10132           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10133                   captured = (ChessSquare) (DEMOTED captured);
10134                   p = DEMOTED p;
10135           }
10136           p = PieceToNumber((ChessSquare)p);
10137           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10138           board[BOARD_HEIGHT-1-p][1]++;
10139           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10140         }
10141       }
10142     } else if (gameInfo.variant == VariantAtomic) {
10143       if (captured != EmptySquare) {
10144         int y, x;
10145         for (y = toY-1; y <= toY+1; y++) {
10146           for (x = toX-1; x <= toX+1; x++) {
10147             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10148                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10149               board[y][x] = EmptySquare;
10150             }
10151           }
10152         }
10153         board[toY][toX] = EmptySquare;
10154       }
10155     }
10156
10157     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10158         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10159     } else
10160     if(promoChar == '+') {
10161         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10162         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10163         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10164           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10165     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10166         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10167         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10168            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10169         board[toY][toX] = newPiece;
10170     }
10171     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10172                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10173         // [HGM] superchess: take promotion piece out of holdings
10174         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10175         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10176             if(!--board[k][BOARD_WIDTH-2])
10177                 board[k][BOARD_WIDTH-1] = EmptySquare;
10178         } else {
10179             if(!--board[BOARD_HEIGHT-1-k][1])
10180                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10181         }
10182     }
10183 }
10184
10185 /* Updates forwardMostMove */
10186 void
10187 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10188 {
10189     int x = toX, y = toY;
10190     char *s = parseList[forwardMostMove];
10191     ChessSquare p = boards[forwardMostMove][toY][toX];
10192 //    forwardMostMove++; // [HGM] bare: moved downstream
10193
10194     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10195     (void) CoordsToAlgebraic(boards[forwardMostMove],
10196                              PosFlags(forwardMostMove),
10197                              fromY, fromX, y, x, promoChar,
10198                              s);
10199     if(killX >= 0 && killY >= 0)
10200         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10201
10202     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10203         int timeLeft; static int lastLoadFlag=0; int king, piece;
10204         piece = boards[forwardMostMove][fromY][fromX];
10205         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10206         if(gameInfo.variant == VariantKnightmate)
10207             king += (int) WhiteUnicorn - (int) WhiteKing;
10208         if(forwardMostMove == 0) {
10209             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10210                 fprintf(serverMoves, "%s;", UserName());
10211             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10212                 fprintf(serverMoves, "%s;", second.tidy);
10213             fprintf(serverMoves, "%s;", first.tidy);
10214             if(gameMode == MachinePlaysWhite)
10215                 fprintf(serverMoves, "%s;", UserName());
10216             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10217                 fprintf(serverMoves, "%s;", second.tidy);
10218         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10219         lastLoadFlag = loadFlag;
10220         // print base move
10221         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10222         // print castling suffix
10223         if( toY == fromY && piece == king ) {
10224             if(toX-fromX > 1)
10225                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10226             if(fromX-toX >1)
10227                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10228         }
10229         // e.p. suffix
10230         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10231              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10232              boards[forwardMostMove][toY][toX] == EmptySquare
10233              && fromX != toX && fromY != toY)
10234                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10235         // promotion suffix
10236         if(promoChar != NULLCHAR) {
10237             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10238                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10239                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10240             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10241         }
10242         if(!loadFlag) {
10243                 char buf[MOVE_LEN*2], *p; int len;
10244             fprintf(serverMoves, "/%d/%d",
10245                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10246             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10247             else                      timeLeft = blackTimeRemaining/1000;
10248             fprintf(serverMoves, "/%d", timeLeft);
10249                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10250                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10251                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10252                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10253             fprintf(serverMoves, "/%s", buf);
10254         }
10255         fflush(serverMoves);
10256     }
10257
10258     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10259         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10260       return;
10261     }
10262     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10263     if (commentList[forwardMostMove+1] != NULL) {
10264         free(commentList[forwardMostMove+1]);
10265         commentList[forwardMostMove+1] = NULL;
10266     }
10267     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10268     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10269     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10270     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10271     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10272     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10273     adjustedClock = FALSE;
10274     gameInfo.result = GameUnfinished;
10275     if (gameInfo.resultDetails != NULL) {
10276         free(gameInfo.resultDetails);
10277         gameInfo.resultDetails = NULL;
10278     }
10279     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10280                               moveList[forwardMostMove - 1]);
10281     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10282       case MT_NONE:
10283       case MT_STALEMATE:
10284       default:
10285         break;
10286       case MT_CHECK:
10287         if(!IS_SHOGI(gameInfo.variant))
10288             strcat(parseList[forwardMostMove - 1], "+");
10289         break;
10290       case MT_CHECKMATE:
10291       case MT_STAINMATE:
10292         strcat(parseList[forwardMostMove - 1], "#");
10293         break;
10294     }
10295 }
10296
10297 /* Updates currentMove if not pausing */
10298 void
10299 ShowMove (int fromX, int fromY, int toX, int toY)
10300 {
10301     int instant = (gameMode == PlayFromGameFile) ?
10302         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10303     if(appData.noGUI) return;
10304     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10305         if (!instant) {
10306             if (forwardMostMove == currentMove + 1) {
10307                 AnimateMove(boards[forwardMostMove - 1],
10308                             fromX, fromY, toX, toY);
10309             }
10310         }
10311         currentMove = forwardMostMove;
10312     }
10313
10314     killX = killY = -1; // [HGM] lion: used up
10315
10316     if (instant) return;
10317
10318     DisplayMove(currentMove - 1);
10319     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10320             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10321                 SetHighlights(fromX, fromY, toX, toY);
10322             }
10323     }
10324     DrawPosition(FALSE, boards[currentMove]);
10325     DisplayBothClocks();
10326     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10327 }
10328
10329 void
10330 SendEgtPath (ChessProgramState *cps)
10331 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10332         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10333
10334         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10335
10336         while(*p) {
10337             char c, *q = name+1, *r, *s;
10338
10339             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10340             while(*p && *p != ',') *q++ = *p++;
10341             *q++ = ':'; *q = 0;
10342             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10343                 strcmp(name, ",nalimov:") == 0 ) {
10344                 // take nalimov path from the menu-changeable option first, if it is defined
10345               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10346                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10347             } else
10348             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10349                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10350                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10351                 s = r = StrStr(s, ":") + 1; // beginning of path info
10352                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10353                 c = *r; *r = 0;             // temporarily null-terminate path info
10354                     *--q = 0;               // strip of trailig ':' from name
10355                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10356                 *r = c;
10357                 SendToProgram(buf,cps);     // send egtbpath command for this format
10358             }
10359             if(*p == ',') p++; // read away comma to position for next format name
10360         }
10361 }
10362
10363 static int
10364 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10365 {
10366       int width = 8, height = 8, holdings = 0;             // most common sizes
10367       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10368       // correct the deviations default for each variant
10369       if( v == VariantXiangqi ) width = 9,  height = 10;
10370       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10371       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10372       if( v == VariantCapablanca || v == VariantCapaRandom ||
10373           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10374                                 width = 10;
10375       if( v == VariantCourier ) width = 12;
10376       if( v == VariantSuper )                            holdings = 8;
10377       if( v == VariantGreat )   width = 10,              holdings = 8;
10378       if( v == VariantSChess )                           holdings = 7;
10379       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10380       if( v == VariantChuChess) width = 10, height = 10;
10381       if( v == VariantChu )     width = 12, height = 12;
10382       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10383              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10384              holdingsSize >= 0 && holdingsSize != holdings;
10385 }
10386
10387 char variantError[MSG_SIZ];
10388
10389 char *
10390 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10391 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10392       char *p, *variant = VariantName(v);
10393       static char b[MSG_SIZ];
10394       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10395            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10396                                                holdingsSize, variant); // cook up sized variant name
10397            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10398            if(StrStr(list, b) == NULL) {
10399                // specific sized variant not known, check if general sizing allowed
10400                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10401                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10402                             boardWidth, boardHeight, holdingsSize, engine);
10403                    return NULL;
10404                }
10405                /* [HGM] here we really should compare with the maximum supported board size */
10406            }
10407       } else snprintf(b, MSG_SIZ,"%s", variant);
10408       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10409       p = StrStr(list, b);
10410       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10411       if(p == NULL) {
10412           // occurs not at all in list, or only as sub-string
10413           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10414           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10415               int l = strlen(variantError);
10416               char *q;
10417               while(p != list && p[-1] != ',') p--;
10418               q = strchr(p, ',');
10419               if(q) *q = NULLCHAR;
10420               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10421               if(q) *q= ',';
10422           }
10423           return NULL;
10424       }
10425       return b;
10426 }
10427
10428 void
10429 InitChessProgram (ChessProgramState *cps, int setup)
10430 /* setup needed to setup FRC opening position */
10431 {
10432     char buf[MSG_SIZ], *b;
10433     if (appData.noChessProgram) return;
10434     hintRequested = FALSE;
10435     bookRequested = FALSE;
10436
10437     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10438     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10439     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10440     if(cps->memSize) { /* [HGM] memory */
10441       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10442         SendToProgram(buf, cps);
10443     }
10444     SendEgtPath(cps); /* [HGM] EGT */
10445     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10446       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10447         SendToProgram(buf, cps);
10448     }
10449
10450     setboardSpoiledMachineBlack = FALSE;
10451     SendToProgram(cps->initString, cps);
10452     if (gameInfo.variant != VariantNormal &&
10453         gameInfo.variant != VariantLoadable
10454         /* [HGM] also send variant if board size non-standard */
10455         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10456
10457       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10458                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10459       if (b == NULL) {
10460         DisplayFatalError(variantError, 0, 1);
10461         return;
10462       }
10463
10464       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10465       SendToProgram(buf, cps);
10466     }
10467     currentlyInitializedVariant = gameInfo.variant;
10468
10469     /* [HGM] send opening position in FRC to first engine */
10470     if(setup) {
10471           SendToProgram("force\n", cps);
10472           SendBoard(cps, 0);
10473           /* engine is now in force mode! Set flag to wake it up after first move. */
10474           setboardSpoiledMachineBlack = 1;
10475     }
10476
10477     if (cps->sendICS) {
10478       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10479       SendToProgram(buf, cps);
10480     }
10481     cps->maybeThinking = FALSE;
10482     cps->offeredDraw = 0;
10483     if (!appData.icsActive) {
10484         SendTimeControl(cps, movesPerSession, timeControl,
10485                         timeIncrement, appData.searchDepth,
10486                         searchTime);
10487     }
10488     if (appData.showThinking
10489         // [HGM] thinking: four options require thinking output to be sent
10490         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10491                                 ) {
10492         SendToProgram("post\n", cps);
10493     }
10494     SendToProgram("hard\n", cps);
10495     if (!appData.ponderNextMove) {
10496         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10497            it without being sure what state we are in first.  "hard"
10498            is not a toggle, so that one is OK.
10499          */
10500         SendToProgram("easy\n", cps);
10501     }
10502     if (cps->usePing) {
10503       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10504       SendToProgram(buf, cps);
10505     }
10506     cps->initDone = TRUE;
10507     ClearEngineOutputPane(cps == &second);
10508 }
10509
10510
10511 void
10512 ResendOptions (ChessProgramState *cps)
10513 { // send the stored value of the options
10514   int i;
10515   char buf[MSG_SIZ];
10516   Option *opt = cps->option;
10517   for(i=0; i<cps->nrOptions; i++, opt++) {
10518       switch(opt->type) {
10519         case Spin:
10520         case Slider:
10521         case CheckBox:
10522             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10523           break;
10524         case ComboBox:
10525           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10526           break;
10527         default:
10528             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10529           break;
10530         case Button:
10531         case SaveButton:
10532           continue;
10533       }
10534       SendToProgram(buf, cps);
10535   }
10536 }
10537
10538 void
10539 StartChessProgram (ChessProgramState *cps)
10540 {
10541     char buf[MSG_SIZ];
10542     int err;
10543
10544     if (appData.noChessProgram) return;
10545     cps->initDone = FALSE;
10546
10547     if (strcmp(cps->host, "localhost") == 0) {
10548         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10549     } else if (*appData.remoteShell == NULLCHAR) {
10550         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10551     } else {
10552         if (*appData.remoteUser == NULLCHAR) {
10553           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10554                     cps->program);
10555         } else {
10556           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10557                     cps->host, appData.remoteUser, cps->program);
10558         }
10559         err = StartChildProcess(buf, "", &cps->pr);
10560     }
10561
10562     if (err != 0) {
10563       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10564         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10565         if(cps != &first) return;
10566         appData.noChessProgram = TRUE;
10567         ThawUI();
10568         SetNCPMode();
10569 //      DisplayFatalError(buf, err, 1);
10570 //      cps->pr = NoProc;
10571 //      cps->isr = NULL;
10572         return;
10573     }
10574
10575     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10576     if (cps->protocolVersion > 1) {
10577       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10578       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10579         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10580         cps->comboCnt = 0;  //                and values of combo boxes
10581       }
10582       SendToProgram(buf, cps);
10583       if(cps->reload) ResendOptions(cps);
10584     } else {
10585       SendToProgram("xboard\n", cps);
10586     }
10587 }
10588
10589 void
10590 TwoMachinesEventIfReady P((void))
10591 {
10592   static int curMess = 0;
10593   if (first.lastPing != first.lastPong) {
10594     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10595     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10596     return;
10597   }
10598   if (second.lastPing != second.lastPong) {
10599     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10600     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10601     return;
10602   }
10603   DisplayMessage("", ""); curMess = 0;
10604   TwoMachinesEvent();
10605 }
10606
10607 char *
10608 MakeName (char *template)
10609 {
10610     time_t clock;
10611     struct tm *tm;
10612     static char buf[MSG_SIZ];
10613     char *p = buf;
10614     int i;
10615
10616     clock = time((time_t *)NULL);
10617     tm = localtime(&clock);
10618
10619     while(*p++ = *template++) if(p[-1] == '%') {
10620         switch(*template++) {
10621           case 0:   *p = 0; return buf;
10622           case 'Y': i = tm->tm_year+1900; break;
10623           case 'y': i = tm->tm_year-100; break;
10624           case 'M': i = tm->tm_mon+1; break;
10625           case 'd': i = tm->tm_mday; break;
10626           case 'h': i = tm->tm_hour; break;
10627           case 'm': i = tm->tm_min; break;
10628           case 's': i = tm->tm_sec; break;
10629           default:  i = 0;
10630         }
10631         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10632     }
10633     return buf;
10634 }
10635
10636 int
10637 CountPlayers (char *p)
10638 {
10639     int n = 0;
10640     while(p = strchr(p, '\n')) p++, n++; // count participants
10641     return n;
10642 }
10643
10644 FILE *
10645 WriteTourneyFile (char *results, FILE *f)
10646 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10647     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10648     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10649         // create a file with tournament description
10650         fprintf(f, "-participants {%s}\n", appData.participants);
10651         fprintf(f, "-seedBase %d\n", appData.seedBase);
10652         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10653         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10654         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10655         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10656         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10657         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10658         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10659         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10660         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10661         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10662         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10663         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10664         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10665         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10666         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10667         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10668         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10669         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10670         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10671         fprintf(f, "-smpCores %d\n", appData.smpCores);
10672         if(searchTime > 0)
10673                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10674         else {
10675                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10676                 fprintf(f, "-tc %s\n", appData.timeControl);
10677                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10678         }
10679         fprintf(f, "-results \"%s\"\n", results);
10680     }
10681     return f;
10682 }
10683
10684 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10685
10686 void
10687 Substitute (char *participants, int expunge)
10688 {
10689     int i, changed, changes=0, nPlayers=0;
10690     char *p, *q, *r, buf[MSG_SIZ];
10691     if(participants == NULL) return;
10692     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10693     r = p = participants; q = appData.participants;
10694     while(*p && *p == *q) {
10695         if(*p == '\n') r = p+1, nPlayers++;
10696         p++; q++;
10697     }
10698     if(*p) { // difference
10699         while(*p && *p++ != '\n');
10700         while(*q && *q++ != '\n');
10701       changed = nPlayers;
10702         changes = 1 + (strcmp(p, q) != 0);
10703     }
10704     if(changes == 1) { // a single engine mnemonic was changed
10705         q = r; while(*q) nPlayers += (*q++ == '\n');
10706         p = buf; while(*r && (*p = *r++) != '\n') p++;
10707         *p = NULLCHAR;
10708         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10709         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10710         if(mnemonic[i]) { // The substitute is valid
10711             FILE *f;
10712             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10713                 flock(fileno(f), LOCK_EX);
10714                 ParseArgsFromFile(f);
10715                 fseek(f, 0, SEEK_SET);
10716                 FREE(appData.participants); appData.participants = participants;
10717                 if(expunge) { // erase results of replaced engine
10718                     int len = strlen(appData.results), w, b, dummy;
10719                     for(i=0; i<len; i++) {
10720                         Pairing(i, nPlayers, &w, &b, &dummy);
10721                         if((w == changed || b == changed) && appData.results[i] == '*') {
10722                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10723                             fclose(f);
10724                             return;
10725                         }
10726                     }
10727                     for(i=0; i<len; i++) {
10728                         Pairing(i, nPlayers, &w, &b, &dummy);
10729                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10730                     }
10731                 }
10732                 WriteTourneyFile(appData.results, f);
10733                 fclose(f); // release lock
10734                 return;
10735             }
10736         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10737     }
10738     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10739     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10740     free(participants);
10741     return;
10742 }
10743
10744 int
10745 CheckPlayers (char *participants)
10746 {
10747         int i;
10748         char buf[MSG_SIZ], *p;
10749         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10750         while(p = strchr(participants, '\n')) {
10751             *p = NULLCHAR;
10752             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10753             if(!mnemonic[i]) {
10754                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10755                 *p = '\n';
10756                 DisplayError(buf, 0);
10757                 return 1;
10758             }
10759             *p = '\n';
10760             participants = p + 1;
10761         }
10762         return 0;
10763 }
10764
10765 int
10766 CreateTourney (char *name)
10767 {
10768         FILE *f;
10769         if(matchMode && strcmp(name, appData.tourneyFile)) {
10770              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10771         }
10772         if(name[0] == NULLCHAR) {
10773             if(appData.participants[0])
10774                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10775             return 0;
10776         }
10777         f = fopen(name, "r");
10778         if(f) { // file exists
10779             ASSIGN(appData.tourneyFile, name);
10780             ParseArgsFromFile(f); // parse it
10781         } else {
10782             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10783             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10784                 DisplayError(_("Not enough participants"), 0);
10785                 return 0;
10786             }
10787             if(CheckPlayers(appData.participants)) return 0;
10788             ASSIGN(appData.tourneyFile, name);
10789             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10790             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10791         }
10792         fclose(f);
10793         appData.noChessProgram = FALSE;
10794         appData.clockMode = TRUE;
10795         SetGNUMode();
10796         return 1;
10797 }
10798
10799 int
10800 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10801 {
10802     char buf[MSG_SIZ], *p, *q;
10803     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10804     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10805     skip = !all && group[0]; // if group requested, we start in skip mode
10806     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10807         p = names; q = buf; header = 0;
10808         while(*p && *p != '\n') *q++ = *p++;
10809         *q = 0;
10810         if(*p == '\n') p++;
10811         if(buf[0] == '#') {
10812             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10813             depth++; // we must be entering a new group
10814             if(all) continue; // suppress printing group headers when complete list requested
10815             header = 1;
10816             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10817         }
10818         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10819         if(engineList[i]) free(engineList[i]);
10820         engineList[i] = strdup(buf);
10821         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10822         if(engineMnemonic[i]) free(engineMnemonic[i]);
10823         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10824             strcat(buf, " (");
10825             sscanf(q + 8, "%s", buf + strlen(buf));
10826             strcat(buf, ")");
10827         }
10828         engineMnemonic[i] = strdup(buf);
10829         i++;
10830     }
10831     engineList[i] = engineMnemonic[i] = NULL;
10832     return i;
10833 }
10834
10835 // following implemented as macro to avoid type limitations
10836 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10837
10838 void
10839 SwapEngines (int n)
10840 {   // swap settings for first engine and other engine (so far only some selected options)
10841     int h;
10842     char *p;
10843     if(n == 0) return;
10844     SWAP(directory, p)
10845     SWAP(chessProgram, p)
10846     SWAP(isUCI, h)
10847     SWAP(hasOwnBookUCI, h)
10848     SWAP(protocolVersion, h)
10849     SWAP(reuse, h)
10850     SWAP(scoreIsAbsolute, h)
10851     SWAP(timeOdds, h)
10852     SWAP(logo, p)
10853     SWAP(pgnName, p)
10854     SWAP(pvSAN, h)
10855     SWAP(engOptions, p)
10856     SWAP(engInitString, p)
10857     SWAP(computerString, p)
10858     SWAP(features, p)
10859     SWAP(fenOverride, p)
10860     SWAP(NPS, h)
10861     SWAP(accumulateTC, h)
10862     SWAP(drawDepth, h)
10863     SWAP(host, p)
10864     SWAP(pseudo, h)
10865 }
10866
10867 int
10868 GetEngineLine (char *s, int n)
10869 {
10870     int i;
10871     char buf[MSG_SIZ];
10872     extern char *icsNames;
10873     if(!s || !*s) return 0;
10874     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10875     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10876     if(!mnemonic[i]) return 0;
10877     if(n == 11) return 1; // just testing if there was a match
10878     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10879     if(n == 1) SwapEngines(n);
10880     ParseArgsFromString(buf);
10881     if(n == 1) SwapEngines(n);
10882     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10883         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10884         ParseArgsFromString(buf);
10885     }
10886     return 1;
10887 }
10888
10889 int
10890 SetPlayer (int player, char *p)
10891 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10892     int i;
10893     char buf[MSG_SIZ], *engineName;
10894     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10895     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10896     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10897     if(mnemonic[i]) {
10898         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10899         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10900         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10901         ParseArgsFromString(buf);
10902     } else { // no engine with this nickname is installed!
10903         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10904         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10905         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10906         ModeHighlight();
10907         DisplayError(buf, 0);
10908         return 0;
10909     }
10910     free(engineName);
10911     return i;
10912 }
10913
10914 char *recentEngines;
10915
10916 void
10917 RecentEngineEvent (int nr)
10918 {
10919     int n;
10920 //    SwapEngines(1); // bump first to second
10921 //    ReplaceEngine(&second, 1); // and load it there
10922     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10923     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10924     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10925         ReplaceEngine(&first, 0);
10926         FloatToFront(&appData.recentEngineList, command[n]);
10927     }
10928 }
10929
10930 int
10931 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10932 {   // determine players from game number
10933     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10934
10935     if(appData.tourneyType == 0) {
10936         roundsPerCycle = (nPlayers - 1) | 1;
10937         pairingsPerRound = nPlayers / 2;
10938     } else if(appData.tourneyType > 0) {
10939         roundsPerCycle = nPlayers - appData.tourneyType;
10940         pairingsPerRound = appData.tourneyType;
10941     }
10942     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10943     gamesPerCycle = gamesPerRound * roundsPerCycle;
10944     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10945     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10946     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10947     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10948     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10949     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10950
10951     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10952     if(appData.roundSync) *syncInterval = gamesPerRound;
10953
10954     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10955
10956     if(appData.tourneyType == 0) {
10957         if(curPairing == (nPlayers-1)/2 ) {
10958             *whitePlayer = curRound;
10959             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10960         } else {
10961             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10962             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10963             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10964             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10965         }
10966     } else if(appData.tourneyType > 1) {
10967         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10968         *whitePlayer = curRound + appData.tourneyType;
10969     } else if(appData.tourneyType > 0) {
10970         *whitePlayer = curPairing;
10971         *blackPlayer = curRound + appData.tourneyType;
10972     }
10973
10974     // take care of white/black alternation per round.
10975     // For cycles and games this is already taken care of by default, derived from matchGame!
10976     return curRound & 1;
10977 }
10978
10979 int
10980 NextTourneyGame (int nr, int *swapColors)
10981 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10982     char *p, *q;
10983     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10984     FILE *tf;
10985     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10986     tf = fopen(appData.tourneyFile, "r");
10987     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10988     ParseArgsFromFile(tf); fclose(tf);
10989     InitTimeControls(); // TC might be altered from tourney file
10990
10991     nPlayers = CountPlayers(appData.participants); // count participants
10992     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10993     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10994
10995     if(syncInterval) {
10996         p = q = appData.results;
10997         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10998         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10999             DisplayMessage(_("Waiting for other game(s)"),"");
11000             waitingForGame = TRUE;
11001             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11002             return 0;
11003         }
11004         waitingForGame = FALSE;
11005     }
11006
11007     if(appData.tourneyType < 0) {
11008         if(nr>=0 && !pairingReceived) {
11009             char buf[1<<16];
11010             if(pairing.pr == NoProc) {
11011                 if(!appData.pairingEngine[0]) {
11012                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11013                     return 0;
11014                 }
11015                 StartChessProgram(&pairing); // starts the pairing engine
11016             }
11017             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11018             SendToProgram(buf, &pairing);
11019             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11020             SendToProgram(buf, &pairing);
11021             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11022         }
11023         pairingReceived = 0;                              // ... so we continue here
11024         *swapColors = 0;
11025         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11026         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11027         matchGame = 1; roundNr = nr / syncInterval + 1;
11028     }
11029
11030     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11031
11032     // redefine engines, engine dir, etc.
11033     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11034     if(first.pr == NoProc) {
11035       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11036       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11037     }
11038     if(second.pr == NoProc) {
11039       SwapEngines(1);
11040       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11041       SwapEngines(1);         // and make that valid for second engine by swapping
11042       InitEngine(&second, 1);
11043     }
11044     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11045     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11046     return OK;
11047 }
11048
11049 void
11050 NextMatchGame ()
11051 {   // performs game initialization that does not invoke engines, and then tries to start the game
11052     int res, firstWhite, swapColors = 0;
11053     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11054     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
11055         char buf[MSG_SIZ];
11056         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11057         if(strcmp(buf, currentDebugFile)) { // name has changed
11058             FILE *f = fopen(buf, "w");
11059             if(f) { // if opening the new file failed, just keep using the old one
11060                 ASSIGN(currentDebugFile, buf);
11061                 fclose(debugFP);
11062                 debugFP = f;
11063             }
11064             if(appData.serverFileName) {
11065                 if(serverFP) fclose(serverFP);
11066                 serverFP = fopen(appData.serverFileName, "w");
11067                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11068                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11069             }
11070         }
11071     }
11072     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11073     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11074     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11075     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11076     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11077     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11078     Reset(FALSE, first.pr != NoProc);
11079     res = LoadGameOrPosition(matchGame); // setup game
11080     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11081     if(!res) return; // abort when bad game/pos file
11082     TwoMachinesEvent();
11083 }
11084
11085 void
11086 UserAdjudicationEvent (int result)
11087 {
11088     ChessMove gameResult = GameIsDrawn;
11089
11090     if( result > 0 ) {
11091         gameResult = WhiteWins;
11092     }
11093     else if( result < 0 ) {
11094         gameResult = BlackWins;
11095     }
11096
11097     if( gameMode == TwoMachinesPlay ) {
11098         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11099     }
11100 }
11101
11102
11103 // [HGM] save: calculate checksum of game to make games easily identifiable
11104 int
11105 StringCheckSum (char *s)
11106 {
11107         int i = 0;
11108         if(s==NULL) return 0;
11109         while(*s) i = i*259 + *s++;
11110         return i;
11111 }
11112
11113 int
11114 GameCheckSum ()
11115 {
11116         int i, sum=0;
11117         for(i=backwardMostMove; i<forwardMostMove; i++) {
11118                 sum += pvInfoList[i].depth;
11119                 sum += StringCheckSum(parseList[i]);
11120                 sum += StringCheckSum(commentList[i]);
11121                 sum *= 261;
11122         }
11123         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11124         return sum + StringCheckSum(commentList[i]);
11125 } // end of save patch
11126
11127 void
11128 GameEnds (ChessMove result, char *resultDetails, int whosays)
11129 {
11130     GameMode nextGameMode;
11131     int isIcsGame;
11132     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11133
11134     if(endingGame) return; /* [HGM] crash: forbid recursion */
11135     endingGame = 1;
11136     if(twoBoards) { // [HGM] dual: switch back to one board
11137         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11138         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11139     }
11140     if (appData.debugMode) {
11141       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11142               result, resultDetails ? resultDetails : "(null)", whosays);
11143     }
11144
11145     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11146
11147     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11148
11149     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11150         /* If we are playing on ICS, the server decides when the
11151            game is over, but the engine can offer to draw, claim
11152            a draw, or resign.
11153          */
11154 #if ZIPPY
11155         if (appData.zippyPlay && first.initDone) {
11156             if (result == GameIsDrawn) {
11157                 /* In case draw still needs to be claimed */
11158                 SendToICS(ics_prefix);
11159                 SendToICS("draw\n");
11160             } else if (StrCaseStr(resultDetails, "resign")) {
11161                 SendToICS(ics_prefix);
11162                 SendToICS("resign\n");
11163             }
11164         }
11165 #endif
11166         endingGame = 0; /* [HGM] crash */
11167         return;
11168     }
11169
11170     /* If we're loading the game from a file, stop */
11171     if (whosays == GE_FILE) {
11172       (void) StopLoadGameTimer();
11173       gameFileFP = NULL;
11174     }
11175
11176     /* Cancel draw offers */
11177     first.offeredDraw = second.offeredDraw = 0;
11178
11179     /* If this is an ICS game, only ICS can really say it's done;
11180        if not, anyone can. */
11181     isIcsGame = (gameMode == IcsPlayingWhite ||
11182                  gameMode == IcsPlayingBlack ||
11183                  gameMode == IcsObserving    ||
11184                  gameMode == IcsExamining);
11185
11186     if (!isIcsGame || whosays == GE_ICS) {
11187         /* OK -- not an ICS game, or ICS said it was done */
11188         StopClocks();
11189         if (!isIcsGame && !appData.noChessProgram)
11190           SetUserThinkingEnables();
11191
11192         /* [HGM] if a machine claims the game end we verify this claim */
11193         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11194             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11195                 char claimer;
11196                 ChessMove trueResult = (ChessMove) -1;
11197
11198                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11199                                             first.twoMachinesColor[0] :
11200                                             second.twoMachinesColor[0] ;
11201
11202                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11204                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11205                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11206                 } else
11207                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11208                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11209                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11210                 } else
11211                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11212                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11213                 }
11214
11215                 // now verify win claims, but not in drop games, as we don't understand those yet
11216                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11217                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11218                     (result == WhiteWins && claimer == 'w' ||
11219                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11220                       if (appData.debugMode) {
11221                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11222                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11223                       }
11224                       if(result != trueResult) {
11225                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11226                               result = claimer == 'w' ? BlackWins : WhiteWins;
11227                               resultDetails = buf;
11228                       }
11229                 } else
11230                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11231                     && (forwardMostMove <= backwardMostMove ||
11232                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11233                         (claimer=='b')==(forwardMostMove&1))
11234                                                                                   ) {
11235                       /* [HGM] verify: draws that were not flagged are false claims */
11236                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11237                       result = claimer == 'w' ? BlackWins : WhiteWins;
11238                       resultDetails = buf;
11239                 }
11240                 /* (Claiming a loss is accepted no questions asked!) */
11241             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11242                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11243                 result = GameUnfinished;
11244                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11245             }
11246             /* [HGM] bare: don't allow bare King to win */
11247             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11248                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11249                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11250                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11251                && result != GameIsDrawn)
11252             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11253                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11254                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11255                         if(p >= 0 && p <= (int)WhiteKing) k++;
11256                 }
11257                 if (appData.debugMode) {
11258                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11259                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11260                 }
11261                 if(k <= 1) {
11262                         result = GameIsDrawn;
11263                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11264                         resultDetails = buf;
11265                 }
11266             }
11267         }
11268
11269
11270         if(serverMoves != NULL && !loadFlag) { char c = '=';
11271             if(result==WhiteWins) c = '+';
11272             if(result==BlackWins) c = '-';
11273             if(resultDetails != NULL)
11274                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11275         }
11276         if (resultDetails != NULL) {
11277             gameInfo.result = result;
11278             gameInfo.resultDetails = StrSave(resultDetails);
11279
11280             /* display last move only if game was not loaded from file */
11281             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11282                 DisplayMove(currentMove - 1);
11283
11284             if (forwardMostMove != 0) {
11285                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11286                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11287                                                                 ) {
11288                     if (*appData.saveGameFile != NULLCHAR) {
11289                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11290                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11291                         else
11292                         SaveGameToFile(appData.saveGameFile, TRUE);
11293                     } else if (appData.autoSaveGames) {
11294                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11295                     }
11296                     if (*appData.savePositionFile != NULLCHAR) {
11297                         SavePositionToFile(appData.savePositionFile);
11298                     }
11299                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11300                 }
11301             }
11302
11303             /* Tell program how game ended in case it is learning */
11304             /* [HGM] Moved this to after saving the PGN, just in case */
11305             /* engine died and we got here through time loss. In that */
11306             /* case we will get a fatal error writing the pipe, which */
11307             /* would otherwise lose us the PGN.                       */
11308             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11309             /* output during GameEnds should never be fatal anymore   */
11310             if (gameMode == MachinePlaysWhite ||
11311                 gameMode == MachinePlaysBlack ||
11312                 gameMode == TwoMachinesPlay ||
11313                 gameMode == IcsPlayingWhite ||
11314                 gameMode == IcsPlayingBlack ||
11315                 gameMode == BeginningOfGame) {
11316                 char buf[MSG_SIZ];
11317                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11318                         resultDetails);
11319                 if (first.pr != NoProc) {
11320                     SendToProgram(buf, &first);
11321                 }
11322                 if (second.pr != NoProc &&
11323                     gameMode == TwoMachinesPlay) {
11324                     SendToProgram(buf, &second);
11325                 }
11326             }
11327         }
11328
11329         if (appData.icsActive) {
11330             if (appData.quietPlay &&
11331                 (gameMode == IcsPlayingWhite ||
11332                  gameMode == IcsPlayingBlack)) {
11333                 SendToICS(ics_prefix);
11334                 SendToICS("set shout 1\n");
11335             }
11336             nextGameMode = IcsIdle;
11337             ics_user_moved = FALSE;
11338             /* clean up premove.  It's ugly when the game has ended and the
11339              * premove highlights are still on the board.
11340              */
11341             if (gotPremove) {
11342               gotPremove = FALSE;
11343               ClearPremoveHighlights();
11344               DrawPosition(FALSE, boards[currentMove]);
11345             }
11346             if (whosays == GE_ICS) {
11347                 switch (result) {
11348                 case WhiteWins:
11349                     if (gameMode == IcsPlayingWhite)
11350                         PlayIcsWinSound();
11351                     else if(gameMode == IcsPlayingBlack)
11352                         PlayIcsLossSound();
11353                     break;
11354                 case BlackWins:
11355                     if (gameMode == IcsPlayingBlack)
11356                         PlayIcsWinSound();
11357                     else if(gameMode == IcsPlayingWhite)
11358                         PlayIcsLossSound();
11359                     break;
11360                 case GameIsDrawn:
11361                     PlayIcsDrawSound();
11362                     break;
11363                 default:
11364                     PlayIcsUnfinishedSound();
11365                 }
11366             }
11367             if(appData.quitNext) { ExitEvent(0); return; }
11368         } else if (gameMode == EditGame ||
11369                    gameMode == PlayFromGameFile ||
11370                    gameMode == AnalyzeMode ||
11371                    gameMode == AnalyzeFile) {
11372             nextGameMode = gameMode;
11373         } else {
11374             nextGameMode = EndOfGame;
11375         }
11376         pausing = FALSE;
11377         ModeHighlight();
11378     } else {
11379         nextGameMode = gameMode;
11380     }
11381
11382     if (appData.noChessProgram) {
11383         gameMode = nextGameMode;
11384         ModeHighlight();
11385         endingGame = 0; /* [HGM] crash */
11386         return;
11387     }
11388
11389     if (first.reuse) {
11390         /* Put first chess program into idle state */
11391         if (first.pr != NoProc &&
11392             (gameMode == MachinePlaysWhite ||
11393              gameMode == MachinePlaysBlack ||
11394              gameMode == TwoMachinesPlay ||
11395              gameMode == IcsPlayingWhite ||
11396              gameMode == IcsPlayingBlack ||
11397              gameMode == BeginningOfGame)) {
11398             SendToProgram("force\n", &first);
11399             if (first.usePing) {
11400               char buf[MSG_SIZ];
11401               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11402               SendToProgram(buf, &first);
11403             }
11404         }
11405     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11406         /* Kill off first chess program */
11407         if (first.isr != NULL)
11408           RemoveInputSource(first.isr);
11409         first.isr = NULL;
11410
11411         if (first.pr != NoProc) {
11412             ExitAnalyzeMode();
11413             DoSleep( appData.delayBeforeQuit );
11414             SendToProgram("quit\n", &first);
11415             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11416             first.reload = TRUE;
11417         }
11418         first.pr = NoProc;
11419     }
11420     if (second.reuse) {
11421         /* Put second chess program into idle state */
11422         if (second.pr != NoProc &&
11423             gameMode == TwoMachinesPlay) {
11424             SendToProgram("force\n", &second);
11425             if (second.usePing) {
11426               char buf[MSG_SIZ];
11427               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11428               SendToProgram(buf, &second);
11429             }
11430         }
11431     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11432         /* Kill off second chess program */
11433         if (second.isr != NULL)
11434           RemoveInputSource(second.isr);
11435         second.isr = NULL;
11436
11437         if (second.pr != NoProc) {
11438             DoSleep( appData.delayBeforeQuit );
11439             SendToProgram("quit\n", &second);
11440             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11441             second.reload = TRUE;
11442         }
11443         second.pr = NoProc;
11444     }
11445
11446     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11447         char resChar = '=';
11448         switch (result) {
11449         case WhiteWins:
11450           resChar = '+';
11451           if (first.twoMachinesColor[0] == 'w') {
11452             first.matchWins++;
11453           } else {
11454             second.matchWins++;
11455           }
11456           break;
11457         case BlackWins:
11458           resChar = '-';
11459           if (first.twoMachinesColor[0] == 'b') {
11460             first.matchWins++;
11461           } else {
11462             second.matchWins++;
11463           }
11464           break;
11465         case GameUnfinished:
11466           resChar = ' ';
11467         default:
11468           break;
11469         }
11470
11471         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11472         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11473             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11474             ReserveGame(nextGame, resChar); // sets nextGame
11475             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11476             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11477         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11478
11479         if (nextGame <= appData.matchGames && !abortMatch) {
11480             gameMode = nextGameMode;
11481             matchGame = nextGame; // this will be overruled in tourney mode!
11482             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11483             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11484             endingGame = 0; /* [HGM] crash */
11485             return;
11486         } else {
11487             gameMode = nextGameMode;
11488             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11489                      first.tidy, second.tidy,
11490                      first.matchWins, second.matchWins,
11491                      appData.matchGames - (first.matchWins + second.matchWins));
11492             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11493             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11494             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11495             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11496                 first.twoMachinesColor = "black\n";
11497                 second.twoMachinesColor = "white\n";
11498             } else {
11499                 first.twoMachinesColor = "white\n";
11500                 second.twoMachinesColor = "black\n";
11501             }
11502         }
11503     }
11504     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11505         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11506       ExitAnalyzeMode();
11507     gameMode = nextGameMode;
11508     ModeHighlight();
11509     endingGame = 0;  /* [HGM] crash */
11510     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11511         if(matchMode == TRUE) { // match through command line: exit with or without popup
11512             if(ranking) {
11513                 ToNrEvent(forwardMostMove);
11514                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11515                 else ExitEvent(0);
11516             } else DisplayFatalError(buf, 0, 0);
11517         } else { // match through menu; just stop, with or without popup
11518             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11519             ModeHighlight();
11520             if(ranking){
11521                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11522             } else DisplayNote(buf);
11523       }
11524       if(ranking) free(ranking);
11525     }
11526 }
11527
11528 /* Assumes program was just initialized (initString sent).
11529    Leaves program in force mode. */
11530 void
11531 FeedMovesToProgram (ChessProgramState *cps, int upto)
11532 {
11533     int i;
11534
11535     if (appData.debugMode)
11536       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11537               startedFromSetupPosition ? "position and " : "",
11538               backwardMostMove, upto, cps->which);
11539     if(currentlyInitializedVariant != gameInfo.variant) {
11540       char buf[MSG_SIZ];
11541         // [HGM] variantswitch: make engine aware of new variant
11542         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11543                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11544                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11545         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11546         SendToProgram(buf, cps);
11547         currentlyInitializedVariant = gameInfo.variant;
11548     }
11549     SendToProgram("force\n", cps);
11550     if (startedFromSetupPosition) {
11551         SendBoard(cps, backwardMostMove);
11552     if (appData.debugMode) {
11553         fprintf(debugFP, "feedMoves\n");
11554     }
11555     }
11556     for (i = backwardMostMove; i < upto; i++) {
11557         SendMoveToProgram(i, cps);
11558     }
11559 }
11560
11561
11562 int
11563 ResurrectChessProgram ()
11564 {
11565      /* The chess program may have exited.
11566         If so, restart it and feed it all the moves made so far. */
11567     static int doInit = 0;
11568
11569     if (appData.noChessProgram) return 1;
11570
11571     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11572         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11573         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11574         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11575     } else {
11576         if (first.pr != NoProc) return 1;
11577         StartChessProgram(&first);
11578     }
11579     InitChessProgram(&first, FALSE);
11580     FeedMovesToProgram(&first, currentMove);
11581
11582     if (!first.sendTime) {
11583         /* can't tell gnuchess what its clock should read,
11584            so we bow to its notion. */
11585         ResetClocks();
11586         timeRemaining[0][currentMove] = whiteTimeRemaining;
11587         timeRemaining[1][currentMove] = blackTimeRemaining;
11588     }
11589
11590     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11591                 appData.icsEngineAnalyze) && first.analysisSupport) {
11592       SendToProgram("analyze\n", &first);
11593       first.analyzing = TRUE;
11594     }
11595     return 1;
11596 }
11597
11598 /*
11599  * Button procedures
11600  */
11601 void
11602 Reset (int redraw, int init)
11603 {
11604     int i;
11605
11606     if (appData.debugMode) {
11607         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11608                 redraw, init, gameMode);
11609     }
11610     CleanupTail(); // [HGM] vari: delete any stored variations
11611     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11612     pausing = pauseExamInvalid = FALSE;
11613     startedFromSetupPosition = blackPlaysFirst = FALSE;
11614     firstMove = TRUE;
11615     whiteFlag = blackFlag = FALSE;
11616     userOfferedDraw = FALSE;
11617     hintRequested = bookRequested = FALSE;
11618     first.maybeThinking = FALSE;
11619     second.maybeThinking = FALSE;
11620     first.bookSuspend = FALSE; // [HGM] book
11621     second.bookSuspend = FALSE;
11622     thinkOutput[0] = NULLCHAR;
11623     lastHint[0] = NULLCHAR;
11624     ClearGameInfo(&gameInfo);
11625     gameInfo.variant = StringToVariant(appData.variant);
11626     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11627     ics_user_moved = ics_clock_paused = FALSE;
11628     ics_getting_history = H_FALSE;
11629     ics_gamenum = -1;
11630     white_holding[0] = black_holding[0] = NULLCHAR;
11631     ClearProgramStats();
11632     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11633
11634     ResetFrontEnd();
11635     ClearHighlights();
11636     flipView = appData.flipView;
11637     ClearPremoveHighlights();
11638     gotPremove = FALSE;
11639     alarmSounded = FALSE;
11640     killX = killY = -1; // [HGM] lion
11641
11642     GameEnds(EndOfFile, NULL, GE_PLAYER);
11643     if(appData.serverMovesName != NULL) {
11644         /* [HGM] prepare to make moves file for broadcasting */
11645         clock_t t = clock();
11646         if(serverMoves != NULL) fclose(serverMoves);
11647         serverMoves = fopen(appData.serverMovesName, "r");
11648         if(serverMoves != NULL) {
11649             fclose(serverMoves);
11650             /* delay 15 sec before overwriting, so all clients can see end */
11651             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11652         }
11653         serverMoves = fopen(appData.serverMovesName, "w");
11654     }
11655
11656     ExitAnalyzeMode();
11657     gameMode = BeginningOfGame;
11658     ModeHighlight();
11659     if(appData.icsActive) gameInfo.variant = VariantNormal;
11660     currentMove = forwardMostMove = backwardMostMove = 0;
11661     MarkTargetSquares(1);
11662     InitPosition(redraw);
11663     for (i = 0; i < MAX_MOVES; i++) {
11664         if (commentList[i] != NULL) {
11665             free(commentList[i]);
11666             commentList[i] = NULL;
11667         }
11668     }
11669     ResetClocks();
11670     timeRemaining[0][0] = whiteTimeRemaining;
11671     timeRemaining[1][0] = blackTimeRemaining;
11672
11673     if (first.pr == NoProc) {
11674         StartChessProgram(&first);
11675     }
11676     if (init) {
11677             InitChessProgram(&first, startedFromSetupPosition);
11678     }
11679     DisplayTitle("");
11680     DisplayMessage("", "");
11681     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11682     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11683     ClearMap();        // [HGM] exclude: invalidate map
11684 }
11685
11686 void
11687 AutoPlayGameLoop ()
11688 {
11689     for (;;) {
11690         if (!AutoPlayOneMove())
11691           return;
11692         if (matchMode || appData.timeDelay == 0)
11693           continue;
11694         if (appData.timeDelay < 0)
11695           return;
11696         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11697         break;
11698     }
11699 }
11700
11701 void
11702 AnalyzeNextGame()
11703 {
11704     ReloadGame(1); // next game
11705 }
11706
11707 int
11708 AutoPlayOneMove ()
11709 {
11710     int fromX, fromY, toX, toY;
11711
11712     if (appData.debugMode) {
11713       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11714     }
11715
11716     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11717       return FALSE;
11718
11719     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11720       pvInfoList[currentMove].depth = programStats.depth;
11721       pvInfoList[currentMove].score = programStats.score;
11722       pvInfoList[currentMove].time  = 0;
11723       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11724       else { // append analysis of final position as comment
11725         char buf[MSG_SIZ];
11726         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11727         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11728       }
11729       programStats.depth = 0;
11730     }
11731
11732     if (currentMove >= forwardMostMove) {
11733       if(gameMode == AnalyzeFile) {
11734           if(appData.loadGameIndex == -1) {
11735             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11736           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11737           } else {
11738           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11739         }
11740       }
11741 //      gameMode = EndOfGame;
11742 //      ModeHighlight();
11743
11744       /* [AS] Clear current move marker at the end of a game */
11745       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11746
11747       return FALSE;
11748     }
11749
11750     toX = moveList[currentMove][2] - AAA;
11751     toY = moveList[currentMove][3] - ONE;
11752
11753     if (moveList[currentMove][1] == '@') {
11754         if (appData.highlightLastMove) {
11755             SetHighlights(-1, -1, toX, toY);
11756         }
11757     } else {
11758         int viaX = moveList[currentMove][5] - AAA;
11759         int viaY = moveList[currentMove][6] - ONE;
11760         fromX = moveList[currentMove][0] - AAA;
11761         fromY = moveList[currentMove][1] - ONE;
11762
11763         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11764
11765         if(moveList[currentMove][4] == ';') { // multi-leg
11766             ChessSquare piece = boards[currentMove][viaY][viaX];
11767             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11768             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11769             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11770             boards[currentMove][viaY][viaX] = piece;
11771         } else
11772         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11773
11774         if (appData.highlightLastMove) {
11775             SetHighlights(fromX, fromY, toX, toY);
11776         }
11777     }
11778     DisplayMove(currentMove);
11779     SendMoveToProgram(currentMove++, &first);
11780     DisplayBothClocks();
11781     DrawPosition(FALSE, boards[currentMove]);
11782     // [HGM] PV info: always display, routine tests if empty
11783     DisplayComment(currentMove - 1, commentList[currentMove]);
11784     return TRUE;
11785 }
11786
11787
11788 int
11789 LoadGameOneMove (ChessMove readAhead)
11790 {
11791     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11792     char promoChar = NULLCHAR;
11793     ChessMove moveType;
11794     char move[MSG_SIZ];
11795     char *p, *q;
11796
11797     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11798         gameMode != AnalyzeMode && gameMode != Training) {
11799         gameFileFP = NULL;
11800         return FALSE;
11801     }
11802
11803     yyboardindex = forwardMostMove;
11804     if (readAhead != EndOfFile) {
11805       moveType = readAhead;
11806     } else {
11807       if (gameFileFP == NULL)
11808           return FALSE;
11809       moveType = (ChessMove) Myylex();
11810     }
11811
11812     done = FALSE;
11813     switch (moveType) {
11814       case Comment:
11815         if (appData.debugMode)
11816           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11817         p = yy_text;
11818
11819         /* append the comment but don't display it */
11820         AppendComment(currentMove, p, FALSE);
11821         return TRUE;
11822
11823       case WhiteCapturesEnPassant:
11824       case BlackCapturesEnPassant:
11825       case WhitePromotion:
11826       case BlackPromotion:
11827       case WhiteNonPromotion:
11828       case BlackNonPromotion:
11829       case NormalMove:
11830       case FirstLeg:
11831       case WhiteKingSideCastle:
11832       case WhiteQueenSideCastle:
11833       case BlackKingSideCastle:
11834       case BlackQueenSideCastle:
11835       case WhiteKingSideCastleWild:
11836       case WhiteQueenSideCastleWild:
11837       case BlackKingSideCastleWild:
11838       case BlackQueenSideCastleWild:
11839       /* PUSH Fabien */
11840       case WhiteHSideCastleFR:
11841       case WhiteASideCastleFR:
11842       case BlackHSideCastleFR:
11843       case BlackASideCastleFR:
11844       /* POP Fabien */
11845         if (appData.debugMode)
11846           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11847         fromX = currentMoveString[0] - AAA;
11848         fromY = currentMoveString[1] - ONE;
11849         toX = currentMoveString[2] - AAA;
11850         toY = currentMoveString[3] - ONE;
11851         promoChar = currentMoveString[4];
11852         if(promoChar == ';') promoChar = NULLCHAR;
11853         break;
11854
11855       case WhiteDrop:
11856       case BlackDrop:
11857         if (appData.debugMode)
11858           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11859         fromX = moveType == WhiteDrop ?
11860           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11861         (int) CharToPiece(ToLower(currentMoveString[0]));
11862         fromY = DROP_RANK;
11863         toX = currentMoveString[2] - AAA;
11864         toY = currentMoveString[3] - ONE;
11865         break;
11866
11867       case WhiteWins:
11868       case BlackWins:
11869       case GameIsDrawn:
11870       case GameUnfinished:
11871         if (appData.debugMode)
11872           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11873         p = strchr(yy_text, '{');
11874         if (p == NULL) p = strchr(yy_text, '(');
11875         if (p == NULL) {
11876             p = yy_text;
11877             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11878         } else {
11879             q = strchr(p, *p == '{' ? '}' : ')');
11880             if (q != NULL) *q = NULLCHAR;
11881             p++;
11882         }
11883         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11884         GameEnds(moveType, p, GE_FILE);
11885         done = TRUE;
11886         if (cmailMsgLoaded) {
11887             ClearHighlights();
11888             flipView = WhiteOnMove(currentMove);
11889             if (moveType == GameUnfinished) flipView = !flipView;
11890             if (appData.debugMode)
11891               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11892         }
11893         break;
11894
11895       case EndOfFile:
11896         if (appData.debugMode)
11897           fprintf(debugFP, "Parser hit end of file\n");
11898         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11899           case MT_NONE:
11900           case MT_CHECK:
11901             break;
11902           case MT_CHECKMATE:
11903           case MT_STAINMATE:
11904             if (WhiteOnMove(currentMove)) {
11905                 GameEnds(BlackWins, "Black mates", GE_FILE);
11906             } else {
11907                 GameEnds(WhiteWins, "White mates", GE_FILE);
11908             }
11909             break;
11910           case MT_STALEMATE:
11911             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11912             break;
11913         }
11914         done = TRUE;
11915         break;
11916
11917       case MoveNumberOne:
11918         if (lastLoadGameStart == GNUChessGame) {
11919             /* GNUChessGames have numbers, but they aren't move numbers */
11920             if (appData.debugMode)
11921               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11922                       yy_text, (int) moveType);
11923             return LoadGameOneMove(EndOfFile); /* tail recursion */
11924         }
11925         /* else fall thru */
11926
11927       case XBoardGame:
11928       case GNUChessGame:
11929       case PGNTag:
11930         /* Reached start of next game in file */
11931         if (appData.debugMode)
11932           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11933         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11934           case MT_NONE:
11935           case MT_CHECK:
11936             break;
11937           case MT_CHECKMATE:
11938           case MT_STAINMATE:
11939             if (WhiteOnMove(currentMove)) {
11940                 GameEnds(BlackWins, "Black mates", GE_FILE);
11941             } else {
11942                 GameEnds(WhiteWins, "White mates", GE_FILE);
11943             }
11944             break;
11945           case MT_STALEMATE:
11946             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11947             break;
11948         }
11949         done = TRUE;
11950         break;
11951
11952       case PositionDiagram:     /* should not happen; ignore */
11953       case ElapsedTime:         /* ignore */
11954       case NAG:                 /* ignore */
11955         if (appData.debugMode)
11956           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11957                   yy_text, (int) moveType);
11958         return LoadGameOneMove(EndOfFile); /* tail recursion */
11959
11960       case IllegalMove:
11961         if (appData.testLegality) {
11962             if (appData.debugMode)
11963               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11964             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11965                     (forwardMostMove / 2) + 1,
11966                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11967             DisplayError(move, 0);
11968             done = TRUE;
11969         } else {
11970             if (appData.debugMode)
11971               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11972                       yy_text, currentMoveString);
11973             fromX = currentMoveString[0] - AAA;
11974             fromY = currentMoveString[1] - ONE;
11975             toX = currentMoveString[2] - AAA;
11976             toY = currentMoveString[3] - ONE;
11977             promoChar = currentMoveString[4];
11978         }
11979         break;
11980
11981       case AmbiguousMove:
11982         if (appData.debugMode)
11983           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11984         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11985                 (forwardMostMove / 2) + 1,
11986                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11987         DisplayError(move, 0);
11988         done = TRUE;
11989         break;
11990
11991       default:
11992       case ImpossibleMove:
11993         if (appData.debugMode)
11994           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11995         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11996                 (forwardMostMove / 2) + 1,
11997                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11998         DisplayError(move, 0);
11999         done = TRUE;
12000         break;
12001     }
12002
12003     if (done) {
12004         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12005             DrawPosition(FALSE, boards[currentMove]);
12006             DisplayBothClocks();
12007             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12008               DisplayComment(currentMove - 1, commentList[currentMove]);
12009         }
12010         (void) StopLoadGameTimer();
12011         gameFileFP = NULL;
12012         cmailOldMove = forwardMostMove;
12013         return FALSE;
12014     } else {
12015         /* currentMoveString is set as a side-effect of yylex */
12016
12017         thinkOutput[0] = NULLCHAR;
12018         MakeMove(fromX, fromY, toX, toY, promoChar);
12019         killX = killY = -1; // [HGM] lion: used up
12020         currentMove = forwardMostMove;
12021         return TRUE;
12022     }
12023 }
12024
12025 /* Load the nth game from the given file */
12026 int
12027 LoadGameFromFile (char *filename, int n, char *title, int useList)
12028 {
12029     FILE *f;
12030     char buf[MSG_SIZ];
12031
12032     if (strcmp(filename, "-") == 0) {
12033         f = stdin;
12034         title = "stdin";
12035     } else {
12036         f = fopen(filename, "rb");
12037         if (f == NULL) {
12038           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12039             DisplayError(buf, errno);
12040             return FALSE;
12041         }
12042     }
12043     if (fseek(f, 0, 0) == -1) {
12044         /* f is not seekable; probably a pipe */
12045         useList = FALSE;
12046     }
12047     if (useList && n == 0) {
12048         int error = GameListBuild(f);
12049         if (error) {
12050             DisplayError(_("Cannot build game list"), error);
12051         } else if (!ListEmpty(&gameList) &&
12052                    ((ListGame *) gameList.tailPred)->number > 1) {
12053             GameListPopUp(f, title);
12054             return TRUE;
12055         }
12056         GameListDestroy();
12057         n = 1;
12058     }
12059     if (n == 0) n = 1;
12060     return LoadGame(f, n, title, FALSE);
12061 }
12062
12063
12064 void
12065 MakeRegisteredMove ()
12066 {
12067     int fromX, fromY, toX, toY;
12068     char promoChar;
12069     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12070         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12071           case CMAIL_MOVE:
12072           case CMAIL_DRAW:
12073             if (appData.debugMode)
12074               fprintf(debugFP, "Restoring %s for game %d\n",
12075                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12076
12077             thinkOutput[0] = NULLCHAR;
12078             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12079             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12080             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12081             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12082             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12083             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12084             MakeMove(fromX, fromY, toX, toY, promoChar);
12085             ShowMove(fromX, fromY, toX, toY);
12086
12087             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12088               case MT_NONE:
12089               case MT_CHECK:
12090                 break;
12091
12092               case MT_CHECKMATE:
12093               case MT_STAINMATE:
12094                 if (WhiteOnMove(currentMove)) {
12095                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12096                 } else {
12097                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12098                 }
12099                 break;
12100
12101               case MT_STALEMATE:
12102                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12103                 break;
12104             }
12105
12106             break;
12107
12108           case CMAIL_RESIGN:
12109             if (WhiteOnMove(currentMove)) {
12110                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12111             } else {
12112                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12113             }
12114             break;
12115
12116           case CMAIL_ACCEPT:
12117             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12118             break;
12119
12120           default:
12121             break;
12122         }
12123     }
12124
12125     return;
12126 }
12127
12128 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12129 int
12130 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12131 {
12132     int retVal;
12133
12134     if (gameNumber > nCmailGames) {
12135         DisplayError(_("No more games in this message"), 0);
12136         return FALSE;
12137     }
12138     if (f == lastLoadGameFP) {
12139         int offset = gameNumber - lastLoadGameNumber;
12140         if (offset == 0) {
12141             cmailMsg[0] = NULLCHAR;
12142             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12143                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12144                 nCmailMovesRegistered--;
12145             }
12146             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12147             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12148                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12149             }
12150         } else {
12151             if (! RegisterMove()) return FALSE;
12152         }
12153     }
12154
12155     retVal = LoadGame(f, gameNumber, title, useList);
12156
12157     /* Make move registered during previous look at this game, if any */
12158     MakeRegisteredMove();
12159
12160     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12161         commentList[currentMove]
12162           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12163         DisplayComment(currentMove - 1, commentList[currentMove]);
12164     }
12165
12166     return retVal;
12167 }
12168
12169 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12170 int
12171 ReloadGame (int offset)
12172 {
12173     int gameNumber = lastLoadGameNumber + offset;
12174     if (lastLoadGameFP == NULL) {
12175         DisplayError(_("No game has been loaded yet"), 0);
12176         return FALSE;
12177     }
12178     if (gameNumber <= 0) {
12179         DisplayError(_("Can't back up any further"), 0);
12180         return FALSE;
12181     }
12182     if (cmailMsgLoaded) {
12183         return CmailLoadGame(lastLoadGameFP, gameNumber,
12184                              lastLoadGameTitle, lastLoadGameUseList);
12185     } else {
12186         return LoadGame(lastLoadGameFP, gameNumber,
12187                         lastLoadGameTitle, lastLoadGameUseList);
12188     }
12189 }
12190
12191 int keys[EmptySquare+1];
12192
12193 int
12194 PositionMatches (Board b1, Board b2)
12195 {
12196     int r, f, sum=0;
12197     switch(appData.searchMode) {
12198         case 1: return CompareWithRights(b1, b2);
12199         case 2:
12200             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12201                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12202             }
12203             return TRUE;
12204         case 3:
12205             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12206               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12207                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12208             }
12209             return sum==0;
12210         case 4:
12211             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12212                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12213             }
12214             return sum==0;
12215     }
12216     return TRUE;
12217 }
12218
12219 #define Q_PROMO  4
12220 #define Q_EP     3
12221 #define Q_BCASTL 2
12222 #define Q_WCASTL 1
12223
12224 int pieceList[256], quickBoard[256];
12225 ChessSquare pieceType[256] = { EmptySquare };
12226 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12227 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12228 int soughtTotal, turn;
12229 Boolean epOK, flipSearch;
12230
12231 typedef struct {
12232     unsigned char piece, to;
12233 } Move;
12234
12235 #define DSIZE (250000)
12236
12237 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12238 Move *moveDatabase = initialSpace;
12239 unsigned int movePtr, dataSize = DSIZE;
12240
12241 int
12242 MakePieceList (Board board, int *counts)
12243 {
12244     int r, f, n=Q_PROMO, total=0;
12245     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12246     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12247         int sq = f + (r<<4);
12248         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12249             quickBoard[sq] = ++n;
12250             pieceList[n] = sq;
12251             pieceType[n] = board[r][f];
12252             counts[board[r][f]]++;
12253             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12254             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12255             total++;
12256         }
12257     }
12258     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12259     return total;
12260 }
12261
12262 void
12263 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12264 {
12265     int sq = fromX + (fromY<<4);
12266     int piece = quickBoard[sq], rook;
12267     quickBoard[sq] = 0;
12268     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12269     if(piece == pieceList[1] && fromY == toY) {
12270       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12271         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12272         moveDatabase[movePtr++].piece = Q_WCASTL;
12273         quickBoard[sq] = piece;
12274         piece = quickBoard[from]; quickBoard[from] = 0;
12275         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12276       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12277         quickBoard[sq] = 0; // remove Rook
12278         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12279         moveDatabase[movePtr++].piece = Q_WCASTL;
12280         quickBoard[sq] = pieceList[1]; // put King
12281         piece = rook;
12282         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12283       }
12284     } else
12285     if(piece == pieceList[2] && fromY == toY) {
12286       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12287         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12288         moveDatabase[movePtr++].piece = Q_BCASTL;
12289         quickBoard[sq] = piece;
12290         piece = quickBoard[from]; quickBoard[from] = 0;
12291         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12292       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12293         quickBoard[sq] = 0; // remove Rook
12294         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12295         moveDatabase[movePtr++].piece = Q_BCASTL;
12296         quickBoard[sq] = pieceList[2]; // put King
12297         piece = rook;
12298         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12299       }
12300     } else
12301     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12302         quickBoard[(fromY<<4)+toX] = 0;
12303         moveDatabase[movePtr].piece = Q_EP;
12304         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12305         moveDatabase[movePtr].to = sq;
12306     } else
12307     if(promoPiece != pieceType[piece]) {
12308         moveDatabase[movePtr++].piece = Q_PROMO;
12309         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12310     }
12311     moveDatabase[movePtr].piece = piece;
12312     quickBoard[sq] = piece;
12313     movePtr++;
12314 }
12315
12316 int
12317 PackGame (Board board)
12318 {
12319     Move *newSpace = NULL;
12320     moveDatabase[movePtr].piece = 0; // terminate previous game
12321     if(movePtr > dataSize) {
12322         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12323         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12324         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12325         if(newSpace) {
12326             int i;
12327             Move *p = moveDatabase, *q = newSpace;
12328             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12329             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12330             moveDatabase = newSpace;
12331         } else { // calloc failed, we must be out of memory. Too bad...
12332             dataSize = 0; // prevent calloc events for all subsequent games
12333             return 0;     // and signal this one isn't cached
12334         }
12335     }
12336     movePtr++;
12337     MakePieceList(board, counts);
12338     return movePtr;
12339 }
12340
12341 int
12342 QuickCompare (Board board, int *minCounts, int *maxCounts)
12343 {   // compare according to search mode
12344     int r, f;
12345     switch(appData.searchMode)
12346     {
12347       case 1: // exact position match
12348         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12349         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12350             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12351         }
12352         break;
12353       case 2: // can have extra material on empty squares
12354         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12355             if(board[r][f] == EmptySquare) continue;
12356             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12357         }
12358         break;
12359       case 3: // material with exact Pawn structure
12360         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12361             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12362             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12363         } // fall through to material comparison
12364       case 4: // exact material
12365         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12366         break;
12367       case 6: // material range with given imbalance
12368         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12369         // fall through to range comparison
12370       case 5: // material range
12371         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12372     }
12373     return TRUE;
12374 }
12375
12376 int
12377 QuickScan (Board board, Move *move)
12378 {   // reconstruct game,and compare all positions in it
12379     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12380     do {
12381         int piece = move->piece;
12382         int to = move->to, from = pieceList[piece];
12383         if(found < 0) { // if already found just scan to game end for final piece count
12384           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12385            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12386            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12387                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12388             ) {
12389             static int lastCounts[EmptySquare+1];
12390             int i;
12391             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12392             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12393           } else stretch = 0;
12394           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12395           if(found >= 0 && !appData.minPieces) return found;
12396         }
12397         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12398           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12399           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12400             piece = (++move)->piece;
12401             from = pieceList[piece];
12402             counts[pieceType[piece]]--;
12403             pieceType[piece] = (ChessSquare) move->to;
12404             counts[move->to]++;
12405           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12406             counts[pieceType[quickBoard[to]]]--;
12407             quickBoard[to] = 0; total--;
12408             move++;
12409             continue;
12410           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12411             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12412             from  = pieceList[piece]; // so this must be King
12413             quickBoard[from] = 0;
12414             pieceList[piece] = to;
12415             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12416             quickBoard[from] = 0; // rook
12417             quickBoard[to] = piece;
12418             to = move->to; piece = move->piece;
12419             goto aftercastle;
12420           }
12421         }
12422         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12423         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12424         quickBoard[from] = 0;
12425       aftercastle:
12426         quickBoard[to] = piece;
12427         pieceList[piece] = to;
12428         cnt++; turn ^= 3;
12429         move++;
12430     } while(1);
12431 }
12432
12433 void
12434 InitSearch ()
12435 {
12436     int r, f;
12437     flipSearch = FALSE;
12438     CopyBoard(soughtBoard, boards[currentMove]);
12439     soughtTotal = MakePieceList(soughtBoard, maxSought);
12440     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12441     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12442     CopyBoard(reverseBoard, boards[currentMove]);
12443     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12444         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12445         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12446         reverseBoard[r][f] = piece;
12447     }
12448     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12449     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12450     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12451                  || (boards[currentMove][CASTLING][2] == NoRights ||
12452                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12453                  && (boards[currentMove][CASTLING][5] == NoRights ||
12454                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12455       ) {
12456         flipSearch = TRUE;
12457         CopyBoard(flipBoard, soughtBoard);
12458         CopyBoard(rotateBoard, reverseBoard);
12459         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12460             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12461             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12462         }
12463     }
12464     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12465     if(appData.searchMode >= 5) {
12466         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12467         MakePieceList(soughtBoard, minSought);
12468         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12469     }
12470     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12471         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12472 }
12473
12474 GameInfo dummyInfo;
12475 static int creatingBook;
12476
12477 int
12478 GameContainsPosition (FILE *f, ListGame *lg)
12479 {
12480     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12481     int fromX, fromY, toX, toY;
12482     char promoChar;
12483     static int initDone=FALSE;
12484
12485     // weed out games based on numerical tag comparison
12486     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12487     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12488     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12489     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12490     if(!initDone) {
12491         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12492         initDone = TRUE;
12493     }
12494     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12495     else CopyBoard(boards[scratch], initialPosition); // default start position
12496     if(lg->moves) {
12497         turn = btm + 1;
12498         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12499         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12500     }
12501     if(btm) plyNr++;
12502     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12503     fseek(f, lg->offset, 0);
12504     yynewfile(f);
12505     while(1) {
12506         yyboardindex = scratch;
12507         quickFlag = plyNr+1;
12508         next = Myylex();
12509         quickFlag = 0;
12510         switch(next) {
12511             case PGNTag:
12512                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12513             default:
12514                 continue;
12515
12516             case XBoardGame:
12517             case GNUChessGame:
12518                 if(plyNr) return -1; // after we have seen moves, this is for new game
12519               continue;
12520
12521             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12522             case ImpossibleMove:
12523             case WhiteWins: // game ends here with these four
12524             case BlackWins:
12525             case GameIsDrawn:
12526             case GameUnfinished:
12527                 return -1;
12528
12529             case IllegalMove:
12530                 if(appData.testLegality) return -1;
12531             case WhiteCapturesEnPassant:
12532             case BlackCapturesEnPassant:
12533             case WhitePromotion:
12534             case BlackPromotion:
12535             case WhiteNonPromotion:
12536             case BlackNonPromotion:
12537             case NormalMove:
12538             case FirstLeg:
12539             case WhiteKingSideCastle:
12540             case WhiteQueenSideCastle:
12541             case BlackKingSideCastle:
12542             case BlackQueenSideCastle:
12543             case WhiteKingSideCastleWild:
12544             case WhiteQueenSideCastleWild:
12545             case BlackKingSideCastleWild:
12546             case BlackQueenSideCastleWild:
12547             case WhiteHSideCastleFR:
12548             case WhiteASideCastleFR:
12549             case BlackHSideCastleFR:
12550             case BlackASideCastleFR:
12551                 fromX = currentMoveString[0] - AAA;
12552                 fromY = currentMoveString[1] - ONE;
12553                 toX = currentMoveString[2] - AAA;
12554                 toY = currentMoveString[3] - ONE;
12555                 promoChar = currentMoveString[4];
12556                 break;
12557             case WhiteDrop:
12558             case BlackDrop:
12559                 fromX = next == WhiteDrop ?
12560                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12561                   (int) CharToPiece(ToLower(currentMoveString[0]));
12562                 fromY = DROP_RANK;
12563                 toX = currentMoveString[2] - AAA;
12564                 toY = currentMoveString[3] - ONE;
12565                 promoChar = 0;
12566                 break;
12567         }
12568         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12569         plyNr++;
12570         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12571         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12572         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12573         if(appData.findMirror) {
12574             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12575             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12576         }
12577     }
12578 }
12579
12580 /* Load the nth game from open file f */
12581 int
12582 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12583 {
12584     ChessMove cm;
12585     char buf[MSG_SIZ];
12586     int gn = gameNumber;
12587     ListGame *lg = NULL;
12588     int numPGNTags = 0;
12589     int err, pos = -1;
12590     GameMode oldGameMode;
12591     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12592
12593     if (appData.debugMode)
12594         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12595
12596     if (gameMode == Training )
12597         SetTrainingModeOff();
12598
12599     oldGameMode = gameMode;
12600     if (gameMode != BeginningOfGame) {
12601       Reset(FALSE, TRUE);
12602     }
12603     killX = killY = -1; // [HGM] lion: in case we did not Reset
12604
12605     gameFileFP = f;
12606     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12607         fclose(lastLoadGameFP);
12608     }
12609
12610     if (useList) {
12611         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12612
12613         if (lg) {
12614             fseek(f, lg->offset, 0);
12615             GameListHighlight(gameNumber);
12616             pos = lg->position;
12617             gn = 1;
12618         }
12619         else {
12620             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12621               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12622             else
12623             DisplayError(_("Game number out of range"), 0);
12624             return FALSE;
12625         }
12626     } else {
12627         GameListDestroy();
12628         if (fseek(f, 0, 0) == -1) {
12629             if (f == lastLoadGameFP ?
12630                 gameNumber == lastLoadGameNumber + 1 :
12631                 gameNumber == 1) {
12632                 gn = 1;
12633             } else {
12634                 DisplayError(_("Can't seek on game file"), 0);
12635                 return FALSE;
12636             }
12637         }
12638     }
12639     lastLoadGameFP = f;
12640     lastLoadGameNumber = gameNumber;
12641     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12642     lastLoadGameUseList = useList;
12643
12644     yynewfile(f);
12645
12646     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12647       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12648                 lg->gameInfo.black);
12649             DisplayTitle(buf);
12650     } else if (*title != NULLCHAR) {
12651         if (gameNumber > 1) {
12652           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12653             DisplayTitle(buf);
12654         } else {
12655             DisplayTitle(title);
12656         }
12657     }
12658
12659     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12660         gameMode = PlayFromGameFile;
12661         ModeHighlight();
12662     }
12663
12664     currentMove = forwardMostMove = backwardMostMove = 0;
12665     CopyBoard(boards[0], initialPosition);
12666     StopClocks();
12667
12668     /*
12669      * Skip the first gn-1 games in the file.
12670      * Also skip over anything that precedes an identifiable
12671      * start of game marker, to avoid being confused by
12672      * garbage at the start of the file.  Currently
12673      * recognized start of game markers are the move number "1",
12674      * the pattern "gnuchess .* game", the pattern
12675      * "^[#;%] [^ ]* game file", and a PGN tag block.
12676      * A game that starts with one of the latter two patterns
12677      * will also have a move number 1, possibly
12678      * following a position diagram.
12679      * 5-4-02: Let's try being more lenient and allowing a game to
12680      * start with an unnumbered move.  Does that break anything?
12681      */
12682     cm = lastLoadGameStart = EndOfFile;
12683     while (gn > 0) {
12684         yyboardindex = forwardMostMove;
12685         cm = (ChessMove) Myylex();
12686         switch (cm) {
12687           case EndOfFile:
12688             if (cmailMsgLoaded) {
12689                 nCmailGames = CMAIL_MAX_GAMES - gn;
12690             } else {
12691                 Reset(TRUE, TRUE);
12692                 DisplayError(_("Game not found in file"), 0);
12693             }
12694             return FALSE;
12695
12696           case GNUChessGame:
12697           case XBoardGame:
12698             gn--;
12699             lastLoadGameStart = cm;
12700             break;
12701
12702           case MoveNumberOne:
12703             switch (lastLoadGameStart) {
12704               case GNUChessGame:
12705               case XBoardGame:
12706               case PGNTag:
12707                 break;
12708               case MoveNumberOne:
12709               case EndOfFile:
12710                 gn--;           /* count this game */
12711                 lastLoadGameStart = cm;
12712                 break;
12713               default:
12714                 /* impossible */
12715                 break;
12716             }
12717             break;
12718
12719           case PGNTag:
12720             switch (lastLoadGameStart) {
12721               case GNUChessGame:
12722               case PGNTag:
12723               case MoveNumberOne:
12724               case EndOfFile:
12725                 gn--;           /* count this game */
12726                 lastLoadGameStart = cm;
12727                 break;
12728               case XBoardGame:
12729                 lastLoadGameStart = cm; /* game counted already */
12730                 break;
12731               default:
12732                 /* impossible */
12733                 break;
12734             }
12735             if (gn > 0) {
12736                 do {
12737                     yyboardindex = forwardMostMove;
12738                     cm = (ChessMove) Myylex();
12739                 } while (cm == PGNTag || cm == Comment);
12740             }
12741             break;
12742
12743           case WhiteWins:
12744           case BlackWins:
12745           case GameIsDrawn:
12746             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12747                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12748                     != CMAIL_OLD_RESULT) {
12749                     nCmailResults ++ ;
12750                     cmailResult[  CMAIL_MAX_GAMES
12751                                 - gn - 1] = CMAIL_OLD_RESULT;
12752                 }
12753             }
12754             break;
12755
12756           case NormalMove:
12757           case FirstLeg:
12758             /* Only a NormalMove can be at the start of a game
12759              * without a position diagram. */
12760             if (lastLoadGameStart == EndOfFile ) {
12761               gn--;
12762               lastLoadGameStart = MoveNumberOne;
12763             }
12764             break;
12765
12766           default:
12767             break;
12768         }
12769     }
12770
12771     if (appData.debugMode)
12772       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12773
12774     if (cm == XBoardGame) {
12775         /* Skip any header junk before position diagram and/or move 1 */
12776         for (;;) {
12777             yyboardindex = forwardMostMove;
12778             cm = (ChessMove) Myylex();
12779
12780             if (cm == EndOfFile ||
12781                 cm == GNUChessGame || cm == XBoardGame) {
12782                 /* Empty game; pretend end-of-file and handle later */
12783                 cm = EndOfFile;
12784                 break;
12785             }
12786
12787             if (cm == MoveNumberOne || cm == PositionDiagram ||
12788                 cm == PGNTag || cm == Comment)
12789               break;
12790         }
12791     } else if (cm == GNUChessGame) {
12792         if (gameInfo.event != NULL) {
12793             free(gameInfo.event);
12794         }
12795         gameInfo.event = StrSave(yy_text);
12796     }
12797
12798     startedFromSetupPosition = FALSE;
12799     while (cm == PGNTag) {
12800         if (appData.debugMode)
12801           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12802         err = ParsePGNTag(yy_text, &gameInfo);
12803         if (!err) numPGNTags++;
12804
12805         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12806         if(gameInfo.variant != oldVariant) {
12807             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12808             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12809             InitPosition(TRUE);
12810             oldVariant = gameInfo.variant;
12811             if (appData.debugMode)
12812               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12813         }
12814
12815
12816         if (gameInfo.fen != NULL) {
12817           Board initial_position;
12818           startedFromSetupPosition = TRUE;
12819           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12820             Reset(TRUE, TRUE);
12821             DisplayError(_("Bad FEN position in file"), 0);
12822             return FALSE;
12823           }
12824           CopyBoard(boards[0], initial_position);
12825           if (blackPlaysFirst) {
12826             currentMove = forwardMostMove = backwardMostMove = 1;
12827             CopyBoard(boards[1], initial_position);
12828             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12829             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12830             timeRemaining[0][1] = whiteTimeRemaining;
12831             timeRemaining[1][1] = blackTimeRemaining;
12832             if (commentList[0] != NULL) {
12833               commentList[1] = commentList[0];
12834               commentList[0] = NULL;
12835             }
12836           } else {
12837             currentMove = forwardMostMove = backwardMostMove = 0;
12838           }
12839           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12840           {   int i;
12841               initialRulePlies = FENrulePlies;
12842               for( i=0; i< nrCastlingRights; i++ )
12843                   initialRights[i] = initial_position[CASTLING][i];
12844           }
12845           yyboardindex = forwardMostMove;
12846           free(gameInfo.fen);
12847           gameInfo.fen = NULL;
12848         }
12849
12850         yyboardindex = forwardMostMove;
12851         cm = (ChessMove) Myylex();
12852
12853         /* Handle comments interspersed among the tags */
12854         while (cm == Comment) {
12855             char *p;
12856             if (appData.debugMode)
12857               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12858             p = yy_text;
12859             AppendComment(currentMove, p, FALSE);
12860             yyboardindex = forwardMostMove;
12861             cm = (ChessMove) Myylex();
12862         }
12863     }
12864
12865     /* don't rely on existence of Event tag since if game was
12866      * pasted from clipboard the Event tag may not exist
12867      */
12868     if (numPGNTags > 0){
12869         char *tags;
12870         if (gameInfo.variant == VariantNormal) {
12871           VariantClass v = StringToVariant(gameInfo.event);
12872           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12873           if(v < VariantShogi) gameInfo.variant = v;
12874         }
12875         if (!matchMode) {
12876           if( appData.autoDisplayTags ) {
12877             tags = PGNTags(&gameInfo);
12878             TagsPopUp(tags, CmailMsg());
12879             free(tags);
12880           }
12881         }
12882     } else {
12883         /* Make something up, but don't display it now */
12884         SetGameInfo();
12885         TagsPopDown();
12886     }
12887
12888     if (cm == PositionDiagram) {
12889         int i, j;
12890         char *p;
12891         Board initial_position;
12892
12893         if (appData.debugMode)
12894           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12895
12896         if (!startedFromSetupPosition) {
12897             p = yy_text;
12898             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12899               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12900                 switch (*p) {
12901                   case '{':
12902                   case '[':
12903                   case '-':
12904                   case ' ':
12905                   case '\t':
12906                   case '\n':
12907                   case '\r':
12908                     break;
12909                   default:
12910                     initial_position[i][j++] = CharToPiece(*p);
12911                     break;
12912                 }
12913             while (*p == ' ' || *p == '\t' ||
12914                    *p == '\n' || *p == '\r') p++;
12915
12916             if (strncmp(p, "black", strlen("black"))==0)
12917               blackPlaysFirst = TRUE;
12918             else
12919               blackPlaysFirst = FALSE;
12920             startedFromSetupPosition = TRUE;
12921
12922             CopyBoard(boards[0], initial_position);
12923             if (blackPlaysFirst) {
12924                 currentMove = forwardMostMove = backwardMostMove = 1;
12925                 CopyBoard(boards[1], initial_position);
12926                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12927                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12928                 timeRemaining[0][1] = whiteTimeRemaining;
12929                 timeRemaining[1][1] = blackTimeRemaining;
12930                 if (commentList[0] != NULL) {
12931                     commentList[1] = commentList[0];
12932                     commentList[0] = NULL;
12933                 }
12934             } else {
12935                 currentMove = forwardMostMove = backwardMostMove = 0;
12936             }
12937         }
12938         yyboardindex = forwardMostMove;
12939         cm = (ChessMove) Myylex();
12940     }
12941
12942   if(!creatingBook) {
12943     if (first.pr == NoProc) {
12944         StartChessProgram(&first);
12945     }
12946     InitChessProgram(&first, FALSE);
12947     SendToProgram("force\n", &first);
12948     if (startedFromSetupPosition) {
12949         SendBoard(&first, forwardMostMove);
12950     if (appData.debugMode) {
12951         fprintf(debugFP, "Load Game\n");
12952     }
12953         DisplayBothClocks();
12954     }
12955   }
12956
12957     /* [HGM] server: flag to write setup moves in broadcast file as one */
12958     loadFlag = appData.suppressLoadMoves;
12959
12960     while (cm == Comment) {
12961         char *p;
12962         if (appData.debugMode)
12963           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12964         p = yy_text;
12965         AppendComment(currentMove, p, FALSE);
12966         yyboardindex = forwardMostMove;
12967         cm = (ChessMove) Myylex();
12968     }
12969
12970     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12971         cm == WhiteWins || cm == BlackWins ||
12972         cm == GameIsDrawn || cm == GameUnfinished) {
12973         DisplayMessage("", _("No moves in game"));
12974         if (cmailMsgLoaded) {
12975             if (appData.debugMode)
12976               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12977             ClearHighlights();
12978             flipView = FALSE;
12979         }
12980         DrawPosition(FALSE, boards[currentMove]);
12981         DisplayBothClocks();
12982         gameMode = EditGame;
12983         ModeHighlight();
12984         gameFileFP = NULL;
12985         cmailOldMove = 0;
12986         return TRUE;
12987     }
12988
12989     // [HGM] PV info: routine tests if comment empty
12990     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12991         DisplayComment(currentMove - 1, commentList[currentMove]);
12992     }
12993     if (!matchMode && appData.timeDelay != 0)
12994       DrawPosition(FALSE, boards[currentMove]);
12995
12996     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12997       programStats.ok_to_send = 1;
12998     }
12999
13000     /* if the first token after the PGN tags is a move
13001      * and not move number 1, retrieve it from the parser
13002      */
13003     if (cm != MoveNumberOne)
13004         LoadGameOneMove(cm);
13005
13006     /* load the remaining moves from the file */
13007     while (LoadGameOneMove(EndOfFile)) {
13008       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13009       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13010     }
13011
13012     /* rewind to the start of the game */
13013     currentMove = backwardMostMove;
13014
13015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13016
13017     if (oldGameMode == AnalyzeFile) {
13018       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13019       AnalyzeFileEvent();
13020     } else
13021     if (oldGameMode == AnalyzeMode) {
13022       AnalyzeFileEvent();
13023     }
13024
13025     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13026         long int w, b; // [HGM] adjourn: restore saved clock times
13027         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13028         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13029             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13030             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13031         }
13032     }
13033
13034     if(creatingBook) return TRUE;
13035     if (!matchMode && pos > 0) {
13036         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13037     } else
13038     if (matchMode || appData.timeDelay == 0) {
13039       ToEndEvent();
13040     } else if (appData.timeDelay > 0) {
13041       AutoPlayGameLoop();
13042     }
13043
13044     if (appData.debugMode)
13045         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13046
13047     loadFlag = 0; /* [HGM] true game starts */
13048     return TRUE;
13049 }
13050
13051 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13052 int
13053 ReloadPosition (int offset)
13054 {
13055     int positionNumber = lastLoadPositionNumber + offset;
13056     if (lastLoadPositionFP == NULL) {
13057         DisplayError(_("No position has been loaded yet"), 0);
13058         return FALSE;
13059     }
13060     if (positionNumber <= 0) {
13061         DisplayError(_("Can't back up any further"), 0);
13062         return FALSE;
13063     }
13064     return LoadPosition(lastLoadPositionFP, positionNumber,
13065                         lastLoadPositionTitle);
13066 }
13067
13068 /* Load the nth position from the given file */
13069 int
13070 LoadPositionFromFile (char *filename, int n, char *title)
13071 {
13072     FILE *f;
13073     char buf[MSG_SIZ];
13074
13075     if (strcmp(filename, "-") == 0) {
13076         return LoadPosition(stdin, n, "stdin");
13077     } else {
13078         f = fopen(filename, "rb");
13079         if (f == NULL) {
13080             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13081             DisplayError(buf, errno);
13082             return FALSE;
13083         } else {
13084             return LoadPosition(f, n, title);
13085         }
13086     }
13087 }
13088
13089 /* Load the nth position from the given open file, and close it */
13090 int
13091 LoadPosition (FILE *f, int positionNumber, char *title)
13092 {
13093     char *p, line[MSG_SIZ];
13094     Board initial_position;
13095     int i, j, fenMode, pn;
13096
13097     if (gameMode == Training )
13098         SetTrainingModeOff();
13099
13100     if (gameMode != BeginningOfGame) {
13101         Reset(FALSE, TRUE);
13102     }
13103     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13104         fclose(lastLoadPositionFP);
13105     }
13106     if (positionNumber == 0) positionNumber = 1;
13107     lastLoadPositionFP = f;
13108     lastLoadPositionNumber = positionNumber;
13109     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13110     if (first.pr == NoProc && !appData.noChessProgram) {
13111       StartChessProgram(&first);
13112       InitChessProgram(&first, FALSE);
13113     }
13114     pn = positionNumber;
13115     if (positionNumber < 0) {
13116         /* Negative position number means to seek to that byte offset */
13117         if (fseek(f, -positionNumber, 0) == -1) {
13118             DisplayError(_("Can't seek on position file"), 0);
13119             return FALSE;
13120         };
13121         pn = 1;
13122     } else {
13123         if (fseek(f, 0, 0) == -1) {
13124             if (f == lastLoadPositionFP ?
13125                 positionNumber == lastLoadPositionNumber + 1 :
13126                 positionNumber == 1) {
13127                 pn = 1;
13128             } else {
13129                 DisplayError(_("Can't seek on position file"), 0);
13130                 return FALSE;
13131             }
13132         }
13133     }
13134     /* See if this file is FEN or old-style xboard */
13135     if (fgets(line, MSG_SIZ, f) == NULL) {
13136         DisplayError(_("Position not found in file"), 0);
13137         return FALSE;
13138     }
13139     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13140     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13141
13142     if (pn >= 2) {
13143         if (fenMode || line[0] == '#') pn--;
13144         while (pn > 0) {
13145             /* skip positions before number pn */
13146             if (fgets(line, MSG_SIZ, f) == NULL) {
13147                 Reset(TRUE, TRUE);
13148                 DisplayError(_("Position not found in file"), 0);
13149                 return FALSE;
13150             }
13151             if (fenMode || line[0] == '#') pn--;
13152         }
13153     }
13154
13155     if (fenMode) {
13156         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13157             DisplayError(_("Bad FEN position in file"), 0);
13158             return FALSE;
13159         }
13160     } else {
13161         (void) fgets(line, MSG_SIZ, f);
13162         (void) fgets(line, MSG_SIZ, f);
13163
13164         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13165             (void) fgets(line, MSG_SIZ, f);
13166             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13167                 if (*p == ' ')
13168                   continue;
13169                 initial_position[i][j++] = CharToPiece(*p);
13170             }
13171         }
13172
13173         blackPlaysFirst = FALSE;
13174         if (!feof(f)) {
13175             (void) fgets(line, MSG_SIZ, f);
13176             if (strncmp(line, "black", strlen("black"))==0)
13177               blackPlaysFirst = TRUE;
13178         }
13179     }
13180     startedFromSetupPosition = TRUE;
13181
13182     CopyBoard(boards[0], initial_position);
13183     if (blackPlaysFirst) {
13184         currentMove = forwardMostMove = backwardMostMove = 1;
13185         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13186         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13187         CopyBoard(boards[1], initial_position);
13188         DisplayMessage("", _("Black to play"));
13189     } else {
13190         currentMove = forwardMostMove = backwardMostMove = 0;
13191         DisplayMessage("", _("White to play"));
13192     }
13193     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13194     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13195         SendToProgram("force\n", &first);
13196         SendBoard(&first, forwardMostMove);
13197     }
13198     if (appData.debugMode) {
13199 int i, j;
13200   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13201   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13202         fprintf(debugFP, "Load Position\n");
13203     }
13204
13205     if (positionNumber > 1) {
13206       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13207         DisplayTitle(line);
13208     } else {
13209         DisplayTitle(title);
13210     }
13211     gameMode = EditGame;
13212     ModeHighlight();
13213     ResetClocks();
13214     timeRemaining[0][1] = whiteTimeRemaining;
13215     timeRemaining[1][1] = blackTimeRemaining;
13216     DrawPosition(FALSE, boards[currentMove]);
13217
13218     return TRUE;
13219 }
13220
13221
13222 void
13223 CopyPlayerNameIntoFileName (char **dest, char *src)
13224 {
13225     while (*src != NULLCHAR && *src != ',') {
13226         if (*src == ' ') {
13227             *(*dest)++ = '_';
13228             src++;
13229         } else {
13230             *(*dest)++ = *src++;
13231         }
13232     }
13233 }
13234
13235 char *
13236 DefaultFileName (char *ext)
13237 {
13238     static char def[MSG_SIZ];
13239     char *p;
13240
13241     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13242         p = def;
13243         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13244         *p++ = '-';
13245         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13246         *p++ = '.';
13247         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13248     } else {
13249         def[0] = NULLCHAR;
13250     }
13251     return def;
13252 }
13253
13254 /* Save the current game to the given file */
13255 int
13256 SaveGameToFile (char *filename, int append)
13257 {
13258     FILE *f;
13259     char buf[MSG_SIZ];
13260     int result, i, t,tot=0;
13261
13262     if (strcmp(filename, "-") == 0) {
13263         return SaveGame(stdout, 0, NULL);
13264     } else {
13265         for(i=0; i<10; i++) { // upto 10 tries
13266              f = fopen(filename, append ? "a" : "w");
13267              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13268              if(f || errno != 13) break;
13269              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13270              tot += t;
13271         }
13272         if (f == NULL) {
13273             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13274             DisplayError(buf, errno);
13275             return FALSE;
13276         } else {
13277             safeStrCpy(buf, lastMsg, MSG_SIZ);
13278             DisplayMessage(_("Waiting for access to save file"), "");
13279             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13280             DisplayMessage(_("Saving game"), "");
13281             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13282             result = SaveGame(f, 0, NULL);
13283             DisplayMessage(buf, "");
13284             return result;
13285         }
13286     }
13287 }
13288
13289 char *
13290 SavePart (char *str)
13291 {
13292     static char buf[MSG_SIZ];
13293     char *p;
13294
13295     p = strchr(str, ' ');
13296     if (p == NULL) return str;
13297     strncpy(buf, str, p - str);
13298     buf[p - str] = NULLCHAR;
13299     return buf;
13300 }
13301
13302 #define PGN_MAX_LINE 75
13303
13304 #define PGN_SIDE_WHITE  0
13305 #define PGN_SIDE_BLACK  1
13306
13307 static int
13308 FindFirstMoveOutOfBook (int side)
13309 {
13310     int result = -1;
13311
13312     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13313         int index = backwardMostMove;
13314         int has_book_hit = 0;
13315
13316         if( (index % 2) != side ) {
13317             index++;
13318         }
13319
13320         while( index < forwardMostMove ) {
13321             /* Check to see if engine is in book */
13322             int depth = pvInfoList[index].depth;
13323             int score = pvInfoList[index].score;
13324             int in_book = 0;
13325
13326             if( depth <= 2 ) {
13327                 in_book = 1;
13328             }
13329             else if( score == 0 && depth == 63 ) {
13330                 in_book = 1; /* Zappa */
13331             }
13332             else if( score == 2 && depth == 99 ) {
13333                 in_book = 1; /* Abrok */
13334             }
13335
13336             has_book_hit += in_book;
13337
13338             if( ! in_book ) {
13339                 result = index;
13340
13341                 break;
13342             }
13343
13344             index += 2;
13345         }
13346     }
13347
13348     return result;
13349 }
13350
13351 void
13352 GetOutOfBookInfo (char * buf)
13353 {
13354     int oob[2];
13355     int i;
13356     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13357
13358     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13359     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13360
13361     *buf = '\0';
13362
13363     if( oob[0] >= 0 || oob[1] >= 0 ) {
13364         for( i=0; i<2; i++ ) {
13365             int idx = oob[i];
13366
13367             if( idx >= 0 ) {
13368                 if( i > 0 && oob[0] >= 0 ) {
13369                     strcat( buf, "   " );
13370                 }
13371
13372                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13373                 sprintf( buf+strlen(buf), "%s%.2f",
13374                     pvInfoList[idx].score >= 0 ? "+" : "",
13375                     pvInfoList[idx].score / 100.0 );
13376             }
13377         }
13378     }
13379 }
13380
13381 /* Save game in PGN style */
13382 static void
13383 SaveGamePGN2 (FILE *f)
13384 {
13385     int i, offset, linelen, newblock;
13386 //    char *movetext;
13387     char numtext[32];
13388     int movelen, numlen, blank;
13389     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13390
13391     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13392
13393     PrintPGNTags(f, &gameInfo);
13394
13395     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13396
13397     if (backwardMostMove > 0 || startedFromSetupPosition) {
13398         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13399         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13400         fprintf(f, "\n{--------------\n");
13401         PrintPosition(f, backwardMostMove);
13402         fprintf(f, "--------------}\n");
13403         free(fen);
13404     }
13405     else {
13406         /* [AS] Out of book annotation */
13407         if( appData.saveOutOfBookInfo ) {
13408             char buf[64];
13409
13410             GetOutOfBookInfo( buf );
13411
13412             if( buf[0] != '\0' ) {
13413                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13414             }
13415         }
13416
13417         fprintf(f, "\n");
13418     }
13419
13420     i = backwardMostMove;
13421     linelen = 0;
13422     newblock = TRUE;
13423
13424     while (i < forwardMostMove) {
13425         /* Print comments preceding this move */
13426         if (commentList[i] != NULL) {
13427             if (linelen > 0) fprintf(f, "\n");
13428             fprintf(f, "%s", commentList[i]);
13429             linelen = 0;
13430             newblock = TRUE;
13431         }
13432
13433         /* Format move number */
13434         if ((i % 2) == 0)
13435           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13436         else
13437           if (newblock)
13438             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13439           else
13440             numtext[0] = NULLCHAR;
13441
13442         numlen = strlen(numtext);
13443         newblock = FALSE;
13444
13445         /* Print move number */
13446         blank = linelen > 0 && numlen > 0;
13447         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13448             fprintf(f, "\n");
13449             linelen = 0;
13450             blank = 0;
13451         }
13452         if (blank) {
13453             fprintf(f, " ");
13454             linelen++;
13455         }
13456         fprintf(f, "%s", numtext);
13457         linelen += numlen;
13458
13459         /* Get move */
13460         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13461         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13462
13463         /* Print move */
13464         blank = linelen > 0 && movelen > 0;
13465         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13466             fprintf(f, "\n");
13467             linelen = 0;
13468             blank = 0;
13469         }
13470         if (blank) {
13471             fprintf(f, " ");
13472             linelen++;
13473         }
13474         fprintf(f, "%s", move_buffer);
13475         linelen += movelen;
13476
13477         /* [AS] Add PV info if present */
13478         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13479             /* [HGM] add time */
13480             char buf[MSG_SIZ]; int seconds;
13481
13482             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13483
13484             if( seconds <= 0)
13485               buf[0] = 0;
13486             else
13487               if( seconds < 30 )
13488                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13489               else
13490                 {
13491                   seconds = (seconds + 4)/10; // round to full seconds
13492                   if( seconds < 60 )
13493                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13494                   else
13495                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13496                 }
13497
13498             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13499                       pvInfoList[i].score >= 0 ? "+" : "",
13500                       pvInfoList[i].score / 100.0,
13501                       pvInfoList[i].depth,
13502                       buf );
13503
13504             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13505
13506             /* Print score/depth */
13507             blank = linelen > 0 && movelen > 0;
13508             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13509                 fprintf(f, "\n");
13510                 linelen = 0;
13511                 blank = 0;
13512             }
13513             if (blank) {
13514                 fprintf(f, " ");
13515                 linelen++;
13516             }
13517             fprintf(f, "%s", move_buffer);
13518             linelen += movelen;
13519         }
13520
13521         i++;
13522     }
13523
13524     /* Start a new line */
13525     if (linelen > 0) fprintf(f, "\n");
13526
13527     /* Print comments after last move */
13528     if (commentList[i] != NULL) {
13529         fprintf(f, "%s\n", commentList[i]);
13530     }
13531
13532     /* Print result */
13533     if (gameInfo.resultDetails != NULL &&
13534         gameInfo.resultDetails[0] != NULLCHAR) {
13535         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13536         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13537            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13538             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13539         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13540     } else {
13541         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13542     }
13543 }
13544
13545 /* Save game in PGN style and close the file */
13546 int
13547 SaveGamePGN (FILE *f)
13548 {
13549     SaveGamePGN2(f);
13550     fclose(f);
13551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13552     return TRUE;
13553 }
13554
13555 /* Save game in old style and close the file */
13556 int
13557 SaveGameOldStyle (FILE *f)
13558 {
13559     int i, offset;
13560     time_t tm;
13561
13562     tm = time((time_t *) NULL);
13563
13564     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13565     PrintOpponents(f);
13566
13567     if (backwardMostMove > 0 || startedFromSetupPosition) {
13568         fprintf(f, "\n[--------------\n");
13569         PrintPosition(f, backwardMostMove);
13570         fprintf(f, "--------------]\n");
13571     } else {
13572         fprintf(f, "\n");
13573     }
13574
13575     i = backwardMostMove;
13576     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13577
13578     while (i < forwardMostMove) {
13579         if (commentList[i] != NULL) {
13580             fprintf(f, "[%s]\n", commentList[i]);
13581         }
13582
13583         if ((i % 2) == 1) {
13584             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13585             i++;
13586         } else {
13587             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13588             i++;
13589             if (commentList[i] != NULL) {
13590                 fprintf(f, "\n");
13591                 continue;
13592             }
13593             if (i >= forwardMostMove) {
13594                 fprintf(f, "\n");
13595                 break;
13596             }
13597             fprintf(f, "%s\n", parseList[i]);
13598             i++;
13599         }
13600     }
13601
13602     if (commentList[i] != NULL) {
13603         fprintf(f, "[%s]\n", commentList[i]);
13604     }
13605
13606     /* This isn't really the old style, but it's close enough */
13607     if (gameInfo.resultDetails != NULL &&
13608         gameInfo.resultDetails[0] != NULLCHAR) {
13609         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13610                 gameInfo.resultDetails);
13611     } else {
13612         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13613     }
13614
13615     fclose(f);
13616     return TRUE;
13617 }
13618
13619 /* Save the current game to open file f and close the file */
13620 int
13621 SaveGame (FILE *f, int dummy, char *dummy2)
13622 {
13623     if (gameMode == EditPosition) EditPositionDone(TRUE);
13624     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13625     if (appData.oldSaveStyle)
13626       return SaveGameOldStyle(f);
13627     else
13628       return SaveGamePGN(f);
13629 }
13630
13631 /* Save the current position to the given file */
13632 int
13633 SavePositionToFile (char *filename)
13634 {
13635     FILE *f;
13636     char buf[MSG_SIZ];
13637
13638     if (strcmp(filename, "-") == 0) {
13639         return SavePosition(stdout, 0, NULL);
13640     } else {
13641         f = fopen(filename, "a");
13642         if (f == NULL) {
13643             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13644             DisplayError(buf, errno);
13645             return FALSE;
13646         } else {
13647             safeStrCpy(buf, lastMsg, MSG_SIZ);
13648             DisplayMessage(_("Waiting for access to save file"), "");
13649             flock(fileno(f), LOCK_EX); // [HGM] lock
13650             DisplayMessage(_("Saving position"), "");
13651             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13652             SavePosition(f, 0, NULL);
13653             DisplayMessage(buf, "");
13654             return TRUE;
13655         }
13656     }
13657 }
13658
13659 /* Save the current position to the given open file and close the file */
13660 int
13661 SavePosition (FILE *f, int dummy, char *dummy2)
13662 {
13663     time_t tm;
13664     char *fen;
13665
13666     if (gameMode == EditPosition) EditPositionDone(TRUE);
13667     if (appData.oldSaveStyle) {
13668         tm = time((time_t *) NULL);
13669
13670         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13671         PrintOpponents(f);
13672         fprintf(f, "[--------------\n");
13673         PrintPosition(f, currentMove);
13674         fprintf(f, "--------------]\n");
13675     } else {
13676         fen = PositionToFEN(currentMove, NULL, 1);
13677         fprintf(f, "%s\n", fen);
13678         free(fen);
13679     }
13680     fclose(f);
13681     return TRUE;
13682 }
13683
13684 void
13685 ReloadCmailMsgEvent (int unregister)
13686 {
13687 #if !WIN32
13688     static char *inFilename = NULL;
13689     static char *outFilename;
13690     int i;
13691     struct stat inbuf, outbuf;
13692     int status;
13693
13694     /* Any registered moves are unregistered if unregister is set, */
13695     /* i.e. invoked by the signal handler */
13696     if (unregister) {
13697         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13698             cmailMoveRegistered[i] = FALSE;
13699             if (cmailCommentList[i] != NULL) {
13700                 free(cmailCommentList[i]);
13701                 cmailCommentList[i] = NULL;
13702             }
13703         }
13704         nCmailMovesRegistered = 0;
13705     }
13706
13707     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13708         cmailResult[i] = CMAIL_NOT_RESULT;
13709     }
13710     nCmailResults = 0;
13711
13712     if (inFilename == NULL) {
13713         /* Because the filenames are static they only get malloced once  */
13714         /* and they never get freed                                      */
13715         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13716         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13717
13718         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13719         sprintf(outFilename, "%s.out", appData.cmailGameName);
13720     }
13721
13722     status = stat(outFilename, &outbuf);
13723     if (status < 0) {
13724         cmailMailedMove = FALSE;
13725     } else {
13726         status = stat(inFilename, &inbuf);
13727         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13728     }
13729
13730     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13731        counts the games, notes how each one terminated, etc.
13732
13733        It would be nice to remove this kludge and instead gather all
13734        the information while building the game list.  (And to keep it
13735        in the game list nodes instead of having a bunch of fixed-size
13736        parallel arrays.)  Note this will require getting each game's
13737        termination from the PGN tags, as the game list builder does
13738        not process the game moves.  --mann
13739        */
13740     cmailMsgLoaded = TRUE;
13741     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13742
13743     /* Load first game in the file or popup game menu */
13744     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13745
13746 #endif /* !WIN32 */
13747     return;
13748 }
13749
13750 int
13751 RegisterMove ()
13752 {
13753     FILE *f;
13754     char string[MSG_SIZ];
13755
13756     if (   cmailMailedMove
13757         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13758         return TRUE;            /* Allow free viewing  */
13759     }
13760
13761     /* Unregister move to ensure that we don't leave RegisterMove        */
13762     /* with the move registered when the conditions for registering no   */
13763     /* longer hold                                                       */
13764     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13765         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13766         nCmailMovesRegistered --;
13767
13768         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13769           {
13770               free(cmailCommentList[lastLoadGameNumber - 1]);
13771               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13772           }
13773     }
13774
13775     if (cmailOldMove == -1) {
13776         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13777         return FALSE;
13778     }
13779
13780     if (currentMove > cmailOldMove + 1) {
13781         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13782         return FALSE;
13783     }
13784
13785     if (currentMove < cmailOldMove) {
13786         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13787         return FALSE;
13788     }
13789
13790     if (forwardMostMove > currentMove) {
13791         /* Silently truncate extra moves */
13792         TruncateGame();
13793     }
13794
13795     if (   (currentMove == cmailOldMove + 1)
13796         || (   (currentMove == cmailOldMove)
13797             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13798                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13799         if (gameInfo.result != GameUnfinished) {
13800             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13801         }
13802
13803         if (commentList[currentMove] != NULL) {
13804             cmailCommentList[lastLoadGameNumber - 1]
13805               = StrSave(commentList[currentMove]);
13806         }
13807         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13808
13809         if (appData.debugMode)
13810           fprintf(debugFP, "Saving %s for game %d\n",
13811                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13812
13813         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13814
13815         f = fopen(string, "w");
13816         if (appData.oldSaveStyle) {
13817             SaveGameOldStyle(f); /* also closes the file */
13818
13819             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13820             f = fopen(string, "w");
13821             SavePosition(f, 0, NULL); /* also closes the file */
13822         } else {
13823             fprintf(f, "{--------------\n");
13824             PrintPosition(f, currentMove);
13825             fprintf(f, "--------------}\n\n");
13826
13827             SaveGame(f, 0, NULL); /* also closes the file*/
13828         }
13829
13830         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13831         nCmailMovesRegistered ++;
13832     } else if (nCmailGames == 1) {
13833         DisplayError(_("You have not made a move yet"), 0);
13834         return FALSE;
13835     }
13836
13837     return TRUE;
13838 }
13839
13840 void
13841 MailMoveEvent ()
13842 {
13843 #if !WIN32
13844     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13845     FILE *commandOutput;
13846     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13847     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13848     int nBuffers;
13849     int i;
13850     int archived;
13851     char *arcDir;
13852
13853     if (! cmailMsgLoaded) {
13854         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13855         return;
13856     }
13857
13858     if (nCmailGames == nCmailResults) {
13859         DisplayError(_("No unfinished games"), 0);
13860         return;
13861     }
13862
13863 #if CMAIL_PROHIBIT_REMAIL
13864     if (cmailMailedMove) {
13865       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);
13866         DisplayError(msg, 0);
13867         return;
13868     }
13869 #endif
13870
13871     if (! (cmailMailedMove || RegisterMove())) return;
13872
13873     if (   cmailMailedMove
13874         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13875       snprintf(string, MSG_SIZ, partCommandString,
13876                appData.debugMode ? " -v" : "", appData.cmailGameName);
13877         commandOutput = popen(string, "r");
13878
13879         if (commandOutput == NULL) {
13880             DisplayError(_("Failed to invoke cmail"), 0);
13881         } else {
13882             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13883                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13884             }
13885             if (nBuffers > 1) {
13886                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13887                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13888                 nBytes = MSG_SIZ - 1;
13889             } else {
13890                 (void) memcpy(msg, buffer, nBytes);
13891             }
13892             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13893
13894             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13895                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13896
13897                 archived = TRUE;
13898                 for (i = 0; i < nCmailGames; i ++) {
13899                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13900                         archived = FALSE;
13901                     }
13902                 }
13903                 if (   archived
13904                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13905                         != NULL)) {
13906                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13907                            arcDir,
13908                            appData.cmailGameName,
13909                            gameInfo.date);
13910                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13911                     cmailMsgLoaded = FALSE;
13912                 }
13913             }
13914
13915             DisplayInformation(msg);
13916             pclose(commandOutput);
13917         }
13918     } else {
13919         if ((*cmailMsg) != '\0') {
13920             DisplayInformation(cmailMsg);
13921         }
13922     }
13923
13924     return;
13925 #endif /* !WIN32 */
13926 }
13927
13928 char *
13929 CmailMsg ()
13930 {
13931 #if WIN32
13932     return NULL;
13933 #else
13934     int  prependComma = 0;
13935     char number[5];
13936     char string[MSG_SIZ];       /* Space for game-list */
13937     int  i;
13938
13939     if (!cmailMsgLoaded) return "";
13940
13941     if (cmailMailedMove) {
13942       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13943     } else {
13944         /* Create a list of games left */
13945       snprintf(string, MSG_SIZ, "[");
13946         for (i = 0; i < nCmailGames; i ++) {
13947             if (! (   cmailMoveRegistered[i]
13948                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13949                 if (prependComma) {
13950                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13951                 } else {
13952                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13953                     prependComma = 1;
13954                 }
13955
13956                 strcat(string, number);
13957             }
13958         }
13959         strcat(string, "]");
13960
13961         if (nCmailMovesRegistered + nCmailResults == 0) {
13962             switch (nCmailGames) {
13963               case 1:
13964                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13965                 break;
13966
13967               case 2:
13968                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13969                 break;
13970
13971               default:
13972                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13973                          nCmailGames);
13974                 break;
13975             }
13976         } else {
13977             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13978               case 1:
13979                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13980                          string);
13981                 break;
13982
13983               case 0:
13984                 if (nCmailResults == nCmailGames) {
13985                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13986                 } else {
13987                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13988                 }
13989                 break;
13990
13991               default:
13992                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13993                          string);
13994             }
13995         }
13996     }
13997     return cmailMsg;
13998 #endif /* WIN32 */
13999 }
14000
14001 void
14002 ResetGameEvent ()
14003 {
14004     if (gameMode == Training)
14005       SetTrainingModeOff();
14006
14007     Reset(TRUE, TRUE);
14008     cmailMsgLoaded = FALSE;
14009     if (appData.icsActive) {
14010       SendToICS(ics_prefix);
14011       SendToICS("refresh\n");
14012     }
14013 }
14014
14015 void
14016 ExitEvent (int status)
14017 {
14018     exiting++;
14019     if (exiting > 2) {
14020       /* Give up on clean exit */
14021       exit(status);
14022     }
14023     if (exiting > 1) {
14024       /* Keep trying for clean exit */
14025       return;
14026     }
14027
14028     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14029     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14030
14031     if (telnetISR != NULL) {
14032       RemoveInputSource(telnetISR);
14033     }
14034     if (icsPR != NoProc) {
14035       DestroyChildProcess(icsPR, TRUE);
14036     }
14037
14038     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14039     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14040
14041     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14042     /* make sure this other one finishes before killing it!                  */
14043     if(endingGame) { int count = 0;
14044         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14045         while(endingGame && count++ < 10) DoSleep(1);
14046         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14047     }
14048
14049     /* Kill off chess programs */
14050     if (first.pr != NoProc) {
14051         ExitAnalyzeMode();
14052
14053         DoSleep( appData.delayBeforeQuit );
14054         SendToProgram("quit\n", &first);
14055         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14056     }
14057     if (second.pr != NoProc) {
14058         DoSleep( appData.delayBeforeQuit );
14059         SendToProgram("quit\n", &second);
14060         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14061     }
14062     if (first.isr != NULL) {
14063         RemoveInputSource(first.isr);
14064     }
14065     if (second.isr != NULL) {
14066         RemoveInputSource(second.isr);
14067     }
14068
14069     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14070     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14071
14072     ShutDownFrontEnd();
14073     exit(status);
14074 }
14075
14076 void
14077 PauseEngine (ChessProgramState *cps)
14078 {
14079     SendToProgram("pause\n", cps);
14080     cps->pause = 2;
14081 }
14082
14083 void
14084 UnPauseEngine (ChessProgramState *cps)
14085 {
14086     SendToProgram("resume\n", cps);
14087     cps->pause = 1;
14088 }
14089
14090 void
14091 PauseEvent ()
14092 {
14093     if (appData.debugMode)
14094         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14095     if (pausing) {
14096         pausing = FALSE;
14097         ModeHighlight();
14098         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14099             StartClocks();
14100             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14101                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14102                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14103             }
14104             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14105             HandleMachineMove(stashedInputMove, stalledEngine);
14106             stalledEngine = NULL;
14107             return;
14108         }
14109         if (gameMode == MachinePlaysWhite ||
14110             gameMode == TwoMachinesPlay   ||
14111             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14112             if(first.pause)  UnPauseEngine(&first);
14113             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14114             if(second.pause) UnPauseEngine(&second);
14115             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14116             StartClocks();
14117         } else {
14118             DisplayBothClocks();
14119         }
14120         if (gameMode == PlayFromGameFile) {
14121             if (appData.timeDelay >= 0)
14122                 AutoPlayGameLoop();
14123         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14124             Reset(FALSE, TRUE);
14125             SendToICS(ics_prefix);
14126             SendToICS("refresh\n");
14127         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14128             ForwardInner(forwardMostMove);
14129         }
14130         pauseExamInvalid = FALSE;
14131     } else {
14132         switch (gameMode) {
14133           default:
14134             return;
14135           case IcsExamining:
14136             pauseExamForwardMostMove = forwardMostMove;
14137             pauseExamInvalid = FALSE;
14138             /* fall through */
14139           case IcsObserving:
14140           case IcsPlayingWhite:
14141           case IcsPlayingBlack:
14142             pausing = TRUE;
14143             ModeHighlight();
14144             return;
14145           case PlayFromGameFile:
14146             (void) StopLoadGameTimer();
14147             pausing = TRUE;
14148             ModeHighlight();
14149             break;
14150           case BeginningOfGame:
14151             if (appData.icsActive) return;
14152             /* else fall through */
14153           case MachinePlaysWhite:
14154           case MachinePlaysBlack:
14155           case TwoMachinesPlay:
14156             if (forwardMostMove == 0)
14157               return;           /* don't pause if no one has moved */
14158             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14159                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14160                 if(onMove->pause) {           // thinking engine can be paused
14161                     PauseEngine(onMove);      // do it
14162                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14163                         PauseEngine(onMove->other);
14164                     else
14165                         SendToProgram("easy\n", onMove->other);
14166                     StopClocks();
14167                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14168             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14169                 if(first.pause) {
14170                     PauseEngine(&first);
14171                     StopClocks();
14172                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14173             } else { // human on move, pause pondering by either method
14174                 if(first.pause)
14175                     PauseEngine(&first);
14176                 else if(appData.ponderNextMove)
14177                     SendToProgram("easy\n", &first);
14178                 StopClocks();
14179             }
14180             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14181           case AnalyzeMode:
14182             pausing = TRUE;
14183             ModeHighlight();
14184             break;
14185         }
14186     }
14187 }
14188
14189 void
14190 EditCommentEvent ()
14191 {
14192     char title[MSG_SIZ];
14193
14194     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14195       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14196     } else {
14197       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14198                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14199                parseList[currentMove - 1]);
14200     }
14201
14202     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14203 }
14204
14205
14206 void
14207 EditTagsEvent ()
14208 {
14209     char *tags = PGNTags(&gameInfo);
14210     bookUp = FALSE;
14211     EditTagsPopUp(tags, NULL);
14212     free(tags);
14213 }
14214
14215 void
14216 ToggleSecond ()
14217 {
14218   if(second.analyzing) {
14219     SendToProgram("exit\n", &second);
14220     second.analyzing = FALSE;
14221   } else {
14222     if (second.pr == NoProc) StartChessProgram(&second);
14223     InitChessProgram(&second, FALSE);
14224     FeedMovesToProgram(&second, currentMove);
14225
14226     SendToProgram("analyze\n", &second);
14227     second.analyzing = TRUE;
14228   }
14229 }
14230
14231 /* Toggle ShowThinking */
14232 void
14233 ToggleShowThinking()
14234 {
14235   appData.showThinking = !appData.showThinking;
14236   ShowThinkingEvent();
14237 }
14238
14239 int
14240 AnalyzeModeEvent ()
14241 {
14242     char buf[MSG_SIZ];
14243
14244     if (!first.analysisSupport) {
14245       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14246       DisplayError(buf, 0);
14247       return 0;
14248     }
14249     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14250     if (appData.icsActive) {
14251         if (gameMode != IcsObserving) {
14252           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14253             DisplayError(buf, 0);
14254             /* secure check */
14255             if (appData.icsEngineAnalyze) {
14256                 if (appData.debugMode)
14257                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14258                 ExitAnalyzeMode();
14259                 ModeHighlight();
14260             }
14261             return 0;
14262         }
14263         /* if enable, user wants to disable icsEngineAnalyze */
14264         if (appData.icsEngineAnalyze) {
14265                 ExitAnalyzeMode();
14266                 ModeHighlight();
14267                 return 0;
14268         }
14269         appData.icsEngineAnalyze = TRUE;
14270         if (appData.debugMode)
14271             fprintf(debugFP, "ICS engine analyze starting... \n");
14272     }
14273
14274     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14275     if (appData.noChessProgram || gameMode == AnalyzeMode)
14276       return 0;
14277
14278     if (gameMode != AnalyzeFile) {
14279         if (!appData.icsEngineAnalyze) {
14280                EditGameEvent();
14281                if (gameMode != EditGame) return 0;
14282         }
14283         if (!appData.showThinking) ToggleShowThinking();
14284         ResurrectChessProgram();
14285         SendToProgram("analyze\n", &first);
14286         first.analyzing = TRUE;
14287         /*first.maybeThinking = TRUE;*/
14288         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14289         EngineOutputPopUp();
14290     }
14291     if (!appData.icsEngineAnalyze) {
14292         gameMode = AnalyzeMode;
14293         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14294     }
14295     pausing = FALSE;
14296     ModeHighlight();
14297     SetGameInfo();
14298
14299     StartAnalysisClock();
14300     GetTimeMark(&lastNodeCountTime);
14301     lastNodeCount = 0;
14302     return 1;
14303 }
14304
14305 void
14306 AnalyzeFileEvent ()
14307 {
14308     if (appData.noChessProgram || gameMode == AnalyzeFile)
14309       return;
14310
14311     if (!first.analysisSupport) {
14312       char buf[MSG_SIZ];
14313       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14314       DisplayError(buf, 0);
14315       return;
14316     }
14317
14318     if (gameMode != AnalyzeMode) {
14319         keepInfo = 1; // mere annotating should not alter PGN tags
14320         EditGameEvent();
14321         keepInfo = 0;
14322         if (gameMode != EditGame) return;
14323         if (!appData.showThinking) ToggleShowThinking();
14324         ResurrectChessProgram();
14325         SendToProgram("analyze\n", &first);
14326         first.analyzing = TRUE;
14327         /*first.maybeThinking = TRUE;*/
14328         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14329         EngineOutputPopUp();
14330     }
14331     gameMode = AnalyzeFile;
14332     pausing = FALSE;
14333     ModeHighlight();
14334
14335     StartAnalysisClock();
14336     GetTimeMark(&lastNodeCountTime);
14337     lastNodeCount = 0;
14338     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14339     AnalysisPeriodicEvent(1);
14340 }
14341
14342 void
14343 MachineWhiteEvent ()
14344 {
14345     char buf[MSG_SIZ];
14346     char *bookHit = NULL;
14347
14348     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14349       return;
14350
14351
14352     if (gameMode == PlayFromGameFile ||
14353         gameMode == TwoMachinesPlay  ||
14354         gameMode == Training         ||
14355         gameMode == AnalyzeMode      ||
14356         gameMode == EndOfGame)
14357         EditGameEvent();
14358
14359     if (gameMode == EditPosition)
14360         EditPositionDone(TRUE);
14361
14362     if (!WhiteOnMove(currentMove)) {
14363         DisplayError(_("It is not White's turn"), 0);
14364         return;
14365     }
14366
14367     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14368       ExitAnalyzeMode();
14369
14370     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14371         gameMode == AnalyzeFile)
14372         TruncateGame();
14373
14374     ResurrectChessProgram();    /* in case it isn't running */
14375     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14376         gameMode = MachinePlaysWhite;
14377         ResetClocks();
14378     } else
14379     gameMode = MachinePlaysWhite;
14380     pausing = FALSE;
14381     ModeHighlight();
14382     SetGameInfo();
14383     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14384     DisplayTitle(buf);
14385     if (first.sendName) {
14386       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14387       SendToProgram(buf, &first);
14388     }
14389     if (first.sendTime) {
14390       if (first.useColors) {
14391         SendToProgram("black\n", &first); /*gnu kludge*/
14392       }
14393       SendTimeRemaining(&first, TRUE);
14394     }
14395     if (first.useColors) {
14396       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14397     }
14398     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14399     SetMachineThinkingEnables();
14400     first.maybeThinking = TRUE;
14401     StartClocks();
14402     firstMove = FALSE;
14403
14404     if (appData.autoFlipView && !flipView) {
14405       flipView = !flipView;
14406       DrawPosition(FALSE, NULL);
14407       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14408     }
14409
14410     if(bookHit) { // [HGM] book: simulate book reply
14411         static char bookMove[MSG_SIZ]; // a bit generous?
14412
14413         programStats.nodes = programStats.depth = programStats.time =
14414         programStats.score = programStats.got_only_move = 0;
14415         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14416
14417         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14418         strcat(bookMove, bookHit);
14419         HandleMachineMove(bookMove, &first);
14420     }
14421 }
14422
14423 void
14424 MachineBlackEvent ()
14425 {
14426   char buf[MSG_SIZ];
14427   char *bookHit = NULL;
14428
14429     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14430         return;
14431
14432
14433     if (gameMode == PlayFromGameFile ||
14434         gameMode == TwoMachinesPlay  ||
14435         gameMode == Training         ||
14436         gameMode == AnalyzeMode      ||
14437         gameMode == EndOfGame)
14438         EditGameEvent();
14439
14440     if (gameMode == EditPosition)
14441         EditPositionDone(TRUE);
14442
14443     if (WhiteOnMove(currentMove)) {
14444         DisplayError(_("It is not Black's turn"), 0);
14445         return;
14446     }
14447
14448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14449       ExitAnalyzeMode();
14450
14451     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14452         gameMode == AnalyzeFile)
14453         TruncateGame();
14454
14455     ResurrectChessProgram();    /* in case it isn't running */
14456     gameMode = MachinePlaysBlack;
14457     pausing = FALSE;
14458     ModeHighlight();
14459     SetGameInfo();
14460     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14461     DisplayTitle(buf);
14462     if (first.sendName) {
14463       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14464       SendToProgram(buf, &first);
14465     }
14466     if (first.sendTime) {
14467       if (first.useColors) {
14468         SendToProgram("white\n", &first); /*gnu kludge*/
14469       }
14470       SendTimeRemaining(&first, FALSE);
14471     }
14472     if (first.useColors) {
14473       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14474     }
14475     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14476     SetMachineThinkingEnables();
14477     first.maybeThinking = TRUE;
14478     StartClocks();
14479
14480     if (appData.autoFlipView && flipView) {
14481       flipView = !flipView;
14482       DrawPosition(FALSE, NULL);
14483       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14484     }
14485     if(bookHit) { // [HGM] book: simulate book reply
14486         static char bookMove[MSG_SIZ]; // a bit generous?
14487
14488         programStats.nodes = programStats.depth = programStats.time =
14489         programStats.score = programStats.got_only_move = 0;
14490         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14491
14492         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14493         strcat(bookMove, bookHit);
14494         HandleMachineMove(bookMove, &first);
14495     }
14496 }
14497
14498
14499 void
14500 DisplayTwoMachinesTitle ()
14501 {
14502     char buf[MSG_SIZ];
14503     if (appData.matchGames > 0) {
14504         if(appData.tourneyFile[0]) {
14505           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14506                    gameInfo.white, _("vs."), gameInfo.black,
14507                    nextGame+1, appData.matchGames+1,
14508                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14509         } else
14510         if (first.twoMachinesColor[0] == 'w') {
14511           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14512                    gameInfo.white, _("vs."),  gameInfo.black,
14513                    first.matchWins, second.matchWins,
14514                    matchGame - 1 - (first.matchWins + second.matchWins));
14515         } else {
14516           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14517                    gameInfo.white, _("vs."), gameInfo.black,
14518                    second.matchWins, first.matchWins,
14519                    matchGame - 1 - (first.matchWins + second.matchWins));
14520         }
14521     } else {
14522       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14523     }
14524     DisplayTitle(buf);
14525 }
14526
14527 void
14528 SettingsMenuIfReady ()
14529 {
14530   if (second.lastPing != second.lastPong) {
14531     DisplayMessage("", _("Waiting for second chess program"));
14532     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14533     return;
14534   }
14535   ThawUI();
14536   DisplayMessage("", "");
14537   SettingsPopUp(&second);
14538 }
14539
14540 int
14541 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14542 {
14543     char buf[MSG_SIZ];
14544     if (cps->pr == NoProc) {
14545         StartChessProgram(cps);
14546         if (cps->protocolVersion == 1) {
14547           retry();
14548           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14549         } else {
14550           /* kludge: allow timeout for initial "feature" command */
14551           if(retry != TwoMachinesEventIfReady) FreezeUI();
14552           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14553           DisplayMessage("", buf);
14554           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14555         }
14556         return 1;
14557     }
14558     return 0;
14559 }
14560
14561 void
14562 TwoMachinesEvent P((void))
14563 {
14564     int i;
14565     char buf[MSG_SIZ];
14566     ChessProgramState *onmove;
14567     char *bookHit = NULL;
14568     static int stalling = 0;
14569     TimeMark now;
14570     long wait;
14571
14572     if (appData.noChessProgram) return;
14573
14574     switch (gameMode) {
14575       case TwoMachinesPlay:
14576         return;
14577       case MachinePlaysWhite:
14578       case MachinePlaysBlack:
14579         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14580             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14581             return;
14582         }
14583         /* fall through */
14584       case BeginningOfGame:
14585       case PlayFromGameFile:
14586       case EndOfGame:
14587         EditGameEvent();
14588         if (gameMode != EditGame) return;
14589         break;
14590       case EditPosition:
14591         EditPositionDone(TRUE);
14592         break;
14593       case AnalyzeMode:
14594       case AnalyzeFile:
14595         ExitAnalyzeMode();
14596         break;
14597       case EditGame:
14598       default:
14599         break;
14600     }
14601
14602 //    forwardMostMove = currentMove;
14603     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14604     startingEngine = TRUE;
14605
14606     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14607
14608     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14609     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14610       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14611       return;
14612     }
14613     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14614
14615     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14616                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14617         startingEngine = FALSE;
14618         DisplayError("second engine does not play this", 0);
14619         return;
14620     }
14621
14622     if(!stalling) {
14623       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14624       SendToProgram("force\n", &second);
14625       stalling = 1;
14626       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14627       return;
14628     }
14629     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14630     if(appData.matchPause>10000 || appData.matchPause<10)
14631                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14632     wait = SubtractTimeMarks(&now, &pauseStart);
14633     if(wait < appData.matchPause) {
14634         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14635         return;
14636     }
14637     // we are now committed to starting the game
14638     stalling = 0;
14639     DisplayMessage("", "");
14640     if (startedFromSetupPosition) {
14641         SendBoard(&second, backwardMostMove);
14642     if (appData.debugMode) {
14643         fprintf(debugFP, "Two Machines\n");
14644     }
14645     }
14646     for (i = backwardMostMove; i < forwardMostMove; i++) {
14647         SendMoveToProgram(i, &second);
14648     }
14649
14650     gameMode = TwoMachinesPlay;
14651     pausing = startingEngine = FALSE;
14652     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14653     SetGameInfo();
14654     DisplayTwoMachinesTitle();
14655     firstMove = TRUE;
14656     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14657         onmove = &first;
14658     } else {
14659         onmove = &second;
14660     }
14661     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14662     SendToProgram(first.computerString, &first);
14663     if (first.sendName) {
14664       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14665       SendToProgram(buf, &first);
14666     }
14667     SendToProgram(second.computerString, &second);
14668     if (second.sendName) {
14669       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14670       SendToProgram(buf, &second);
14671     }
14672
14673     ResetClocks();
14674     if (!first.sendTime || !second.sendTime) {
14675         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14676         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14677     }
14678     if (onmove->sendTime) {
14679       if (onmove->useColors) {
14680         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14681       }
14682       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14683     }
14684     if (onmove->useColors) {
14685       SendToProgram(onmove->twoMachinesColor, onmove);
14686     }
14687     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14688 //    SendToProgram("go\n", onmove);
14689     onmove->maybeThinking = TRUE;
14690     SetMachineThinkingEnables();
14691
14692     StartClocks();
14693
14694     if(bookHit) { // [HGM] book: simulate book reply
14695         static char bookMove[MSG_SIZ]; // a bit generous?
14696
14697         programStats.nodes = programStats.depth = programStats.time =
14698         programStats.score = programStats.got_only_move = 0;
14699         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14700
14701         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14702         strcat(bookMove, bookHit);
14703         savedMessage = bookMove; // args for deferred call
14704         savedState = onmove;
14705         ScheduleDelayedEvent(DeferredBookMove, 1);
14706     }
14707 }
14708
14709 void
14710 TrainingEvent ()
14711 {
14712     if (gameMode == Training) {
14713       SetTrainingModeOff();
14714       gameMode = PlayFromGameFile;
14715       DisplayMessage("", _("Training mode off"));
14716     } else {
14717       gameMode = Training;
14718       animateTraining = appData.animate;
14719
14720       /* make sure we are not already at the end of the game */
14721       if (currentMove < forwardMostMove) {
14722         SetTrainingModeOn();
14723         DisplayMessage("", _("Training mode on"));
14724       } else {
14725         gameMode = PlayFromGameFile;
14726         DisplayError(_("Already at end of game"), 0);
14727       }
14728     }
14729     ModeHighlight();
14730 }
14731
14732 void
14733 IcsClientEvent ()
14734 {
14735     if (!appData.icsActive) return;
14736     switch (gameMode) {
14737       case IcsPlayingWhite:
14738       case IcsPlayingBlack:
14739       case IcsObserving:
14740       case IcsIdle:
14741       case BeginningOfGame:
14742       case IcsExamining:
14743         return;
14744
14745       case EditGame:
14746         break;
14747
14748       case EditPosition:
14749         EditPositionDone(TRUE);
14750         break;
14751
14752       case AnalyzeMode:
14753       case AnalyzeFile:
14754         ExitAnalyzeMode();
14755         break;
14756
14757       default:
14758         EditGameEvent();
14759         break;
14760     }
14761
14762     gameMode = IcsIdle;
14763     ModeHighlight();
14764     return;
14765 }
14766
14767 void
14768 EditGameEvent ()
14769 {
14770     int i;
14771
14772     switch (gameMode) {
14773       case Training:
14774         SetTrainingModeOff();
14775         break;
14776       case MachinePlaysWhite:
14777       case MachinePlaysBlack:
14778       case BeginningOfGame:
14779         SendToProgram("force\n", &first);
14780         SetUserThinkingEnables();
14781         break;
14782       case PlayFromGameFile:
14783         (void) StopLoadGameTimer();
14784         if (gameFileFP != NULL) {
14785             gameFileFP = NULL;
14786         }
14787         break;
14788       case EditPosition:
14789         EditPositionDone(TRUE);
14790         break;
14791       case AnalyzeMode:
14792       case AnalyzeFile:
14793         ExitAnalyzeMode();
14794         SendToProgram("force\n", &first);
14795         break;
14796       case TwoMachinesPlay:
14797         GameEnds(EndOfFile, NULL, GE_PLAYER);
14798         ResurrectChessProgram();
14799         SetUserThinkingEnables();
14800         break;
14801       case EndOfGame:
14802         ResurrectChessProgram();
14803         break;
14804       case IcsPlayingBlack:
14805       case IcsPlayingWhite:
14806         DisplayError(_("Warning: You are still playing a game"), 0);
14807         break;
14808       case IcsObserving:
14809         DisplayError(_("Warning: You are still observing a game"), 0);
14810         break;
14811       case IcsExamining:
14812         DisplayError(_("Warning: You are still examining a game"), 0);
14813         break;
14814       case IcsIdle:
14815         break;
14816       case EditGame:
14817       default:
14818         return;
14819     }
14820
14821     pausing = FALSE;
14822     StopClocks();
14823     first.offeredDraw = second.offeredDraw = 0;
14824
14825     if (gameMode == PlayFromGameFile) {
14826         whiteTimeRemaining = timeRemaining[0][currentMove];
14827         blackTimeRemaining = timeRemaining[1][currentMove];
14828         DisplayTitle("");
14829     }
14830
14831     if (gameMode == MachinePlaysWhite ||
14832         gameMode == MachinePlaysBlack ||
14833         gameMode == TwoMachinesPlay ||
14834         gameMode == EndOfGame) {
14835         i = forwardMostMove;
14836         while (i > currentMove) {
14837             SendToProgram("undo\n", &first);
14838             i--;
14839         }
14840         if(!adjustedClock) {
14841         whiteTimeRemaining = timeRemaining[0][currentMove];
14842         blackTimeRemaining = timeRemaining[1][currentMove];
14843         DisplayBothClocks();
14844         }
14845         if (whiteFlag || blackFlag) {
14846             whiteFlag = blackFlag = 0;
14847         }
14848         DisplayTitle("");
14849     }
14850
14851     gameMode = EditGame;
14852     ModeHighlight();
14853     SetGameInfo();
14854 }
14855
14856
14857 void
14858 EditPositionEvent ()
14859 {
14860     if (gameMode == EditPosition) {
14861         EditGameEvent();
14862         return;
14863     }
14864
14865     EditGameEvent();
14866     if (gameMode != EditGame) return;
14867
14868     gameMode = EditPosition;
14869     ModeHighlight();
14870     SetGameInfo();
14871     if (currentMove > 0)
14872       CopyBoard(boards[0], boards[currentMove]);
14873
14874     blackPlaysFirst = !WhiteOnMove(currentMove);
14875     ResetClocks();
14876     currentMove = forwardMostMove = backwardMostMove = 0;
14877     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14878     DisplayMove(-1);
14879     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14880 }
14881
14882 void
14883 ExitAnalyzeMode ()
14884 {
14885     /* [DM] icsEngineAnalyze - possible call from other functions */
14886     if (appData.icsEngineAnalyze) {
14887         appData.icsEngineAnalyze = FALSE;
14888
14889         DisplayMessage("",_("Close ICS engine analyze..."));
14890     }
14891     if (first.analysisSupport && first.analyzing) {
14892       SendToBoth("exit\n");
14893       first.analyzing = second.analyzing = FALSE;
14894     }
14895     thinkOutput[0] = NULLCHAR;
14896 }
14897
14898 void
14899 EditPositionDone (Boolean fakeRights)
14900 {
14901     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14902
14903     startedFromSetupPosition = TRUE;
14904     InitChessProgram(&first, FALSE);
14905     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14906       boards[0][EP_STATUS] = EP_NONE;
14907       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14908       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14909         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14910         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14911       } else boards[0][CASTLING][2] = NoRights;
14912       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14913         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14914         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14915       } else boards[0][CASTLING][5] = NoRights;
14916       if(gameInfo.variant == VariantSChess) {
14917         int i;
14918         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14919           boards[0][VIRGIN][i] = 0;
14920           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14921           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14922         }
14923       }
14924     }
14925     SendToProgram("force\n", &first);
14926     if (blackPlaysFirst) {
14927         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14928         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14929         currentMove = forwardMostMove = backwardMostMove = 1;
14930         CopyBoard(boards[1], boards[0]);
14931     } else {
14932         currentMove = forwardMostMove = backwardMostMove = 0;
14933     }
14934     SendBoard(&first, forwardMostMove);
14935     if (appData.debugMode) {
14936         fprintf(debugFP, "EditPosDone\n");
14937     }
14938     DisplayTitle("");
14939     DisplayMessage("", "");
14940     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14941     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14942     gameMode = EditGame;
14943     ModeHighlight();
14944     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14945     ClearHighlights(); /* [AS] */
14946 }
14947
14948 /* Pause for `ms' milliseconds */
14949 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14950 void
14951 TimeDelay (long ms)
14952 {
14953     TimeMark m1, m2;
14954
14955     GetTimeMark(&m1);
14956     do {
14957         GetTimeMark(&m2);
14958     } while (SubtractTimeMarks(&m2, &m1) < ms);
14959 }
14960
14961 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14962 void
14963 SendMultiLineToICS (char *buf)
14964 {
14965     char temp[MSG_SIZ+1], *p;
14966     int len;
14967
14968     len = strlen(buf);
14969     if (len > MSG_SIZ)
14970       len = MSG_SIZ;
14971
14972     strncpy(temp, buf, len);
14973     temp[len] = 0;
14974
14975     p = temp;
14976     while (*p) {
14977         if (*p == '\n' || *p == '\r')
14978           *p = ' ';
14979         ++p;
14980     }
14981
14982     strcat(temp, "\n");
14983     SendToICS(temp);
14984     SendToPlayer(temp, strlen(temp));
14985 }
14986
14987 void
14988 SetWhiteToPlayEvent ()
14989 {
14990     if (gameMode == EditPosition) {
14991         blackPlaysFirst = FALSE;
14992         DisplayBothClocks();    /* works because currentMove is 0 */
14993     } else if (gameMode == IcsExamining) {
14994         SendToICS(ics_prefix);
14995         SendToICS("tomove white\n");
14996     }
14997 }
14998
14999 void
15000 SetBlackToPlayEvent ()
15001 {
15002     if (gameMode == EditPosition) {
15003         blackPlaysFirst = TRUE;
15004         currentMove = 1;        /* kludge */
15005         DisplayBothClocks();
15006         currentMove = 0;
15007     } else if (gameMode == IcsExamining) {
15008         SendToICS(ics_prefix);
15009         SendToICS("tomove black\n");
15010     }
15011 }
15012
15013 void
15014 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15015 {
15016     char buf[MSG_SIZ];
15017     ChessSquare piece = boards[0][y][x];
15018     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15019     static int lastVariant;
15020
15021     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15022
15023     switch (selection) {
15024       case ClearBoard:
15025         CopyBoard(currentBoard, boards[0]);
15026         CopyBoard(menuBoard, initialPosition);
15027         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15028             SendToICS(ics_prefix);
15029             SendToICS("bsetup clear\n");
15030         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15031             SendToICS(ics_prefix);
15032             SendToICS("clearboard\n");
15033         } else {
15034             int nonEmpty = 0;
15035             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15036                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15037                 for (y = 0; y < BOARD_HEIGHT; y++) {
15038                     if (gameMode == IcsExamining) {
15039                         if (boards[currentMove][y][x] != EmptySquare) {
15040                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15041                                     AAA + x, ONE + y);
15042                             SendToICS(buf);
15043                         }
15044                     } else {
15045                         if(boards[0][y][x] != p) nonEmpty++;
15046                         boards[0][y][x] = p;
15047                     }
15048                 }
15049             }
15050             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15051                 int r;
15052                 for(r = 0; r < BOARD_HEIGHT; r++) {
15053                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15054                     ChessSquare p = menuBoard[r][x];
15055                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15056                   }
15057                 }
15058                 DisplayMessage("Clicking clock again restores position", "");
15059                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15060                 if(!nonEmpty) { // asked to clear an empty board
15061                     CopyBoard(boards[0], menuBoard);
15062                 } else
15063                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15064                     CopyBoard(boards[0], initialPosition);
15065                 } else
15066                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15067                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15068                     CopyBoard(boards[0], erasedBoard);
15069                 } else
15070                     CopyBoard(erasedBoard, currentBoard);
15071
15072             }
15073         }
15074         if (gameMode == EditPosition) {
15075             DrawPosition(FALSE, boards[0]);
15076         }
15077         break;
15078
15079       case WhitePlay:
15080         SetWhiteToPlayEvent();
15081         break;
15082
15083       case BlackPlay:
15084         SetBlackToPlayEvent();
15085         break;
15086
15087       case EmptySquare:
15088         if (gameMode == IcsExamining) {
15089             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15090             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15091             SendToICS(buf);
15092         } else {
15093             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15094                 if(x == BOARD_LEFT-2) {
15095                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15096                     boards[0][y][1] = 0;
15097                 } else
15098                 if(x == BOARD_RGHT+1) {
15099                     if(y >= gameInfo.holdingsSize) break;
15100                     boards[0][y][BOARD_WIDTH-2] = 0;
15101                 } else break;
15102             }
15103             boards[0][y][x] = EmptySquare;
15104             DrawPosition(FALSE, boards[0]);
15105         }
15106         break;
15107
15108       case PromotePiece:
15109         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15110            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15111             selection = (ChessSquare) (PROMOTED piece);
15112         } else if(piece == EmptySquare) selection = WhiteSilver;
15113         else selection = (ChessSquare)((int)piece - 1);
15114         goto defaultlabel;
15115
15116       case DemotePiece:
15117         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15118            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15119             selection = (ChessSquare) (DEMOTED piece);
15120         } else if(piece == EmptySquare) selection = BlackSilver;
15121         else selection = (ChessSquare)((int)piece + 1);
15122         goto defaultlabel;
15123
15124       case WhiteQueen:
15125       case BlackQueen:
15126         if(gameInfo.variant == VariantShatranj ||
15127            gameInfo.variant == VariantXiangqi  ||
15128            gameInfo.variant == VariantCourier  ||
15129            gameInfo.variant == VariantASEAN    ||
15130            gameInfo.variant == VariantMakruk     )
15131             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15132         goto defaultlabel;
15133
15134       case WhiteKing:
15135       case BlackKing:
15136         if(gameInfo.variant == VariantXiangqi)
15137             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15138         if(gameInfo.variant == VariantKnightmate)
15139             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15140       default:
15141         defaultlabel:
15142         if (gameMode == IcsExamining) {
15143             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15144             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15145                      PieceToChar(selection), AAA + x, ONE + y);
15146             SendToICS(buf);
15147         } else {
15148             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15149                 int n;
15150                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15151                     n = PieceToNumber(selection - BlackPawn);
15152                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15153                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15154                     boards[0][BOARD_HEIGHT-1-n][1]++;
15155                 } else
15156                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15157                     n = PieceToNumber(selection);
15158                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15159                     boards[0][n][BOARD_WIDTH-1] = selection;
15160                     boards[0][n][BOARD_WIDTH-2]++;
15161                 }
15162             } else
15163             boards[0][y][x] = selection;
15164             DrawPosition(TRUE, boards[0]);
15165             ClearHighlights();
15166             fromX = fromY = -1;
15167         }
15168         break;
15169     }
15170 }
15171
15172
15173 void
15174 DropMenuEvent (ChessSquare selection, int x, int y)
15175 {
15176     ChessMove moveType;
15177
15178     switch (gameMode) {
15179       case IcsPlayingWhite:
15180       case MachinePlaysBlack:
15181         if (!WhiteOnMove(currentMove)) {
15182             DisplayMoveError(_("It is Black's turn"));
15183             return;
15184         }
15185         moveType = WhiteDrop;
15186         break;
15187       case IcsPlayingBlack:
15188       case MachinePlaysWhite:
15189         if (WhiteOnMove(currentMove)) {
15190             DisplayMoveError(_("It is White's turn"));
15191             return;
15192         }
15193         moveType = BlackDrop;
15194         break;
15195       case EditGame:
15196         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15197         break;
15198       default:
15199         return;
15200     }
15201
15202     if (moveType == BlackDrop && selection < BlackPawn) {
15203       selection = (ChessSquare) ((int) selection
15204                                  + (int) BlackPawn - (int) WhitePawn);
15205     }
15206     if (boards[currentMove][y][x] != EmptySquare) {
15207         DisplayMoveError(_("That square is occupied"));
15208         return;
15209     }
15210
15211     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15212 }
15213
15214 void
15215 AcceptEvent ()
15216 {
15217     /* Accept a pending offer of any kind from opponent */
15218
15219     if (appData.icsActive) {
15220         SendToICS(ics_prefix);
15221         SendToICS("accept\n");
15222     } else if (cmailMsgLoaded) {
15223         if (currentMove == cmailOldMove &&
15224             commentList[cmailOldMove] != NULL &&
15225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15226                    "Black offers a draw" : "White offers a draw")) {
15227             TruncateGame();
15228             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15229             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15230         } else {
15231             DisplayError(_("There is no pending offer on this move"), 0);
15232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15233         }
15234     } else {
15235         /* Not used for offers from chess program */
15236     }
15237 }
15238
15239 void
15240 DeclineEvent ()
15241 {
15242     /* Decline a pending offer of any kind from opponent */
15243
15244     if (appData.icsActive) {
15245         SendToICS(ics_prefix);
15246         SendToICS("decline\n");
15247     } else if (cmailMsgLoaded) {
15248         if (currentMove == cmailOldMove &&
15249             commentList[cmailOldMove] != NULL &&
15250             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15251                    "Black offers a draw" : "White offers a draw")) {
15252 #ifdef NOTDEF
15253             AppendComment(cmailOldMove, "Draw declined", TRUE);
15254             DisplayComment(cmailOldMove - 1, "Draw declined");
15255 #endif /*NOTDEF*/
15256         } else {
15257             DisplayError(_("There is no pending offer on this move"), 0);
15258         }
15259     } else {
15260         /* Not used for offers from chess program */
15261     }
15262 }
15263
15264 void
15265 RematchEvent ()
15266 {
15267     /* Issue ICS rematch command */
15268     if (appData.icsActive) {
15269         SendToICS(ics_prefix);
15270         SendToICS("rematch\n");
15271     }
15272 }
15273
15274 void
15275 CallFlagEvent ()
15276 {
15277     /* Call your opponent's flag (claim a win on time) */
15278     if (appData.icsActive) {
15279         SendToICS(ics_prefix);
15280         SendToICS("flag\n");
15281     } else {
15282         switch (gameMode) {
15283           default:
15284             return;
15285           case MachinePlaysWhite:
15286             if (whiteFlag) {
15287                 if (blackFlag)
15288                   GameEnds(GameIsDrawn, "Both players ran out of time",
15289                            GE_PLAYER);
15290                 else
15291                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15292             } else {
15293                 DisplayError(_("Your opponent is not out of time"), 0);
15294             }
15295             break;
15296           case MachinePlaysBlack:
15297             if (blackFlag) {
15298                 if (whiteFlag)
15299                   GameEnds(GameIsDrawn, "Both players ran out of time",
15300                            GE_PLAYER);
15301                 else
15302                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15303             } else {
15304                 DisplayError(_("Your opponent is not out of time"), 0);
15305             }
15306             break;
15307         }
15308     }
15309 }
15310
15311 void
15312 ClockClick (int which)
15313 {       // [HGM] code moved to back-end from winboard.c
15314         if(which) { // black clock
15315           if (gameMode == EditPosition || gameMode == IcsExamining) {
15316             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15317             SetBlackToPlayEvent();
15318           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15319                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15320           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15321           } else if (shiftKey) {
15322             AdjustClock(which, -1);
15323           } else if (gameMode == IcsPlayingWhite ||
15324                      gameMode == MachinePlaysBlack) {
15325             CallFlagEvent();
15326           }
15327         } else { // white clock
15328           if (gameMode == EditPosition || gameMode == IcsExamining) {
15329             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15330             SetWhiteToPlayEvent();
15331           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15332                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15333           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15334           } else if (shiftKey) {
15335             AdjustClock(which, -1);
15336           } else if (gameMode == IcsPlayingBlack ||
15337                    gameMode == MachinePlaysWhite) {
15338             CallFlagEvent();
15339           }
15340         }
15341 }
15342
15343 void
15344 DrawEvent ()
15345 {
15346     /* Offer draw or accept pending draw offer from opponent */
15347
15348     if (appData.icsActive) {
15349         /* Note: tournament rules require draw offers to be
15350            made after you make your move but before you punch
15351            your clock.  Currently ICS doesn't let you do that;
15352            instead, you immediately punch your clock after making
15353            a move, but you can offer a draw at any time. */
15354
15355         SendToICS(ics_prefix);
15356         SendToICS("draw\n");
15357         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15358     } else if (cmailMsgLoaded) {
15359         if (currentMove == cmailOldMove &&
15360             commentList[cmailOldMove] != NULL &&
15361             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15362                    "Black offers a draw" : "White offers a draw")) {
15363             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15364             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15365         } else if (currentMove == cmailOldMove + 1) {
15366             char *offer = WhiteOnMove(cmailOldMove) ?
15367               "White offers a draw" : "Black offers a draw";
15368             AppendComment(currentMove, offer, TRUE);
15369             DisplayComment(currentMove - 1, offer);
15370             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15371         } else {
15372             DisplayError(_("You must make your move before offering a draw"), 0);
15373             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15374         }
15375     } else if (first.offeredDraw) {
15376         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15377     } else {
15378         if (first.sendDrawOffers) {
15379             SendToProgram("draw\n", &first);
15380             userOfferedDraw = TRUE;
15381         }
15382     }
15383 }
15384
15385 void
15386 AdjournEvent ()
15387 {
15388     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15389
15390     if (appData.icsActive) {
15391         SendToICS(ics_prefix);
15392         SendToICS("adjourn\n");
15393     } else {
15394         /* Currently GNU Chess doesn't offer or accept Adjourns */
15395     }
15396 }
15397
15398
15399 void
15400 AbortEvent ()
15401 {
15402     /* Offer Abort or accept pending Abort offer from opponent */
15403
15404     if (appData.icsActive) {
15405         SendToICS(ics_prefix);
15406         SendToICS("abort\n");
15407     } else {
15408         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15409     }
15410 }
15411
15412 void
15413 ResignEvent ()
15414 {
15415     /* Resign.  You can do this even if it's not your turn. */
15416
15417     if (appData.icsActive) {
15418         SendToICS(ics_prefix);
15419         SendToICS("resign\n");
15420     } else {
15421         switch (gameMode) {
15422           case MachinePlaysWhite:
15423             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15424             break;
15425           case MachinePlaysBlack:
15426             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15427             break;
15428           case EditGame:
15429             if (cmailMsgLoaded) {
15430                 TruncateGame();
15431                 if (WhiteOnMove(cmailOldMove)) {
15432                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15433                 } else {
15434                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15435                 }
15436                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15437             }
15438             break;
15439           default:
15440             break;
15441         }
15442     }
15443 }
15444
15445
15446 void
15447 StopObservingEvent ()
15448 {
15449     /* Stop observing current games */
15450     SendToICS(ics_prefix);
15451     SendToICS("unobserve\n");
15452 }
15453
15454 void
15455 StopExaminingEvent ()
15456 {
15457     /* Stop observing current game */
15458     SendToICS(ics_prefix);
15459     SendToICS("unexamine\n");
15460 }
15461
15462 void
15463 ForwardInner (int target)
15464 {
15465     int limit; int oldSeekGraphUp = seekGraphUp;
15466
15467     if (appData.debugMode)
15468         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15469                 target, currentMove, forwardMostMove);
15470
15471     if (gameMode == EditPosition)
15472       return;
15473
15474     seekGraphUp = FALSE;
15475     MarkTargetSquares(1);
15476
15477     if (gameMode == PlayFromGameFile && !pausing)
15478       PauseEvent();
15479
15480     if (gameMode == IcsExamining && pausing)
15481       limit = pauseExamForwardMostMove;
15482     else
15483       limit = forwardMostMove;
15484
15485     if (target > limit) target = limit;
15486
15487     if (target > 0 && moveList[target - 1][0]) {
15488         int fromX, fromY, toX, toY;
15489         toX = moveList[target - 1][2] - AAA;
15490         toY = moveList[target - 1][3] - ONE;
15491         if (moveList[target - 1][1] == '@') {
15492             if (appData.highlightLastMove) {
15493                 SetHighlights(-1, -1, toX, toY);
15494             }
15495         } else {
15496             int viaX = moveList[target - 1][5] - AAA;
15497             int viaY = moveList[target - 1][6] - ONE;
15498             fromX = moveList[target - 1][0] - AAA;
15499             fromY = moveList[target - 1][1] - ONE;
15500             if (target == currentMove + 1) {
15501                 if(moveList[target - 1][4] == ';') { // multi-leg
15502                     ChessSquare piece = boards[currentMove][viaY][viaX];
15503                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15504                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15505                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15506                     boards[currentMove][viaY][viaX] = piece;
15507                 } else
15508                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15509             }
15510             if (appData.highlightLastMove) {
15511                 SetHighlights(fromX, fromY, toX, toY);
15512             }
15513         }
15514     }
15515     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15516         gameMode == Training || gameMode == PlayFromGameFile ||
15517         gameMode == AnalyzeFile) {
15518         while (currentMove < target) {
15519             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15520             SendMoveToProgram(currentMove++, &first);
15521         }
15522     } else {
15523         currentMove = target;
15524     }
15525
15526     if (gameMode == EditGame || gameMode == EndOfGame) {
15527         whiteTimeRemaining = timeRemaining[0][currentMove];
15528         blackTimeRemaining = timeRemaining[1][currentMove];
15529     }
15530     DisplayBothClocks();
15531     DisplayMove(currentMove - 1);
15532     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15533     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15534     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15535         DisplayComment(currentMove - 1, commentList[currentMove]);
15536     }
15537     ClearMap(); // [HGM] exclude: invalidate map
15538 }
15539
15540
15541 void
15542 ForwardEvent ()
15543 {
15544     if (gameMode == IcsExamining && !pausing) {
15545         SendToICS(ics_prefix);
15546         SendToICS("forward\n");
15547     } else {
15548         ForwardInner(currentMove + 1);
15549     }
15550 }
15551
15552 void
15553 ToEndEvent ()
15554 {
15555     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15556         /* to optimze, we temporarily turn off analysis mode while we feed
15557          * the remaining moves to the engine. Otherwise we get analysis output
15558          * after each move.
15559          */
15560         if (first.analysisSupport) {
15561           SendToProgram("exit\nforce\n", &first);
15562           first.analyzing = FALSE;
15563         }
15564     }
15565
15566     if (gameMode == IcsExamining && !pausing) {
15567         SendToICS(ics_prefix);
15568         SendToICS("forward 999999\n");
15569     } else {
15570         ForwardInner(forwardMostMove);
15571     }
15572
15573     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15574         /* we have fed all the moves, so reactivate analysis mode */
15575         SendToProgram("analyze\n", &first);
15576         first.analyzing = TRUE;
15577         /*first.maybeThinking = TRUE;*/
15578         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15579     }
15580 }
15581
15582 void
15583 BackwardInner (int target)
15584 {
15585     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15586
15587     if (appData.debugMode)
15588         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15589                 target, currentMove, forwardMostMove);
15590
15591     if (gameMode == EditPosition) return;
15592     seekGraphUp = FALSE;
15593     MarkTargetSquares(1);
15594     if (currentMove <= backwardMostMove) {
15595         ClearHighlights();
15596         DrawPosition(full_redraw, boards[currentMove]);
15597         return;
15598     }
15599     if (gameMode == PlayFromGameFile && !pausing)
15600       PauseEvent();
15601
15602     if (moveList[target][0]) {
15603         int fromX, fromY, toX, toY;
15604         toX = moveList[target][2] - AAA;
15605         toY = moveList[target][3] - ONE;
15606         if (moveList[target][1] == '@') {
15607             if (appData.highlightLastMove) {
15608                 SetHighlights(-1, -1, toX, toY);
15609             }
15610         } else {
15611             fromX = moveList[target][0] - AAA;
15612             fromY = moveList[target][1] - ONE;
15613             if (target == currentMove - 1) {
15614                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15615             }
15616             if (appData.highlightLastMove) {
15617                 SetHighlights(fromX, fromY, toX, toY);
15618             }
15619         }
15620     }
15621     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15622         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15623         while (currentMove > target) {
15624             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15625                 // null move cannot be undone. Reload program with move history before it.
15626                 int i;
15627                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15628                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15629                 }
15630                 SendBoard(&first, i);
15631               if(second.analyzing) SendBoard(&second, i);
15632                 for(currentMove=i; currentMove<target; currentMove++) {
15633                     SendMoveToProgram(currentMove, &first);
15634                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15635                 }
15636                 break;
15637             }
15638             SendToBoth("undo\n");
15639             currentMove--;
15640         }
15641     } else {
15642         currentMove = target;
15643     }
15644
15645     if (gameMode == EditGame || gameMode == EndOfGame) {
15646         whiteTimeRemaining = timeRemaining[0][currentMove];
15647         blackTimeRemaining = timeRemaining[1][currentMove];
15648     }
15649     DisplayBothClocks();
15650     DisplayMove(currentMove - 1);
15651     DrawPosition(full_redraw, boards[currentMove]);
15652     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15653     // [HGM] PV info: routine tests if comment empty
15654     DisplayComment(currentMove - 1, commentList[currentMove]);
15655     ClearMap(); // [HGM] exclude: invalidate map
15656 }
15657
15658 void
15659 BackwardEvent ()
15660 {
15661     if (gameMode == IcsExamining && !pausing) {
15662         SendToICS(ics_prefix);
15663         SendToICS("backward\n");
15664     } else {
15665         BackwardInner(currentMove - 1);
15666     }
15667 }
15668
15669 void
15670 ToStartEvent ()
15671 {
15672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15673         /* to optimize, we temporarily turn off analysis mode while we undo
15674          * all the moves. Otherwise we get analysis output after each undo.
15675          */
15676         if (first.analysisSupport) {
15677           SendToProgram("exit\nforce\n", &first);
15678           first.analyzing = FALSE;
15679         }
15680     }
15681
15682     if (gameMode == IcsExamining && !pausing) {
15683         SendToICS(ics_prefix);
15684         SendToICS("backward 999999\n");
15685     } else {
15686         BackwardInner(backwardMostMove);
15687     }
15688
15689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15690         /* we have fed all the moves, so reactivate analysis mode */
15691         SendToProgram("analyze\n", &first);
15692         first.analyzing = TRUE;
15693         /*first.maybeThinking = TRUE;*/
15694         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15695     }
15696 }
15697
15698 void
15699 ToNrEvent (int to)
15700 {
15701   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15702   if (to >= forwardMostMove) to = forwardMostMove;
15703   if (to <= backwardMostMove) to = backwardMostMove;
15704   if (to < currentMove) {
15705     BackwardInner(to);
15706   } else {
15707     ForwardInner(to);
15708   }
15709 }
15710
15711 void
15712 RevertEvent (Boolean annotate)
15713 {
15714     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15715         return;
15716     }
15717     if (gameMode != IcsExamining) {
15718         DisplayError(_("You are not examining a game"), 0);
15719         return;
15720     }
15721     if (pausing) {
15722         DisplayError(_("You can't revert while pausing"), 0);
15723         return;
15724     }
15725     SendToICS(ics_prefix);
15726     SendToICS("revert\n");
15727 }
15728
15729 void
15730 RetractMoveEvent ()
15731 {
15732     switch (gameMode) {
15733       case MachinePlaysWhite:
15734       case MachinePlaysBlack:
15735         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15736             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15737             return;
15738         }
15739         if (forwardMostMove < 2) return;
15740         currentMove = forwardMostMove = forwardMostMove - 2;
15741         whiteTimeRemaining = timeRemaining[0][currentMove];
15742         blackTimeRemaining = timeRemaining[1][currentMove];
15743         DisplayBothClocks();
15744         DisplayMove(currentMove - 1);
15745         ClearHighlights();/*!! could figure this out*/
15746         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15747         SendToProgram("remove\n", &first);
15748         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15749         break;
15750
15751       case BeginningOfGame:
15752       default:
15753         break;
15754
15755       case IcsPlayingWhite:
15756       case IcsPlayingBlack:
15757         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15758             SendToICS(ics_prefix);
15759             SendToICS("takeback 2\n");
15760         } else {
15761             SendToICS(ics_prefix);
15762             SendToICS("takeback 1\n");
15763         }
15764         break;
15765     }
15766 }
15767
15768 void
15769 MoveNowEvent ()
15770 {
15771     ChessProgramState *cps;
15772
15773     switch (gameMode) {
15774       case MachinePlaysWhite:
15775         if (!WhiteOnMove(forwardMostMove)) {
15776             DisplayError(_("It is your turn"), 0);
15777             return;
15778         }
15779         cps = &first;
15780         break;
15781       case MachinePlaysBlack:
15782         if (WhiteOnMove(forwardMostMove)) {
15783             DisplayError(_("It is your turn"), 0);
15784             return;
15785         }
15786         cps = &first;
15787         break;
15788       case TwoMachinesPlay:
15789         if (WhiteOnMove(forwardMostMove) ==
15790             (first.twoMachinesColor[0] == 'w')) {
15791             cps = &first;
15792         } else {
15793             cps = &second;
15794         }
15795         break;
15796       case BeginningOfGame:
15797       default:
15798         return;
15799     }
15800     SendToProgram("?\n", cps);
15801 }
15802
15803 void
15804 TruncateGameEvent ()
15805 {
15806     EditGameEvent();
15807     if (gameMode != EditGame) return;
15808     TruncateGame();
15809 }
15810
15811 void
15812 TruncateGame ()
15813 {
15814     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15815     if (forwardMostMove > currentMove) {
15816         if (gameInfo.resultDetails != NULL) {
15817             free(gameInfo.resultDetails);
15818             gameInfo.resultDetails = NULL;
15819             gameInfo.result = GameUnfinished;
15820         }
15821         forwardMostMove = currentMove;
15822         HistorySet(parseList, backwardMostMove, forwardMostMove,
15823                    currentMove-1);
15824     }
15825 }
15826
15827 void
15828 HintEvent ()
15829 {
15830     if (appData.noChessProgram) return;
15831     switch (gameMode) {
15832       case MachinePlaysWhite:
15833         if (WhiteOnMove(forwardMostMove)) {
15834             DisplayError(_("Wait until your turn."), 0);
15835             return;
15836         }
15837         break;
15838       case BeginningOfGame:
15839       case MachinePlaysBlack:
15840         if (!WhiteOnMove(forwardMostMove)) {
15841             DisplayError(_("Wait until your turn."), 0);
15842             return;
15843         }
15844         break;
15845       default:
15846         DisplayError(_("No hint available"), 0);
15847         return;
15848     }
15849     SendToProgram("hint\n", &first);
15850     hintRequested = TRUE;
15851 }
15852
15853 int
15854 SaveSelected (FILE *g, int dummy, char *dummy2)
15855 {
15856     ListGame * lg = (ListGame *) gameList.head;
15857     int nItem, cnt=0;
15858     FILE *f;
15859
15860     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15861         DisplayError(_("Game list not loaded or empty"), 0);
15862         return 0;
15863     }
15864
15865     creatingBook = TRUE; // suppresses stuff during load game
15866
15867     /* Get list size */
15868     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15869         if(lg->position >= 0) { // selected?
15870             LoadGame(f, nItem, "", TRUE);
15871             SaveGamePGN2(g); // leaves g open
15872             cnt++; DoEvents();
15873         }
15874         lg = (ListGame *) lg->node.succ;
15875     }
15876
15877     fclose(g);
15878     creatingBook = FALSE;
15879
15880     return cnt;
15881 }
15882
15883 void
15884 CreateBookEvent ()
15885 {
15886     ListGame * lg = (ListGame *) gameList.head;
15887     FILE *f, *g;
15888     int nItem;
15889     static int secondTime = FALSE;
15890
15891     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15892         DisplayError(_("Game list not loaded or empty"), 0);
15893         return;
15894     }
15895
15896     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15897         fclose(g);
15898         secondTime++;
15899         DisplayNote(_("Book file exists! Try again for overwrite."));
15900         return;
15901     }
15902
15903     creatingBook = TRUE;
15904     secondTime = FALSE;
15905
15906     /* Get list size */
15907     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15908         if(lg->position >= 0) {
15909             LoadGame(f, nItem, "", TRUE);
15910             AddGameToBook(TRUE);
15911             DoEvents();
15912         }
15913         lg = (ListGame *) lg->node.succ;
15914     }
15915
15916     creatingBook = FALSE;
15917     FlushBook();
15918 }
15919
15920 void
15921 BookEvent ()
15922 {
15923     if (appData.noChessProgram) return;
15924     switch (gameMode) {
15925       case MachinePlaysWhite:
15926         if (WhiteOnMove(forwardMostMove)) {
15927             DisplayError(_("Wait until your turn."), 0);
15928             return;
15929         }
15930         break;
15931       case BeginningOfGame:
15932       case MachinePlaysBlack:
15933         if (!WhiteOnMove(forwardMostMove)) {
15934             DisplayError(_("Wait until your turn."), 0);
15935             return;
15936         }
15937         break;
15938       case EditPosition:
15939         EditPositionDone(TRUE);
15940         break;
15941       case TwoMachinesPlay:
15942         return;
15943       default:
15944         break;
15945     }
15946     SendToProgram("bk\n", &first);
15947     bookOutput[0] = NULLCHAR;
15948     bookRequested = TRUE;
15949 }
15950
15951 void
15952 AboutGameEvent ()
15953 {
15954     char *tags = PGNTags(&gameInfo);
15955     TagsPopUp(tags, CmailMsg());
15956     free(tags);
15957 }
15958
15959 /* end button procedures */
15960
15961 void
15962 PrintPosition (FILE *fp, int move)
15963 {
15964     int i, j;
15965
15966     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15967         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15968             char c = PieceToChar(boards[move][i][j]);
15969             fputc(c == 'x' ? '.' : c, fp);
15970             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15971         }
15972     }
15973     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15974       fprintf(fp, "white to play\n");
15975     else
15976       fprintf(fp, "black to play\n");
15977 }
15978
15979 void
15980 PrintOpponents (FILE *fp)
15981 {
15982     if (gameInfo.white != NULL) {
15983         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15984     } else {
15985         fprintf(fp, "\n");
15986     }
15987 }
15988
15989 /* Find last component of program's own name, using some heuristics */
15990 void
15991 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15992 {
15993     char *p, *q, c;
15994     int local = (strcmp(host, "localhost") == 0);
15995     while (!local && (p = strchr(prog, ';')) != NULL) {
15996         p++;
15997         while (*p == ' ') p++;
15998         prog = p;
15999     }
16000     if (*prog == '"' || *prog == '\'') {
16001         q = strchr(prog + 1, *prog);
16002     } else {
16003         q = strchr(prog, ' ');
16004     }
16005     if (q == NULL) q = prog + strlen(prog);
16006     p = q;
16007     while (p >= prog && *p != '/' && *p != '\\') p--;
16008     p++;
16009     if(p == prog && *p == '"') p++;
16010     c = *q; *q = 0;
16011     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16012     memcpy(buf, p, q - p);
16013     buf[q - p] = NULLCHAR;
16014     if (!local) {
16015         strcat(buf, "@");
16016         strcat(buf, host);
16017     }
16018 }
16019
16020 char *
16021 TimeControlTagValue ()
16022 {
16023     char buf[MSG_SIZ];
16024     if (!appData.clockMode) {
16025       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16026     } else if (movesPerSession > 0) {
16027       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16028     } else if (timeIncrement == 0) {
16029       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16030     } else {
16031       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16032     }
16033     return StrSave(buf);
16034 }
16035
16036 void
16037 SetGameInfo ()
16038 {
16039     /* This routine is used only for certain modes */
16040     VariantClass v = gameInfo.variant;
16041     ChessMove r = GameUnfinished;
16042     char *p = NULL;
16043
16044     if(keepInfo) return;
16045
16046     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16047         r = gameInfo.result;
16048         p = gameInfo.resultDetails;
16049         gameInfo.resultDetails = NULL;
16050     }
16051     ClearGameInfo(&gameInfo);
16052     gameInfo.variant = v;
16053
16054     switch (gameMode) {
16055       case MachinePlaysWhite:
16056         gameInfo.event = StrSave( appData.pgnEventHeader );
16057         gameInfo.site = StrSave(HostName());
16058         gameInfo.date = PGNDate();
16059         gameInfo.round = StrSave("-");
16060         gameInfo.white = StrSave(first.tidy);
16061         gameInfo.black = StrSave(UserName());
16062         gameInfo.timeControl = TimeControlTagValue();
16063         break;
16064
16065       case MachinePlaysBlack:
16066         gameInfo.event = StrSave( appData.pgnEventHeader );
16067         gameInfo.site = StrSave(HostName());
16068         gameInfo.date = PGNDate();
16069         gameInfo.round = StrSave("-");
16070         gameInfo.white = StrSave(UserName());
16071         gameInfo.black = StrSave(first.tidy);
16072         gameInfo.timeControl = TimeControlTagValue();
16073         break;
16074
16075       case TwoMachinesPlay:
16076         gameInfo.event = StrSave( appData.pgnEventHeader );
16077         gameInfo.site = StrSave(HostName());
16078         gameInfo.date = PGNDate();
16079         if (roundNr > 0) {
16080             char buf[MSG_SIZ];
16081             snprintf(buf, MSG_SIZ, "%d", roundNr);
16082             gameInfo.round = StrSave(buf);
16083         } else {
16084             gameInfo.round = StrSave("-");
16085         }
16086         if (first.twoMachinesColor[0] == 'w') {
16087             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16088             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16089         } else {
16090             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16091             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16092         }
16093         gameInfo.timeControl = TimeControlTagValue();
16094         break;
16095
16096       case EditGame:
16097         gameInfo.event = StrSave("Edited game");
16098         gameInfo.site = StrSave(HostName());
16099         gameInfo.date = PGNDate();
16100         gameInfo.round = StrSave("-");
16101         gameInfo.white = StrSave("-");
16102         gameInfo.black = StrSave("-");
16103         gameInfo.result = r;
16104         gameInfo.resultDetails = p;
16105         break;
16106
16107       case EditPosition:
16108         gameInfo.event = StrSave("Edited position");
16109         gameInfo.site = StrSave(HostName());
16110         gameInfo.date = PGNDate();
16111         gameInfo.round = StrSave("-");
16112         gameInfo.white = StrSave("-");
16113         gameInfo.black = StrSave("-");
16114         break;
16115
16116       case IcsPlayingWhite:
16117       case IcsPlayingBlack:
16118       case IcsObserving:
16119       case IcsExamining:
16120         break;
16121
16122       case PlayFromGameFile:
16123         gameInfo.event = StrSave("Game from non-PGN file");
16124         gameInfo.site = StrSave(HostName());
16125         gameInfo.date = PGNDate();
16126         gameInfo.round = StrSave("-");
16127         gameInfo.white = StrSave("?");
16128         gameInfo.black = StrSave("?");
16129         break;
16130
16131       default:
16132         break;
16133     }
16134 }
16135
16136 void
16137 ReplaceComment (int index, char *text)
16138 {
16139     int len;
16140     char *p;
16141     float score;
16142
16143     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16144        pvInfoList[index-1].depth == len &&
16145        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16146        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16147     while (*text == '\n') text++;
16148     len = strlen(text);
16149     while (len > 0 && text[len - 1] == '\n') len--;
16150
16151     if (commentList[index] != NULL)
16152       free(commentList[index]);
16153
16154     if (len == 0) {
16155         commentList[index] = NULL;
16156         return;
16157     }
16158   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16159       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16160       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16161     commentList[index] = (char *) malloc(len + 2);
16162     strncpy(commentList[index], text, len);
16163     commentList[index][len] = '\n';
16164     commentList[index][len + 1] = NULLCHAR;
16165   } else {
16166     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16167     char *p;
16168     commentList[index] = (char *) malloc(len + 7);
16169     safeStrCpy(commentList[index], "{\n", 3);
16170     safeStrCpy(commentList[index]+2, text, len+1);
16171     commentList[index][len+2] = NULLCHAR;
16172     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16173     strcat(commentList[index], "\n}\n");
16174   }
16175 }
16176
16177 void
16178 CrushCRs (char *text)
16179 {
16180   char *p = text;
16181   char *q = text;
16182   char ch;
16183
16184   do {
16185     ch = *p++;
16186     if (ch == '\r') continue;
16187     *q++ = ch;
16188   } while (ch != '\0');
16189 }
16190
16191 void
16192 AppendComment (int index, char *text, Boolean addBraces)
16193 /* addBraces  tells if we should add {} */
16194 {
16195     int oldlen, len;
16196     char *old;
16197
16198 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16199     if(addBraces == 3) addBraces = 0; else // force appending literally
16200     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16201
16202     CrushCRs(text);
16203     while (*text == '\n') text++;
16204     len = strlen(text);
16205     while (len > 0 && text[len - 1] == '\n') len--;
16206     text[len] = NULLCHAR;
16207
16208     if (len == 0) return;
16209
16210     if (commentList[index] != NULL) {
16211       Boolean addClosingBrace = addBraces;
16212         old = commentList[index];
16213         oldlen = strlen(old);
16214         while(commentList[index][oldlen-1] ==  '\n')
16215           commentList[index][--oldlen] = NULLCHAR;
16216         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16217         safeStrCpy(commentList[index], old, oldlen + len + 6);
16218         free(old);
16219         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16220         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16221           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16222           while (*text == '\n') { text++; len--; }
16223           commentList[index][--oldlen] = NULLCHAR;
16224       }
16225         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16226         else          strcat(commentList[index], "\n");
16227         strcat(commentList[index], text);
16228         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16229         else          strcat(commentList[index], "\n");
16230     } else {
16231         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16232         if(addBraces)
16233           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16234         else commentList[index][0] = NULLCHAR;
16235         strcat(commentList[index], text);
16236         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16237         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16238     }
16239 }
16240
16241 static char *
16242 FindStr (char * text, char * sub_text)
16243 {
16244     char * result = strstr( text, sub_text );
16245
16246     if( result != NULL ) {
16247         result += strlen( sub_text );
16248     }
16249
16250     return result;
16251 }
16252
16253 /* [AS] Try to extract PV info from PGN comment */
16254 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16255 char *
16256 GetInfoFromComment (int index, char * text)
16257 {
16258     char * sep = text, *p;
16259
16260     if( text != NULL && index > 0 ) {
16261         int score = 0;
16262         int depth = 0;
16263         int time = -1, sec = 0, deci;
16264         char * s_eval = FindStr( text, "[%eval " );
16265         char * s_emt = FindStr( text, "[%emt " );
16266 #if 0
16267         if( s_eval != NULL || s_emt != NULL ) {
16268 #else
16269         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16270 #endif
16271             /* New style */
16272             char delim;
16273
16274             if( s_eval != NULL ) {
16275                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16276                     return text;
16277                 }
16278
16279                 if( delim != ']' ) {
16280                     return text;
16281                 }
16282             }
16283
16284             if( s_emt != NULL ) {
16285             }
16286                 return text;
16287         }
16288         else {
16289             /* We expect something like: [+|-]nnn.nn/dd */
16290             int score_lo = 0;
16291
16292             if(*text != '{') return text; // [HGM] braces: must be normal comment
16293
16294             sep = strchr( text, '/' );
16295             if( sep == NULL || sep < (text+4) ) {
16296                 return text;
16297             }
16298
16299             p = text;
16300             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16301             if(p[1] == '(') { // comment starts with PV
16302                p = strchr(p, ')'); // locate end of PV
16303                if(p == NULL || sep < p+5) return text;
16304                // at this point we have something like "{(.*) +0.23/6 ..."
16305                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16306                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16307                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16308             }
16309             time = -1; sec = -1; deci = -1;
16310             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16311                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16312                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16313                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16314                 return text;
16315             }
16316
16317             if( score_lo < 0 || score_lo >= 100 ) {
16318                 return text;
16319             }
16320
16321             if(sec >= 0) time = 600*time + 10*sec; else
16322             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16323
16324             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16325
16326             /* [HGM] PV time: now locate end of PV info */
16327             while( *++sep >= '0' && *sep <= '9'); // strip depth
16328             if(time >= 0)
16329             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16330             if(sec >= 0)
16331             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16332             if(deci >= 0)
16333             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16334             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16335         }
16336
16337         if( depth <= 0 ) {
16338             return text;
16339         }
16340
16341         if( time < 0 ) {
16342             time = -1;
16343         }
16344
16345         pvInfoList[index-1].depth = depth;
16346         pvInfoList[index-1].score = score;
16347         pvInfoList[index-1].time  = 10*time; // centi-sec
16348         if(*sep == '}') *sep = 0; else *--sep = '{';
16349         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16350     }
16351     return sep;
16352 }
16353
16354 void
16355 SendToProgram (char *message, ChessProgramState *cps)
16356 {
16357     int count, outCount, error;
16358     char buf[MSG_SIZ];
16359
16360     if (cps->pr == NoProc) return;
16361     Attention(cps);
16362
16363     if (appData.debugMode) {
16364         TimeMark now;
16365         GetTimeMark(&now);
16366         fprintf(debugFP, "%ld >%-6s: %s",
16367                 SubtractTimeMarks(&now, &programStartTime),
16368                 cps->which, message);
16369         if(serverFP)
16370             fprintf(serverFP, "%ld >%-6s: %s",
16371                 SubtractTimeMarks(&now, &programStartTime),
16372                 cps->which, message), fflush(serverFP);
16373     }
16374
16375     count = strlen(message);
16376     outCount = OutputToProcess(cps->pr, message, count, &error);
16377     if (outCount < count && !exiting
16378                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16379       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16380       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16381         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16382             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16383                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16384                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16385                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16386             } else {
16387                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16388                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16389                 gameInfo.result = res;
16390             }
16391             gameInfo.resultDetails = StrSave(buf);
16392         }
16393         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16394         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16395     }
16396 }
16397
16398 void
16399 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16400 {
16401     char *end_str;
16402     char buf[MSG_SIZ];
16403     ChessProgramState *cps = (ChessProgramState *)closure;
16404
16405     if (isr != cps->isr) return; /* Killed intentionally */
16406     if (count <= 0) {
16407         if (count == 0) {
16408             RemoveInputSource(cps->isr);
16409             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16410                     _(cps->which), cps->program);
16411             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16412             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16413                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16414                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16415                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16416                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16417                 } else {
16418                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16419                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16420                     gameInfo.result = res;
16421                 }
16422                 gameInfo.resultDetails = StrSave(buf);
16423             }
16424             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16425             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16426         } else {
16427             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16428                     _(cps->which), cps->program);
16429             RemoveInputSource(cps->isr);
16430
16431             /* [AS] Program is misbehaving badly... kill it */
16432             if( count == -2 ) {
16433                 DestroyChildProcess( cps->pr, 9 );
16434                 cps->pr = NoProc;
16435             }
16436
16437             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16438         }
16439         return;
16440     }
16441
16442     if ((end_str = strchr(message, '\r')) != NULL)
16443       *end_str = NULLCHAR;
16444     if ((end_str = strchr(message, '\n')) != NULL)
16445       *end_str = NULLCHAR;
16446
16447     if (appData.debugMode) {
16448         TimeMark now; int print = 1;
16449         char *quote = ""; char c; int i;
16450
16451         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16452                 char start = message[0];
16453                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16454                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16455                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16456                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16457                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16458                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16459                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16460                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16461                    sscanf(message, "hint: %c", &c)!=1 &&
16462                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16463                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16464                     print = (appData.engineComments >= 2);
16465                 }
16466                 message[0] = start; // restore original message
16467         }
16468         if(print) {
16469                 GetTimeMark(&now);
16470                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16471                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16472                         quote,
16473                         message);
16474                 if(serverFP)
16475                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16476                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16477                         quote,
16478                         message), fflush(serverFP);
16479         }
16480     }
16481
16482     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16483     if (appData.icsEngineAnalyze) {
16484         if (strstr(message, "whisper") != NULL ||
16485              strstr(message, "kibitz") != NULL ||
16486             strstr(message, "tellics") != NULL) return;
16487     }
16488
16489     HandleMachineMove(message, cps);
16490 }
16491
16492
16493 void
16494 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16495 {
16496     char buf[MSG_SIZ];
16497     int seconds;
16498
16499     if( timeControl_2 > 0 ) {
16500         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16501             tc = timeControl_2;
16502         }
16503     }
16504     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16505     inc /= cps->timeOdds;
16506     st  /= cps->timeOdds;
16507
16508     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16509
16510     if (st > 0) {
16511       /* Set exact time per move, normally using st command */
16512       if (cps->stKludge) {
16513         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16514         seconds = st % 60;
16515         if (seconds == 0) {
16516           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16517         } else {
16518           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16519         }
16520       } else {
16521         snprintf(buf, MSG_SIZ, "st %d\n", st);
16522       }
16523     } else {
16524       /* Set conventional or incremental time control, using level command */
16525       if (seconds == 0) {
16526         /* Note old gnuchess bug -- minutes:seconds used to not work.
16527            Fixed in later versions, but still avoid :seconds
16528            when seconds is 0. */
16529         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16530       } else {
16531         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16532                  seconds, inc/1000.);
16533       }
16534     }
16535     SendToProgram(buf, cps);
16536
16537     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16538     /* Orthogonally, limit search to given depth */
16539     if (sd > 0) {
16540       if (cps->sdKludge) {
16541         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16542       } else {
16543         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16544       }
16545       SendToProgram(buf, cps);
16546     }
16547
16548     if(cps->nps >= 0) { /* [HGM] nps */
16549         if(cps->supportsNPS == FALSE)
16550           cps->nps = -1; // don't use if engine explicitly says not supported!
16551         else {
16552           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16553           SendToProgram(buf, cps);
16554         }
16555     }
16556 }
16557
16558 ChessProgramState *
16559 WhitePlayer ()
16560 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16561 {
16562     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16563        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16564         return &second;
16565     return &first;
16566 }
16567
16568 void
16569 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16570 {
16571     char message[MSG_SIZ];
16572     long time, otime;
16573
16574     /* Note: this routine must be called when the clocks are stopped
16575        or when they have *just* been set or switched; otherwise
16576        it will be off by the time since the current tick started.
16577     */
16578     if (machineWhite) {
16579         time = whiteTimeRemaining / 10;
16580         otime = blackTimeRemaining / 10;
16581     } else {
16582         time = blackTimeRemaining / 10;
16583         otime = whiteTimeRemaining / 10;
16584     }
16585     /* [HGM] translate opponent's time by time-odds factor */
16586     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16587
16588     if (time <= 0) time = 1;
16589     if (otime <= 0) otime = 1;
16590
16591     snprintf(message, MSG_SIZ, "time %ld\n", time);
16592     SendToProgram(message, cps);
16593
16594     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16595     SendToProgram(message, cps);
16596 }
16597
16598 char *
16599 EngineDefinedVariant (ChessProgramState *cps, int n)
16600 {   // return name of n-th unknown variant that engine supports
16601     static char buf[MSG_SIZ];
16602     char *p, *s = cps->variants;
16603     if(!s) return NULL;
16604     do { // parse string from variants feature
16605       VariantClass v;
16606         p = strchr(s, ',');
16607         if(p) *p = NULLCHAR;
16608       v = StringToVariant(s);
16609       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16610         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16611             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16612         }
16613         if(p) *p++ = ',';
16614         if(n < 0) return buf;
16615     } while(s = p);
16616     return NULL;
16617 }
16618
16619 int
16620 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16621 {
16622   char buf[MSG_SIZ];
16623   int len = strlen(name);
16624   int val;
16625
16626   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16627     (*p) += len + 1;
16628     sscanf(*p, "%d", &val);
16629     *loc = (val != 0);
16630     while (**p && **p != ' ')
16631       (*p)++;
16632     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16633     SendToProgram(buf, cps);
16634     return TRUE;
16635   }
16636   return FALSE;
16637 }
16638
16639 int
16640 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16641 {
16642   char buf[MSG_SIZ];
16643   int len = strlen(name);
16644   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16645     (*p) += len + 1;
16646     sscanf(*p, "%d", loc);
16647     while (**p && **p != ' ') (*p)++;
16648     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16649     SendToProgram(buf, cps);
16650     return TRUE;
16651   }
16652   return FALSE;
16653 }
16654
16655 int
16656 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16657 {
16658   char buf[MSG_SIZ];
16659   int len = strlen(name);
16660   if (strncmp((*p), name, len) == 0
16661       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16662     (*p) += len + 2;
16663     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16664     sscanf(*p, "%[^\"]", *loc);
16665     while (**p && **p != '\"') (*p)++;
16666     if (**p == '\"') (*p)++;
16667     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16668     SendToProgram(buf, cps);
16669     return TRUE;
16670   }
16671   return FALSE;
16672 }
16673
16674 int
16675 ParseOption (Option *opt, ChessProgramState *cps)
16676 // [HGM] options: process the string that defines an engine option, and determine
16677 // name, type, default value, and allowed value range
16678 {
16679         char *p, *q, buf[MSG_SIZ];
16680         int n, min = (-1)<<31, max = 1<<31, def;
16681
16682         if(p = strstr(opt->name, " -spin ")) {
16683             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16684             if(max < min) max = min; // enforce consistency
16685             if(def < min) def = min;
16686             if(def > max) def = max;
16687             opt->value = def;
16688             opt->min = min;
16689             opt->max = max;
16690             opt->type = Spin;
16691         } else if((p = strstr(opt->name, " -slider "))) {
16692             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16693             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16694             if(max < min) max = min; // enforce consistency
16695             if(def < min) def = min;
16696             if(def > max) def = max;
16697             opt->value = def;
16698             opt->min = min;
16699             opt->max = max;
16700             opt->type = Spin; // Slider;
16701         } else if((p = strstr(opt->name, " -string "))) {
16702             opt->textValue = p+9;
16703             opt->type = TextBox;
16704         } else if((p = strstr(opt->name, " -file "))) {
16705             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16706             opt->textValue = p+7;
16707             opt->type = FileName; // FileName;
16708         } else if((p = strstr(opt->name, " -path "))) {
16709             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16710             opt->textValue = p+7;
16711             opt->type = PathName; // PathName;
16712         } else if(p = strstr(opt->name, " -check ")) {
16713             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16714             opt->value = (def != 0);
16715             opt->type = CheckBox;
16716         } else if(p = strstr(opt->name, " -combo ")) {
16717             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16718             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16719             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16720             opt->value = n = 0;
16721             while(q = StrStr(q, " /// ")) {
16722                 n++; *q = 0;    // count choices, and null-terminate each of them
16723                 q += 5;
16724                 if(*q == '*') { // remember default, which is marked with * prefix
16725                     q++;
16726                     opt->value = n;
16727                 }
16728                 cps->comboList[cps->comboCnt++] = q;
16729             }
16730             cps->comboList[cps->comboCnt++] = NULL;
16731             opt->max = n + 1;
16732             opt->type = ComboBox;
16733         } else if(p = strstr(opt->name, " -button")) {
16734             opt->type = Button;
16735         } else if(p = strstr(opt->name, " -save")) {
16736             opt->type = SaveButton;
16737         } else return FALSE;
16738         *p = 0; // terminate option name
16739         // now look if the command-line options define a setting for this engine option.
16740         if(cps->optionSettings && cps->optionSettings[0])
16741             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16742         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16743           snprintf(buf, MSG_SIZ, "option %s", p);
16744                 if(p = strstr(buf, ",")) *p = 0;
16745                 if(q = strchr(buf, '=')) switch(opt->type) {
16746                     case ComboBox:
16747                         for(n=0; n<opt->max; n++)
16748                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16749                         break;
16750                     case TextBox:
16751                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16752                         break;
16753                     case Spin:
16754                     case CheckBox:
16755                         opt->value = atoi(q+1);
16756                     default:
16757                         break;
16758                 }
16759                 strcat(buf, "\n");
16760                 SendToProgram(buf, cps);
16761         }
16762         return TRUE;
16763 }
16764
16765 void
16766 FeatureDone (ChessProgramState *cps, int val)
16767 {
16768   DelayedEventCallback cb = GetDelayedEvent();
16769   if ((cb == InitBackEnd3 && cps == &first) ||
16770       (cb == SettingsMenuIfReady && cps == &second) ||
16771       (cb == LoadEngine) ||
16772       (cb == TwoMachinesEventIfReady)) {
16773     CancelDelayedEvent();
16774     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16775   }
16776   cps->initDone = val;
16777   if(val) cps->reload = FALSE;
16778 }
16779
16780 /* Parse feature command from engine */
16781 void
16782 ParseFeatures (char *args, ChessProgramState *cps)
16783 {
16784   char *p = args;
16785   char *q = NULL;
16786   int val;
16787   char buf[MSG_SIZ];
16788
16789   for (;;) {
16790     while (*p == ' ') p++;
16791     if (*p == NULLCHAR) return;
16792
16793     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16794     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16795     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16796     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16797     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16798     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16799     if (BoolFeature(&p, "reuse", &val, cps)) {
16800       /* Engine can disable reuse, but can't enable it if user said no */
16801       if (!val) cps->reuse = FALSE;
16802       continue;
16803     }
16804     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16805     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16806       if (gameMode == TwoMachinesPlay) {
16807         DisplayTwoMachinesTitle();
16808       } else {
16809         DisplayTitle("");
16810       }
16811       continue;
16812     }
16813     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16814     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16815     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16816     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16817     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16818     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16819     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16820     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16821     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16822     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16823     if (IntFeature(&p, "done", &val, cps)) {
16824       FeatureDone(cps, val);
16825       continue;
16826     }
16827     /* Added by Tord: */
16828     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16829     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16830     /* End of additions by Tord */
16831
16832     /* [HGM] added features: */
16833     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16834     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16835     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16836     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16837     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16838     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16839     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16840     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16841         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16842         FREE(cps->option[cps->nrOptions].name);
16843         cps->option[cps->nrOptions].name = q; q = NULL;
16844         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16845           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16846             SendToProgram(buf, cps);
16847             continue;
16848         }
16849         if(cps->nrOptions >= MAX_OPTIONS) {
16850             cps->nrOptions--;
16851             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16852             DisplayError(buf, 0);
16853         }
16854         continue;
16855     }
16856     /* End of additions by HGM */
16857
16858     /* unknown feature: complain and skip */
16859     q = p;
16860     while (*q && *q != '=') q++;
16861     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16862     SendToProgram(buf, cps);
16863     p = q;
16864     if (*p == '=') {
16865       p++;
16866       if (*p == '\"') {
16867         p++;
16868         while (*p && *p != '\"') p++;
16869         if (*p == '\"') p++;
16870       } else {
16871         while (*p && *p != ' ') p++;
16872       }
16873     }
16874   }
16875
16876 }
16877
16878 void
16879 PeriodicUpdatesEvent (int newState)
16880 {
16881     if (newState == appData.periodicUpdates)
16882       return;
16883
16884     appData.periodicUpdates=newState;
16885
16886     /* Display type changes, so update it now */
16887 //    DisplayAnalysis();
16888
16889     /* Get the ball rolling again... */
16890     if (newState) {
16891         AnalysisPeriodicEvent(1);
16892         StartAnalysisClock();
16893     }
16894 }
16895
16896 void
16897 PonderNextMoveEvent (int newState)
16898 {
16899     if (newState == appData.ponderNextMove) return;
16900     if (gameMode == EditPosition) EditPositionDone(TRUE);
16901     if (newState) {
16902         SendToProgram("hard\n", &first);
16903         if (gameMode == TwoMachinesPlay) {
16904             SendToProgram("hard\n", &second);
16905         }
16906     } else {
16907         SendToProgram("easy\n", &first);
16908         thinkOutput[0] = NULLCHAR;
16909         if (gameMode == TwoMachinesPlay) {
16910             SendToProgram("easy\n", &second);
16911         }
16912     }
16913     appData.ponderNextMove = newState;
16914 }
16915
16916 void
16917 NewSettingEvent (int option, int *feature, char *command, int value)
16918 {
16919     char buf[MSG_SIZ];
16920
16921     if (gameMode == EditPosition) EditPositionDone(TRUE);
16922     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16923     if(feature == NULL || *feature) SendToProgram(buf, &first);
16924     if (gameMode == TwoMachinesPlay) {
16925         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16926     }
16927 }
16928
16929 void
16930 ShowThinkingEvent ()
16931 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16932 {
16933     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16934     int newState = appData.showThinking
16935         // [HGM] thinking: other features now need thinking output as well
16936         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16937
16938     if (oldState == newState) return;
16939     oldState = newState;
16940     if (gameMode == EditPosition) EditPositionDone(TRUE);
16941     if (oldState) {
16942         SendToProgram("post\n", &first);
16943         if (gameMode == TwoMachinesPlay) {
16944             SendToProgram("post\n", &second);
16945         }
16946     } else {
16947         SendToProgram("nopost\n", &first);
16948         thinkOutput[0] = NULLCHAR;
16949         if (gameMode == TwoMachinesPlay) {
16950             SendToProgram("nopost\n", &second);
16951         }
16952     }
16953 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16954 }
16955
16956 void
16957 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16958 {
16959   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16960   if (pr == NoProc) return;
16961   AskQuestion(title, question, replyPrefix, pr);
16962 }
16963
16964 void
16965 TypeInEvent (char firstChar)
16966 {
16967     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16968         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16969         gameMode == AnalyzeMode || gameMode == EditGame ||
16970         gameMode == EditPosition || gameMode == IcsExamining ||
16971         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16972         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16973                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16974                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16975         gameMode == Training) PopUpMoveDialog(firstChar);
16976 }
16977
16978 void
16979 TypeInDoneEvent (char *move)
16980 {
16981         Board board;
16982         int n, fromX, fromY, toX, toY;
16983         char promoChar;
16984         ChessMove moveType;
16985
16986         // [HGM] FENedit
16987         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16988                 EditPositionPasteFEN(move);
16989                 return;
16990         }
16991         // [HGM] movenum: allow move number to be typed in any mode
16992         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16993           ToNrEvent(2*n-1);
16994           return;
16995         }
16996         // undocumented kludge: allow command-line option to be typed in!
16997         // (potentially fatal, and does not implement the effect of the option.)
16998         // should only be used for options that are values on which future decisions will be made,
16999         // and definitely not on options that would be used during initialization.
17000         if(strstr(move, "!!! -") == move) {
17001             ParseArgsFromString(move+4);
17002             return;
17003         }
17004
17005       if (gameMode != EditGame && currentMove != forwardMostMove &&
17006         gameMode != Training) {
17007         DisplayMoveError(_("Displayed move is not current"));
17008       } else {
17009         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17010           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17011         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17012         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17013           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17014           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17015         } else {
17016           DisplayMoveError(_("Could not parse move"));
17017         }
17018       }
17019 }
17020
17021 void
17022 DisplayMove (int moveNumber)
17023 {
17024     char message[MSG_SIZ];
17025     char res[MSG_SIZ];
17026     char cpThinkOutput[MSG_SIZ];
17027
17028     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17029
17030     if (moveNumber == forwardMostMove - 1 ||
17031         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17032
17033         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17034
17035         if (strchr(cpThinkOutput, '\n')) {
17036             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17037         }
17038     } else {
17039         *cpThinkOutput = NULLCHAR;
17040     }
17041
17042     /* [AS] Hide thinking from human user */
17043     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17044         *cpThinkOutput = NULLCHAR;
17045         if( thinkOutput[0] != NULLCHAR ) {
17046             int i;
17047
17048             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17049                 cpThinkOutput[i] = '.';
17050             }
17051             cpThinkOutput[i] = NULLCHAR;
17052             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17053         }
17054     }
17055
17056     if (moveNumber == forwardMostMove - 1 &&
17057         gameInfo.resultDetails != NULL) {
17058         if (gameInfo.resultDetails[0] == NULLCHAR) {
17059           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17060         } else {
17061           snprintf(res, MSG_SIZ, " {%s} %s",
17062                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17063         }
17064     } else {
17065         res[0] = NULLCHAR;
17066     }
17067
17068     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17069         DisplayMessage(res, cpThinkOutput);
17070     } else {
17071       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17072                 WhiteOnMove(moveNumber) ? " " : ".. ",
17073                 parseList[moveNumber], res);
17074         DisplayMessage(message, cpThinkOutput);
17075     }
17076 }
17077
17078 void
17079 DisplayComment (int moveNumber, char *text)
17080 {
17081     char title[MSG_SIZ];
17082
17083     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17084       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17085     } else {
17086       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17087               WhiteOnMove(moveNumber) ? " " : ".. ",
17088               parseList[moveNumber]);
17089     }
17090     if (text != NULL && (appData.autoDisplayComment || commentUp))
17091         CommentPopUp(title, text);
17092 }
17093
17094 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17095  * might be busy thinking or pondering.  It can be omitted if your
17096  * gnuchess is configured to stop thinking immediately on any user
17097  * input.  However, that gnuchess feature depends on the FIONREAD
17098  * ioctl, which does not work properly on some flavors of Unix.
17099  */
17100 void
17101 Attention (ChessProgramState *cps)
17102 {
17103 #if ATTENTION
17104     if (!cps->useSigint) return;
17105     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17106     switch (gameMode) {
17107       case MachinePlaysWhite:
17108       case MachinePlaysBlack:
17109       case TwoMachinesPlay:
17110       case IcsPlayingWhite:
17111       case IcsPlayingBlack:
17112       case AnalyzeMode:
17113       case AnalyzeFile:
17114         /* Skip if we know it isn't thinking */
17115         if (!cps->maybeThinking) return;
17116         if (appData.debugMode)
17117           fprintf(debugFP, "Interrupting %s\n", cps->which);
17118         InterruptChildProcess(cps->pr);
17119         cps->maybeThinking = FALSE;
17120         break;
17121       default:
17122         break;
17123     }
17124 #endif /*ATTENTION*/
17125 }
17126
17127 int
17128 CheckFlags ()
17129 {
17130     if (whiteTimeRemaining <= 0) {
17131         if (!whiteFlag) {
17132             whiteFlag = TRUE;
17133             if (appData.icsActive) {
17134                 if (appData.autoCallFlag &&
17135                     gameMode == IcsPlayingBlack && !blackFlag) {
17136                   SendToICS(ics_prefix);
17137                   SendToICS("flag\n");
17138                 }
17139             } else {
17140                 if (blackFlag) {
17141                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17142                 } else {
17143                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17144                     if (appData.autoCallFlag) {
17145                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17146                         return TRUE;
17147                     }
17148                 }
17149             }
17150         }
17151     }
17152     if (blackTimeRemaining <= 0) {
17153         if (!blackFlag) {
17154             blackFlag = TRUE;
17155             if (appData.icsActive) {
17156                 if (appData.autoCallFlag &&
17157                     gameMode == IcsPlayingWhite && !whiteFlag) {
17158                   SendToICS(ics_prefix);
17159                   SendToICS("flag\n");
17160                 }
17161             } else {
17162                 if (whiteFlag) {
17163                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17164                 } else {
17165                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17166                     if (appData.autoCallFlag) {
17167                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17168                         return TRUE;
17169                     }
17170                 }
17171             }
17172         }
17173     }
17174     return FALSE;
17175 }
17176
17177 void
17178 CheckTimeControl ()
17179 {
17180     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17181         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17182
17183     /*
17184      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17185      */
17186     if ( !WhiteOnMove(forwardMostMove) ) {
17187         /* White made time control */
17188         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17189         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17190         /* [HGM] time odds: correct new time quota for time odds! */
17191                                             / WhitePlayer()->timeOdds;
17192         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17193     } else {
17194         lastBlack -= blackTimeRemaining;
17195         /* Black made time control */
17196         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17197                                             / WhitePlayer()->other->timeOdds;
17198         lastWhite = whiteTimeRemaining;
17199     }
17200 }
17201
17202 void
17203 DisplayBothClocks ()
17204 {
17205     int wom = gameMode == EditPosition ?
17206       !blackPlaysFirst : WhiteOnMove(currentMove);
17207     DisplayWhiteClock(whiteTimeRemaining, wom);
17208     DisplayBlackClock(blackTimeRemaining, !wom);
17209 }
17210
17211
17212 /* Timekeeping seems to be a portability nightmare.  I think everyone
17213    has ftime(), but I'm really not sure, so I'm including some ifdefs
17214    to use other calls if you don't.  Clocks will be less accurate if
17215    you have neither ftime nor gettimeofday.
17216 */
17217
17218 /* VS 2008 requires the #include outside of the function */
17219 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17220 #include <sys/timeb.h>
17221 #endif
17222
17223 /* Get the current time as a TimeMark */
17224 void
17225 GetTimeMark (TimeMark *tm)
17226 {
17227 #if HAVE_GETTIMEOFDAY
17228
17229     struct timeval timeVal;
17230     struct timezone timeZone;
17231
17232     gettimeofday(&timeVal, &timeZone);
17233     tm->sec = (long) timeVal.tv_sec;
17234     tm->ms = (int) (timeVal.tv_usec / 1000L);
17235
17236 #else /*!HAVE_GETTIMEOFDAY*/
17237 #if HAVE_FTIME
17238
17239 // include <sys/timeb.h> / moved to just above start of function
17240     struct timeb timeB;
17241
17242     ftime(&timeB);
17243     tm->sec = (long) timeB.time;
17244     tm->ms = (int) timeB.millitm;
17245
17246 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17247     tm->sec = (long) time(NULL);
17248     tm->ms = 0;
17249 #endif
17250 #endif
17251 }
17252
17253 /* Return the difference in milliseconds between two
17254    time marks.  We assume the difference will fit in a long!
17255 */
17256 long
17257 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17258 {
17259     return 1000L*(tm2->sec - tm1->sec) +
17260            (long) (tm2->ms - tm1->ms);
17261 }
17262
17263
17264 /*
17265  * Code to manage the game clocks.
17266  *
17267  * In tournament play, black starts the clock and then white makes a move.
17268  * We give the human user a slight advantage if he is playing white---the
17269  * clocks don't run until he makes his first move, so it takes zero time.
17270  * Also, we don't account for network lag, so we could get out of sync
17271  * with GNU Chess's clock -- but then, referees are always right.
17272  */
17273
17274 static TimeMark tickStartTM;
17275 static long intendedTickLength;
17276
17277 long
17278 NextTickLength (long timeRemaining)
17279 {
17280     long nominalTickLength, nextTickLength;
17281
17282     if (timeRemaining > 0L && timeRemaining <= 10000L)
17283       nominalTickLength = 100L;
17284     else
17285       nominalTickLength = 1000L;
17286     nextTickLength = timeRemaining % nominalTickLength;
17287     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17288
17289     return nextTickLength;
17290 }
17291
17292 /* Adjust clock one minute up or down */
17293 void
17294 AdjustClock (Boolean which, int dir)
17295 {
17296     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17297     if(which) blackTimeRemaining += 60000*dir;
17298     else      whiteTimeRemaining += 60000*dir;
17299     DisplayBothClocks();
17300     adjustedClock = TRUE;
17301 }
17302
17303 /* Stop clocks and reset to a fresh time control */
17304 void
17305 ResetClocks ()
17306 {
17307     (void) StopClockTimer();
17308     if (appData.icsActive) {
17309         whiteTimeRemaining = blackTimeRemaining = 0;
17310     } else if (searchTime) {
17311         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17312         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17313     } else { /* [HGM] correct new time quote for time odds */
17314         whiteTC = blackTC = fullTimeControlString;
17315         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17316         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17317     }
17318     if (whiteFlag || blackFlag) {
17319         DisplayTitle("");
17320         whiteFlag = blackFlag = FALSE;
17321     }
17322     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17323     DisplayBothClocks();
17324     adjustedClock = FALSE;
17325 }
17326
17327 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17328
17329 /* Decrement running clock by amount of time that has passed */
17330 void
17331 DecrementClocks ()
17332 {
17333     long timeRemaining;
17334     long lastTickLength, fudge;
17335     TimeMark now;
17336
17337     if (!appData.clockMode) return;
17338     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17339
17340     GetTimeMark(&now);
17341
17342     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17343
17344     /* Fudge if we woke up a little too soon */
17345     fudge = intendedTickLength - lastTickLength;
17346     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17347
17348     if (WhiteOnMove(forwardMostMove)) {
17349         if(whiteNPS >= 0) lastTickLength = 0;
17350         timeRemaining = whiteTimeRemaining -= lastTickLength;
17351         if(timeRemaining < 0 && !appData.icsActive) {
17352             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17353             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17354                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17355                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17356             }
17357         }
17358         DisplayWhiteClock(whiteTimeRemaining - fudge,
17359                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17360     } else {
17361         if(blackNPS >= 0) lastTickLength = 0;
17362         timeRemaining = blackTimeRemaining -= lastTickLength;
17363         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17364             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17365             if(suddenDeath) {
17366                 blackStartMove = forwardMostMove;
17367                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17368             }
17369         }
17370         DisplayBlackClock(blackTimeRemaining - fudge,
17371                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17372     }
17373     if (CheckFlags()) return;
17374
17375     if(twoBoards) { // count down secondary board's clocks as well
17376         activePartnerTime -= lastTickLength;
17377         partnerUp = 1;
17378         if(activePartner == 'W')
17379             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17380         else
17381             DisplayBlackClock(activePartnerTime, TRUE);
17382         partnerUp = 0;
17383     }
17384
17385     tickStartTM = now;
17386     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17387     StartClockTimer(intendedTickLength);
17388
17389     /* if the time remaining has fallen below the alarm threshold, sound the
17390      * alarm. if the alarm has sounded and (due to a takeback or time control
17391      * with increment) the time remaining has increased to a level above the
17392      * threshold, reset the alarm so it can sound again.
17393      */
17394
17395     if (appData.icsActive && appData.icsAlarm) {
17396
17397         /* make sure we are dealing with the user's clock */
17398         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17399                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17400            )) return;
17401
17402         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17403             alarmSounded = FALSE;
17404         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17405             PlayAlarmSound();
17406             alarmSounded = TRUE;
17407         }
17408     }
17409 }
17410
17411
17412 /* A player has just moved, so stop the previously running
17413    clock and (if in clock mode) start the other one.
17414    We redisplay both clocks in case we're in ICS mode, because
17415    ICS gives us an update to both clocks after every move.
17416    Note that this routine is called *after* forwardMostMove
17417    is updated, so the last fractional tick must be subtracted
17418    from the color that is *not* on move now.
17419 */
17420 void
17421 SwitchClocks (int newMoveNr)
17422 {
17423     long lastTickLength;
17424     TimeMark now;
17425     int flagged = FALSE;
17426
17427     GetTimeMark(&now);
17428
17429     if (StopClockTimer() && appData.clockMode) {
17430         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17431         if (!WhiteOnMove(forwardMostMove)) {
17432             if(blackNPS >= 0) lastTickLength = 0;
17433             blackTimeRemaining -= lastTickLength;
17434            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17435 //         if(pvInfoList[forwardMostMove].time == -1)
17436                  pvInfoList[forwardMostMove].time =               // use GUI time
17437                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17438         } else {
17439            if(whiteNPS >= 0) lastTickLength = 0;
17440            whiteTimeRemaining -= lastTickLength;
17441            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17442 //         if(pvInfoList[forwardMostMove].time == -1)
17443                  pvInfoList[forwardMostMove].time =
17444                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17445         }
17446         flagged = CheckFlags();
17447     }
17448     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17449     CheckTimeControl();
17450
17451     if (flagged || !appData.clockMode) return;
17452
17453     switch (gameMode) {
17454       case MachinePlaysBlack:
17455       case MachinePlaysWhite:
17456       case BeginningOfGame:
17457         if (pausing) return;
17458         break;
17459
17460       case EditGame:
17461       case PlayFromGameFile:
17462       case IcsExamining:
17463         return;
17464
17465       default:
17466         break;
17467     }
17468
17469     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17470         if(WhiteOnMove(forwardMostMove))
17471              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17472         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17473     }
17474
17475     tickStartTM = now;
17476     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17477       whiteTimeRemaining : blackTimeRemaining);
17478     StartClockTimer(intendedTickLength);
17479 }
17480
17481
17482 /* Stop both clocks */
17483 void
17484 StopClocks ()
17485 {
17486     long lastTickLength;
17487     TimeMark now;
17488
17489     if (!StopClockTimer()) return;
17490     if (!appData.clockMode) return;
17491
17492     GetTimeMark(&now);
17493
17494     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17495     if (WhiteOnMove(forwardMostMove)) {
17496         if(whiteNPS >= 0) lastTickLength = 0;
17497         whiteTimeRemaining -= lastTickLength;
17498         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17499     } else {
17500         if(blackNPS >= 0) lastTickLength = 0;
17501         blackTimeRemaining -= lastTickLength;
17502         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17503     }
17504     CheckFlags();
17505 }
17506
17507 /* Start clock of player on move.  Time may have been reset, so
17508    if clock is already running, stop and restart it. */
17509 void
17510 StartClocks ()
17511 {
17512     (void) StopClockTimer(); /* in case it was running already */
17513     DisplayBothClocks();
17514     if (CheckFlags()) return;
17515
17516     if (!appData.clockMode) return;
17517     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17518
17519     GetTimeMark(&tickStartTM);
17520     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17521       whiteTimeRemaining : blackTimeRemaining);
17522
17523    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17524     whiteNPS = blackNPS = -1;
17525     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17526        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17527         whiteNPS = first.nps;
17528     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17529        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17530         blackNPS = first.nps;
17531     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17532         whiteNPS = second.nps;
17533     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17534         blackNPS = second.nps;
17535     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17536
17537     StartClockTimer(intendedTickLength);
17538 }
17539
17540 char *
17541 TimeString (long ms)
17542 {
17543     long second, minute, hour, day;
17544     char *sign = "";
17545     static char buf[32];
17546
17547     if (ms > 0 && ms <= 9900) {
17548       /* convert milliseconds to tenths, rounding up */
17549       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17550
17551       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17552       return buf;
17553     }
17554
17555     /* convert milliseconds to seconds, rounding up */
17556     /* use floating point to avoid strangeness of integer division
17557        with negative dividends on many machines */
17558     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17559
17560     if (second < 0) {
17561         sign = "-";
17562         second = -second;
17563     }
17564
17565     day = second / (60 * 60 * 24);
17566     second = second % (60 * 60 * 24);
17567     hour = second / (60 * 60);
17568     second = second % (60 * 60);
17569     minute = second / 60;
17570     second = second % 60;
17571
17572     if (day > 0)
17573       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17574               sign, day, hour, minute, second);
17575     else if (hour > 0)
17576       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17577     else
17578       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17579
17580     return buf;
17581 }
17582
17583
17584 /*
17585  * This is necessary because some C libraries aren't ANSI C compliant yet.
17586  */
17587 char *
17588 StrStr (char *string, char *match)
17589 {
17590     int i, length;
17591
17592     length = strlen(match);
17593
17594     for (i = strlen(string) - length; i >= 0; i--, string++)
17595       if (!strncmp(match, string, length))
17596         return string;
17597
17598     return NULL;
17599 }
17600
17601 char *
17602 StrCaseStr (char *string, char *match)
17603 {
17604     int i, j, length;
17605
17606     length = strlen(match);
17607
17608     for (i = strlen(string) - length; i >= 0; i--, string++) {
17609         for (j = 0; j < length; j++) {
17610             if (ToLower(match[j]) != ToLower(string[j]))
17611               break;
17612         }
17613         if (j == length) return string;
17614     }
17615
17616     return NULL;
17617 }
17618
17619 #ifndef _amigados
17620 int
17621 StrCaseCmp (char *s1, char *s2)
17622 {
17623     char c1, c2;
17624
17625     for (;;) {
17626         c1 = ToLower(*s1++);
17627         c2 = ToLower(*s2++);
17628         if (c1 > c2) return 1;
17629         if (c1 < c2) return -1;
17630         if (c1 == NULLCHAR) return 0;
17631     }
17632 }
17633
17634
17635 int
17636 ToLower (int c)
17637 {
17638     return isupper(c) ? tolower(c) : c;
17639 }
17640
17641
17642 int
17643 ToUpper (int c)
17644 {
17645     return islower(c) ? toupper(c) : c;
17646 }
17647 #endif /* !_amigados    */
17648
17649 char *
17650 StrSave (char *s)
17651 {
17652   char *ret;
17653
17654   if ((ret = (char *) malloc(strlen(s) + 1)))
17655     {
17656       safeStrCpy(ret, s, strlen(s)+1);
17657     }
17658   return ret;
17659 }
17660
17661 char *
17662 StrSavePtr (char *s, char **savePtr)
17663 {
17664     if (*savePtr) {
17665         free(*savePtr);
17666     }
17667     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17668       safeStrCpy(*savePtr, s, strlen(s)+1);
17669     }
17670     return(*savePtr);
17671 }
17672
17673 char *
17674 PGNDate ()
17675 {
17676     time_t clock;
17677     struct tm *tm;
17678     char buf[MSG_SIZ];
17679
17680     clock = time((time_t *)NULL);
17681     tm = localtime(&clock);
17682     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17683             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17684     return StrSave(buf);
17685 }
17686
17687
17688 char *
17689 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17690 {
17691     int i, j, fromX, fromY, toX, toY;
17692     int whiteToPlay;
17693     char buf[MSG_SIZ];
17694     char *p, *q;
17695     int emptycount;
17696     ChessSquare piece;
17697
17698     whiteToPlay = (gameMode == EditPosition) ?
17699       !blackPlaysFirst : (move % 2 == 0);
17700     p = buf;
17701
17702     /* Piece placement data */
17703     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17704         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17705         emptycount = 0;
17706         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17707             if (boards[move][i][j] == EmptySquare) {
17708                 emptycount++;
17709             } else { ChessSquare piece = boards[move][i][j];
17710                 if (emptycount > 0) {
17711                     if(emptycount<10) /* [HGM] can be >= 10 */
17712                         *p++ = '0' + emptycount;
17713                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17714                     emptycount = 0;
17715                 }
17716                 if(PieceToChar(piece) == '+') {
17717                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17718                     *p++ = '+';
17719                     piece = (ChessSquare)(CHUDEMOTED piece);
17720                 }
17721                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17722                 if(p[-1] == '~') {
17723                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17724                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17725                     *p++ = '~';
17726                 }
17727             }
17728         }
17729         if (emptycount > 0) {
17730             if(emptycount<10) /* [HGM] can be >= 10 */
17731                 *p++ = '0' + emptycount;
17732             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17733             emptycount = 0;
17734         }
17735         *p++ = '/';
17736     }
17737     *(p - 1) = ' ';
17738
17739     /* [HGM] print Crazyhouse or Shogi holdings */
17740     if( gameInfo.holdingsWidth ) {
17741         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17742         q = p;
17743         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17744             piece = boards[move][i][BOARD_WIDTH-1];
17745             if( piece != EmptySquare )
17746               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17747                   *p++ = PieceToChar(piece);
17748         }
17749         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17750             piece = boards[move][BOARD_HEIGHT-i-1][0];
17751             if( piece != EmptySquare )
17752               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17753                   *p++ = PieceToChar(piece);
17754         }
17755
17756         if( q == p ) *p++ = '-';
17757         *p++ = ']';
17758         *p++ = ' ';
17759     }
17760
17761     /* Active color */
17762     *p++ = whiteToPlay ? 'w' : 'b';
17763     *p++ = ' ';
17764
17765   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17766     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17767   } else {
17768   if(nrCastlingRights) {
17769      q = p;
17770      if(appData.fischerCastling) {
17771        /* [HGM] write directly from rights */
17772            if(boards[move][CASTLING][2] != NoRights &&
17773               boards[move][CASTLING][0] != NoRights   )
17774                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17775            if(boards[move][CASTLING][2] != NoRights &&
17776               boards[move][CASTLING][1] != NoRights   )
17777                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17778            if(boards[move][CASTLING][5] != NoRights &&
17779               boards[move][CASTLING][3] != NoRights   )
17780                 *p++ = boards[move][CASTLING][3] + AAA;
17781            if(boards[move][CASTLING][5] != NoRights &&
17782               boards[move][CASTLING][4] != NoRights   )
17783                 *p++ = boards[move][CASTLING][4] + AAA;
17784      } else {
17785
17786         /* [HGM] write true castling rights */
17787         if( nrCastlingRights == 6 ) {
17788             int q, k=0;
17789             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17790                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17791             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17792                  boards[move][CASTLING][2] != NoRights  );
17793             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17794                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17795                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17796                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17797                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17798             }
17799             if(q) *p++ = 'Q';
17800             k = 0;
17801             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17802                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17803             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17804                  boards[move][CASTLING][5] != NoRights  );
17805             if(gameInfo.variant == VariantSChess) {
17806                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17807                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17808                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17809                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17810             }
17811             if(q) *p++ = 'q';
17812         }
17813      }
17814      if (q == p) *p++ = '-'; /* No castling rights */
17815      *p++ = ' ';
17816   }
17817
17818   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17819      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17820      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17821     /* En passant target square */
17822     if (move > backwardMostMove) {
17823         fromX = moveList[move - 1][0] - AAA;
17824         fromY = moveList[move - 1][1] - ONE;
17825         toX = moveList[move - 1][2] - AAA;
17826         toY = moveList[move - 1][3] - ONE;
17827         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17828             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17829             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17830             fromX == toX) {
17831             /* 2-square pawn move just happened */
17832             *p++ = toX + AAA;
17833             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17834         } else {
17835             *p++ = '-';
17836         }
17837     } else if(move == backwardMostMove) {
17838         // [HGM] perhaps we should always do it like this, and forget the above?
17839         if((signed char)boards[move][EP_STATUS] >= 0) {
17840             *p++ = boards[move][EP_STATUS] + AAA;
17841             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17842         } else {
17843             *p++ = '-';
17844         }
17845     } else {
17846         *p++ = '-';
17847     }
17848     *p++ = ' ';
17849   }
17850   }
17851
17852     if(moveCounts)
17853     {   int i = 0, j=move;
17854
17855         /* [HGM] find reversible plies */
17856         if (appData.debugMode) { int k;
17857             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17858             for(k=backwardMostMove; k<=forwardMostMove; k++)
17859                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17860
17861         }
17862
17863         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17864         if( j == backwardMostMove ) i += initialRulePlies;
17865         sprintf(p, "%d ", i);
17866         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17867
17868         /* Fullmove number */
17869         sprintf(p, "%d", (move / 2) + 1);
17870     } else *--p = NULLCHAR;
17871
17872     return StrSave(buf);
17873 }
17874
17875 Boolean
17876 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17877 {
17878     int i, j, k, w=0, subst=0, shuffle=0;
17879     char *p, c;
17880     int emptycount, virgin[BOARD_FILES];
17881     ChessSquare piece;
17882
17883     p = fen;
17884
17885     /* Piece placement data */
17886     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17887         j = 0;
17888         for (;;) {
17889             if (*p == '/' || *p == ' ' || *p == '[' ) {
17890                 if(j > w) w = j;
17891                 emptycount = gameInfo.boardWidth - j;
17892                 while (emptycount--)
17893                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17894                 if (*p == '/') p++;
17895                 else if(autoSize) { // we stumbled unexpectedly into end of board
17896                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17897                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17898                     }
17899                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17900                 }
17901                 break;
17902 #if(BOARD_FILES >= 10)*0
17903             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17904                 p++; emptycount=10;
17905                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17906                 while (emptycount--)
17907                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17908 #endif
17909             } else if (*p == '*') {
17910                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17911             } else if (isdigit(*p)) {
17912                 emptycount = *p++ - '0';
17913                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17914                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17915                 while (emptycount--)
17916                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17917             } else if (*p == '<') {
17918                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17919                 else if (i != 0 || !shuffle) return FALSE;
17920                 p++;
17921             } else if (shuffle && *p == '>') {
17922                 p++; // for now ignore closing shuffle range, and assume rank-end
17923             } else if (*p == '?') {
17924                 if (j >= gameInfo.boardWidth) return FALSE;
17925                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17926                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17927             } else if (*p == '+' || isalpha(*p)) {
17928                 if (j >= gameInfo.boardWidth) return FALSE;
17929                 if(*p=='+') {
17930                     piece = CharToPiece(*++p);
17931                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17932                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17933                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17934                 } else piece = CharToPiece(*p++);
17935
17936                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17937                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17938                     piece = (ChessSquare) (PROMOTED piece);
17939                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17940                     p++;
17941                 }
17942                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17943             } else {
17944                 return FALSE;
17945             }
17946         }
17947     }
17948     while (*p == '/' || *p == ' ') p++;
17949
17950     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17951
17952     /* [HGM] by default clear Crazyhouse holdings, if present */
17953     if(gameInfo.holdingsWidth) {
17954        for(i=0; i<BOARD_HEIGHT; i++) {
17955            board[i][0]             = EmptySquare; /* black holdings */
17956            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17957            board[i][1]             = (ChessSquare) 0; /* black counts */
17958            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17959        }
17960     }
17961
17962     /* [HGM] look for Crazyhouse holdings here */
17963     while(*p==' ') p++;
17964     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17965         int swap=0, wcnt=0, bcnt=0;
17966         if(*p == '[') p++;
17967         if(*p == '<') swap++, p++;
17968         if(*p == '-' ) p++; /* empty holdings */ else {
17969             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17970             /* if we would allow FEN reading to set board size, we would   */
17971             /* have to add holdings and shift the board read so far here   */
17972             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17973                 p++;
17974                 if((int) piece >= (int) BlackPawn ) {
17975                     i = (int)piece - (int)BlackPawn;
17976                     i = PieceToNumber((ChessSquare)i);
17977                     if( i >= gameInfo.holdingsSize ) return FALSE;
17978                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17979                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17980                     bcnt++;
17981                 } else {
17982                     i = (int)piece - (int)WhitePawn;
17983                     i = PieceToNumber((ChessSquare)i);
17984                     if( i >= gameInfo.holdingsSize ) return FALSE;
17985                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17986                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17987                     wcnt++;
17988                 }
17989             }
17990             if(subst) { // substitute back-rank question marks by holdings pieces
17991                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17992                     int k, m, n = bcnt + 1;
17993                     if(board[0][j] == ClearBoard) {
17994                         if(!wcnt) return FALSE;
17995                         n = rand() % wcnt;
17996                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17997                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17998                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17999                             break;
18000                         }
18001                     }
18002                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18003                         if(!bcnt) return FALSE;
18004                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18005                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18006                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18007                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18008                             break;
18009                         }
18010                     }
18011                 }
18012                 subst = 0;
18013             }
18014         }
18015         if(*p == ']') p++;
18016     }
18017
18018     if(subst) return FALSE; // substitution requested, but no holdings
18019
18020     while(*p == ' ') p++;
18021
18022     /* Active color */
18023     c = *p++;
18024     if(appData.colorNickNames) {
18025       if( c == appData.colorNickNames[0] ) c = 'w'; else
18026       if( c == appData.colorNickNames[1] ) c = 'b';
18027     }
18028     switch (c) {
18029       case 'w':
18030         *blackPlaysFirst = FALSE;
18031         break;
18032       case 'b':
18033         *blackPlaysFirst = TRUE;
18034         break;
18035       default:
18036         return FALSE;
18037     }
18038
18039     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18040     /* return the extra info in global variiables             */
18041
18042     /* set defaults in case FEN is incomplete */
18043     board[EP_STATUS] = EP_UNKNOWN;
18044     for(i=0; i<nrCastlingRights; i++ ) {
18045         board[CASTLING][i] =
18046             appData.fischerCastling ? NoRights : initialRights[i];
18047     }   /* assume possible unless obviously impossible */
18048     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18049     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18050     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18051                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18052     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18053     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18054     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18055                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18056     FENrulePlies = 0;
18057
18058     while(*p==' ') p++;
18059     if(nrCastlingRights) {
18060       int fischer = 0;
18061       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18062       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18063           /* castling indicator present, so default becomes no castlings */
18064           for(i=0; i<nrCastlingRights; i++ ) {
18065                  board[CASTLING][i] = NoRights;
18066           }
18067       }
18068       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18069              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18070              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18071              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18072         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18073
18074         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18075             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18076             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18077         }
18078         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18079             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18080         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18081                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18082         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18083                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18084         switch(c) {
18085           case'K':
18086               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18087               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18088               board[CASTLING][2] = whiteKingFile;
18089               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18090               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18091               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18092               break;
18093           case'Q':
18094               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18095               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18096               board[CASTLING][2] = whiteKingFile;
18097               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18098               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18099               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18100               break;
18101           case'k':
18102               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18103               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18104               board[CASTLING][5] = blackKingFile;
18105               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18106               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18107               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18108               break;
18109           case'q':
18110               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18111               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18112               board[CASTLING][5] = blackKingFile;
18113               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18114               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18115               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18116           case '-':
18117               break;
18118           default: /* FRC castlings */
18119               if(c >= 'a') { /* black rights */
18120                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18121                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18122                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18123                   if(i == BOARD_RGHT) break;
18124                   board[CASTLING][5] = i;
18125                   c -= AAA;
18126                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18127                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18128                   if(c > i)
18129                       board[CASTLING][3] = c;
18130                   else
18131                       board[CASTLING][4] = c;
18132               } else { /* white rights */
18133                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18134                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18135                     if(board[0][i] == WhiteKing) break;
18136                   if(i == BOARD_RGHT) break;
18137                   board[CASTLING][2] = i;
18138                   c -= AAA - 'a' + 'A';
18139                   if(board[0][c] >= WhiteKing) break;
18140                   if(c > i)
18141                       board[CASTLING][0] = c;
18142                   else
18143                       board[CASTLING][1] = c;
18144               }
18145         }
18146       }
18147       for(i=0; i<nrCastlingRights; i++)
18148         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18149       if(gameInfo.variant == VariantSChess)
18150         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18151       if(fischer && shuffle) appData.fischerCastling = TRUE;
18152     if (appData.debugMode) {
18153         fprintf(debugFP, "FEN castling rights:");
18154         for(i=0; i<nrCastlingRights; i++)
18155         fprintf(debugFP, " %d", board[CASTLING][i]);
18156         fprintf(debugFP, "\n");
18157     }
18158
18159       while(*p==' ') p++;
18160     }
18161
18162     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18163
18164     /* read e.p. field in games that know e.p. capture */
18165     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18166        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18167        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18168       if(*p=='-') {
18169         p++; board[EP_STATUS] = EP_NONE;
18170       } else {
18171          char c = *p++ - AAA;
18172
18173          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18174          if(*p >= '0' && *p <='9') p++;
18175          board[EP_STATUS] = c;
18176       }
18177     }
18178
18179
18180     if(sscanf(p, "%d", &i) == 1) {
18181         FENrulePlies = i; /* 50-move ply counter */
18182         /* (The move number is still ignored)    */
18183     }
18184
18185     return TRUE;
18186 }
18187
18188 void
18189 EditPositionPasteFEN (char *fen)
18190 {
18191   if (fen != NULL) {
18192     Board initial_position;
18193
18194     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18195       DisplayError(_("Bad FEN position in clipboard"), 0);
18196       return ;
18197     } else {
18198       int savedBlackPlaysFirst = blackPlaysFirst;
18199       EditPositionEvent();
18200       blackPlaysFirst = savedBlackPlaysFirst;
18201       CopyBoard(boards[0], initial_position);
18202       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18203       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18204       DisplayBothClocks();
18205       DrawPosition(FALSE, boards[currentMove]);
18206     }
18207   }
18208 }
18209
18210 static char cseq[12] = "\\   ";
18211
18212 Boolean
18213 set_cont_sequence (char *new_seq)
18214 {
18215     int len;
18216     Boolean ret;
18217
18218     // handle bad attempts to set the sequence
18219         if (!new_seq)
18220                 return 0; // acceptable error - no debug
18221
18222     len = strlen(new_seq);
18223     ret = (len > 0) && (len < sizeof(cseq));
18224     if (ret)
18225       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18226     else if (appData.debugMode)
18227       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18228     return ret;
18229 }
18230
18231 /*
18232     reformat a source message so words don't cross the width boundary.  internal
18233     newlines are not removed.  returns the wrapped size (no null character unless
18234     included in source message).  If dest is NULL, only calculate the size required
18235     for the dest buffer.  lp argument indicats line position upon entry, and it's
18236     passed back upon exit.
18237 */
18238 int
18239 wrap (char *dest, char *src, int count, int width, int *lp)
18240 {
18241     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18242
18243     cseq_len = strlen(cseq);
18244     old_line = line = *lp;
18245     ansi = len = clen = 0;
18246
18247     for (i=0; i < count; i++)
18248     {
18249         if (src[i] == '\033')
18250             ansi = 1;
18251
18252         // if we hit the width, back up
18253         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18254         {
18255             // store i & len in case the word is too long
18256             old_i = i, old_len = len;
18257
18258             // find the end of the last word
18259             while (i && src[i] != ' ' && src[i] != '\n')
18260             {
18261                 i--;
18262                 len--;
18263             }
18264
18265             // word too long?  restore i & len before splitting it
18266             if ((old_i-i+clen) >= width)
18267             {
18268                 i = old_i;
18269                 len = old_len;
18270             }
18271
18272             // extra space?
18273             if (i && src[i-1] == ' ')
18274                 len--;
18275
18276             if (src[i] != ' ' && src[i] != '\n')
18277             {
18278                 i--;
18279                 if (len)
18280                     len--;
18281             }
18282
18283             // now append the newline and continuation sequence
18284             if (dest)
18285                 dest[len] = '\n';
18286             len++;
18287             if (dest)
18288                 strncpy(dest+len, cseq, cseq_len);
18289             len += cseq_len;
18290             line = cseq_len;
18291             clen = cseq_len;
18292             continue;
18293         }
18294
18295         if (dest)
18296             dest[len] = src[i];
18297         len++;
18298         if (!ansi)
18299             line++;
18300         if (src[i] == '\n')
18301             line = 0;
18302         if (src[i] == 'm')
18303             ansi = 0;
18304     }
18305     if (dest && appData.debugMode)
18306     {
18307         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18308             count, width, line, len, *lp);
18309         show_bytes(debugFP, src, count);
18310         fprintf(debugFP, "\ndest: ");
18311         show_bytes(debugFP, dest, len);
18312         fprintf(debugFP, "\n");
18313     }
18314     *lp = dest ? line : old_line;
18315
18316     return len;
18317 }
18318
18319 // [HGM] vari: routines for shelving variations
18320 Boolean modeRestore = FALSE;
18321
18322 void
18323 PushInner (int firstMove, int lastMove)
18324 {
18325         int i, j, nrMoves = lastMove - firstMove;
18326
18327         // push current tail of game on stack
18328         savedResult[storedGames] = gameInfo.result;
18329         savedDetails[storedGames] = gameInfo.resultDetails;
18330         gameInfo.resultDetails = NULL;
18331         savedFirst[storedGames] = firstMove;
18332         savedLast [storedGames] = lastMove;
18333         savedFramePtr[storedGames] = framePtr;
18334         framePtr -= nrMoves; // reserve space for the boards
18335         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18336             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18337             for(j=0; j<MOVE_LEN; j++)
18338                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18339             for(j=0; j<2*MOVE_LEN; j++)
18340                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18341             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18342             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18343             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18344             pvInfoList[firstMove+i-1].depth = 0;
18345             commentList[framePtr+i] = commentList[firstMove+i];
18346             commentList[firstMove+i] = NULL;
18347         }
18348
18349         storedGames++;
18350         forwardMostMove = firstMove; // truncate game so we can start variation
18351 }
18352
18353 void
18354 PushTail (int firstMove, int lastMove)
18355 {
18356         if(appData.icsActive) { // only in local mode
18357                 forwardMostMove = currentMove; // mimic old ICS behavior
18358                 return;
18359         }
18360         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18361
18362         PushInner(firstMove, lastMove);
18363         if(storedGames == 1) GreyRevert(FALSE);
18364         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18365 }
18366
18367 void
18368 PopInner (Boolean annotate)
18369 {
18370         int i, j, nrMoves;
18371         char buf[8000], moveBuf[20];
18372
18373         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18374         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18375         nrMoves = savedLast[storedGames] - currentMove;
18376         if(annotate) {
18377                 int cnt = 10;
18378                 if(!WhiteOnMove(currentMove))
18379                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18380                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18381                 for(i=currentMove; i<forwardMostMove; i++) {
18382                         if(WhiteOnMove(i))
18383                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18384                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18385                         strcat(buf, moveBuf);
18386                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18387                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18388                 }
18389                 strcat(buf, ")");
18390         }
18391         for(i=1; i<=nrMoves; i++) { // copy last variation back
18392             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18393             for(j=0; j<MOVE_LEN; j++)
18394                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18395             for(j=0; j<2*MOVE_LEN; j++)
18396                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18397             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18398             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18399             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18400             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18401             commentList[currentMove+i] = commentList[framePtr+i];
18402             commentList[framePtr+i] = NULL;
18403         }
18404         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18405         framePtr = savedFramePtr[storedGames];
18406         gameInfo.result = savedResult[storedGames];
18407         if(gameInfo.resultDetails != NULL) {
18408             free(gameInfo.resultDetails);
18409       }
18410         gameInfo.resultDetails = savedDetails[storedGames];
18411         forwardMostMove = currentMove + nrMoves;
18412 }
18413
18414 Boolean
18415 PopTail (Boolean annotate)
18416 {
18417         if(appData.icsActive) return FALSE; // only in local mode
18418         if(!storedGames) return FALSE; // sanity
18419         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18420
18421         PopInner(annotate);
18422         if(currentMove < forwardMostMove) ForwardEvent(); else
18423         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18424
18425         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18426         return TRUE;
18427 }
18428
18429 void
18430 CleanupTail ()
18431 {       // remove all shelved variations
18432         int i;
18433         for(i=0; i<storedGames; i++) {
18434             if(savedDetails[i])
18435                 free(savedDetails[i]);
18436             savedDetails[i] = NULL;
18437         }
18438         for(i=framePtr; i<MAX_MOVES; i++) {
18439                 if(commentList[i]) free(commentList[i]);
18440                 commentList[i] = NULL;
18441         }
18442         framePtr = MAX_MOVES-1;
18443         storedGames = 0;
18444 }
18445
18446 void
18447 LoadVariation (int index, char *text)
18448 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18449         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18450         int level = 0, move;
18451
18452         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18453         // first find outermost bracketing variation
18454         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18455             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18456                 if(*p == '{') wait = '}'; else
18457                 if(*p == '[') wait = ']'; else
18458                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18459                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18460             }
18461             if(*p == wait) wait = NULLCHAR; // closing ]} found
18462             p++;
18463         }
18464         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18465         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18466         end[1] = NULLCHAR; // clip off comment beyond variation
18467         ToNrEvent(currentMove-1);
18468         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18469         // kludge: use ParsePV() to append variation to game
18470         move = currentMove;
18471         ParsePV(start, TRUE, TRUE);
18472         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18473         ClearPremoveHighlights();
18474         CommentPopDown();
18475         ToNrEvent(currentMove+1);
18476 }
18477
18478 void
18479 LoadTheme ()
18480 {
18481     char *p, *q, buf[MSG_SIZ];
18482     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18483         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18484         ParseArgsFromString(buf);
18485         ActivateTheme(TRUE); // also redo colors
18486         return;
18487     }
18488     p = nickName;
18489     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18490     {
18491         int len;
18492         q = appData.themeNames;
18493         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18494       if(appData.useBitmaps) {
18495         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18496                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18497                 appData.liteBackTextureMode,
18498                 appData.darkBackTextureMode );
18499       } else {
18500         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18501                 Col2Text(2),   // lightSquareColor
18502                 Col2Text(3) ); // darkSquareColor
18503       }
18504       if(appData.useBorder) {
18505         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18506                 appData.border);
18507       } else {
18508         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18509       }
18510       if(appData.useFont) {
18511         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18512                 appData.renderPiecesWithFont,
18513                 appData.fontToPieceTable,
18514                 Col2Text(9),    // appData.fontBackColorWhite
18515                 Col2Text(10) ); // appData.fontForeColorBlack
18516       } else {
18517         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18518                 appData.pieceDirectory);
18519         if(!appData.pieceDirectory[0])
18520           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18521                 Col2Text(0),   // whitePieceColor
18522                 Col2Text(1) ); // blackPieceColor
18523       }
18524       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18525                 Col2Text(4),   // highlightSquareColor
18526                 Col2Text(5) ); // premoveHighlightColor
18527         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18528         if(insert != q) insert[-1] = NULLCHAR;
18529         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18530         if(q)   free(q);
18531     }
18532     ActivateTheme(FALSE);
18533 }