Also display channel tell in ICS Console during private chat
[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   default:
417     break;
418   }
419   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
420   return flags;
421 }
422
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
425
426 /*
427     [AS] Note: sometimes, the sscanf() function is used to parse the input
428     into a fixed-size buffer. Because of this, we must be prepared to
429     receive strings as long as the size of the input buffer, which is currently
430     set to 4K for Windows and 8K for the rest.
431     So, we must either allocate sufficiently large buffers here, or
432     reduce the size of the input buffer in the input reading part.
433 */
434
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
438
439 ChessProgramState first, second, pairing;
440
441 /* premove variables */
442 int premoveToX = 0;
443 int premoveToY = 0;
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
447 int gotPremove = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
450
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
453
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
481
482 int have_sent_ICS_logon = 0;
483 int movesPerSession;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
495
496 /* animateTraining preserves the state of appData.animate
497  * when Training mode is activated. This allows the
498  * response to be animated when appData.animate == TRUE and
499  * appData.animateDragging == TRUE.
500  */
501 Boolean animateTraining;
502
503 GameInfo gameInfo;
504
505 AppData appData;
506
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char  initialRights[BOARD_FILES];
511 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int   initialRulePlies, FENrulePlies;
513 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 int loadFlag = 0;
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
517
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int storedGames = 0;
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
527
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
533
534 ChessSquare  FIDEArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538         BlackKing, BlackBishop, BlackKnight, BlackRook }
539 };
540
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackKing, BlackKnight, BlackRook }
546 };
547
548 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551     { BlackRook, BlackMan, BlackBishop, BlackQueen,
552         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 };
554
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 };
561
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 };
568
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackMan, BlackFerz,
580         BlackKing, BlackMan, BlackKnight, BlackRook }
581 };
582
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare  lionArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593     { BlackRook, BlackLion, BlackBishop, BlackQueen,
594         BlackKing, BlackBishop, BlackKnight, BlackRook }
595 };
596
597
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 };
605
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 };
612
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 };
619
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 };
626
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 };
633
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 };
640
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
646 };
647
648 #ifdef GOTHIC
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 };
655 #else // !GOTHIC
656 #define GothicArray CapablancaArray
657 #endif // !GOTHIC
658
659 #ifdef FALCON
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 };
666 #else // !FALCON
667 #define FalconArray CapablancaArray
668 #endif // !FALCON
669
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
676
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 };
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 };
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
702
703
704 Board initialPosition;
705
706
707 /* Convert str to a rating. Checks for special cases of "----",
708
709    "++++", etc. Also strips ()'s */
710 int
711 string_to_rating (char *str)
712 {
713   while(*str && !isdigit(*str)) ++str;
714   if (!*str)
715     return 0;   /* One of the special "no rating" cases */
716   else
717     return atoi(str);
718 }
719
720 void
721 ClearProgramStats ()
722 {
723     /* Init programStats */
724     programStats.movelist[0] = 0;
725     programStats.depth = 0;
726     programStats.nr_moves = 0;
727     programStats.moves_left = 0;
728     programStats.nodes = 0;
729     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
730     programStats.score = 0;
731     programStats.got_only_move = 0;
732     programStats.got_fail = 0;
733     programStats.line_is_book = 0;
734 }
735
736 void
737 CommonEngineInit ()
738 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739     if (appData.firstPlaysBlack) {
740         first.twoMachinesColor = "black\n";
741         second.twoMachinesColor = "white\n";
742     } else {
743         first.twoMachinesColor = "white\n";
744         second.twoMachinesColor = "black\n";
745     }
746
747     first.other = &second;
748     second.other = &first;
749
750     { float norm = 1;
751         if(appData.timeOddsMode) {
752             norm = appData.timeOdds[0];
753             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754         }
755         first.timeOdds  = appData.timeOdds[0]/norm;
756         second.timeOdds = appData.timeOdds[1]/norm;
757     }
758
759     if(programVersion) free(programVersion);
760     if (appData.noChessProgram) {
761         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762         sprintf(programVersion, "%s", PACKAGE_STRING);
763     } else {
764       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
767     }
768 }
769
770 void
771 UnloadEngine (ChessProgramState *cps)
772 {
773         /* Kill off first chess program */
774         if (cps->isr != NULL)
775           RemoveInputSource(cps->isr);
776         cps->isr = NULL;
777
778         if (cps->pr != NoProc) {
779             ExitAnalyzeMode();
780             DoSleep( appData.delayBeforeQuit );
781             SendToProgram("quit\n", cps);
782             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783         }
784         cps->pr = NoProc;
785         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
786 }
787
788 void
789 ClearOptions (ChessProgramState *cps)
790 {
791     int i;
792     cps->nrOptions = cps->comboCnt = 0;
793     for(i=0; i<MAX_OPTIONS; i++) {
794         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795         cps->option[i].textValue = 0;
796     }
797 }
798
799 char *engineNames[] = {
800   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 N_("first"),
803   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
805 N_("second")
806 };
807
808 void
809 InitEngine (ChessProgramState *cps, int n)
810 {   // [HGM] all engine initialiation put in a function that does one engine
811
812     ClearOptions(cps);
813
814     cps->which = engineNames[n];
815     cps->maybeThinking = FALSE;
816     cps->pr = NoProc;
817     cps->isr = NULL;
818     cps->sendTime = 2;
819     cps->sendDrawOffers = 1;
820
821     cps->program = appData.chessProgram[n];
822     cps->host = appData.host[n];
823     cps->dir = appData.directory[n];
824     cps->initString = appData.engInitString[n];
825     cps->computerString = appData.computerString[n];
826     cps->useSigint  = TRUE;
827     cps->useSigterm = TRUE;
828     cps->reuse = appData.reuse[n];
829     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
830     cps->useSetboard = FALSE;
831     cps->useSAN = FALSE;
832     cps->usePing = FALSE;
833     cps->lastPing = 0;
834     cps->lastPong = 0;
835     cps->usePlayother = FALSE;
836     cps->useColors = TRUE;
837     cps->useUsermove = FALSE;
838     cps->sendICS = FALSE;
839     cps->sendName = appData.icsActive;
840     cps->sdKludge = FALSE;
841     cps->stKludge = FALSE;
842     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843     TidyProgramName(cps->program, cps->host, cps->tidy);
844     cps->matchWins = 0;
845     ASSIGN(cps->variants, appData.variant);
846     cps->analysisSupport = 2; /* detect */
847     cps->analyzing = FALSE;
848     cps->initDone = FALSE;
849     cps->reload = FALSE;
850
851     /* New features added by Tord: */
852     cps->useFEN960 = FALSE;
853     cps->useOOCastle = TRUE;
854     /* End of new features added by Tord. */
855     cps->fenOverride  = appData.fenOverride[n];
856
857     /* [HGM] time odds: set factor for each machine */
858     cps->timeOdds  = appData.timeOdds[n];
859
860     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861     cps->accumulateTC = appData.accumulateTC[n];
862     cps->maxNrOfSessions = 1;
863
864     /* [HGM] debug */
865     cps->debug = FALSE;
866
867     cps->drawDepth = appData.drawDepth[n];
868     cps->supportsNPS = UNKNOWN;
869     cps->memSize = FALSE;
870     cps->maxCores = FALSE;
871     ASSIGN(cps->egtFormats, "");
872
873     /* [HGM] options */
874     cps->optionSettings  = appData.engOptions[n];
875
876     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877     cps->isUCI = appData.isUCI[n]; /* [AS] */
878     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879     cps->highlight = 0;
880
881     if (appData.protocolVersion[n] > PROTOVER
882         || appData.protocolVersion[n] < 1)
883       {
884         char buf[MSG_SIZ];
885         int len;
886
887         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888                        appData.protocolVersion[n]);
889         if( (len >= MSG_SIZ) && appData.debugMode )
890           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891
892         DisplayFatalError(buf, 0, 2);
893       }
894     else
895       {
896         cps->protocolVersion = appData.protocolVersion[n];
897       }
898
899     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
900     ParseFeatures(appData.featureDefaults, cps);
901 }
902
903 ChessProgramState *savCps;
904
905 GameMode oldMode;
906
907 void
908 LoadEngine ()
909 {
910     int i;
911     if(WaitForEngine(savCps, LoadEngine)) return;
912     CommonEngineInit(); // recalculate time odds
913     if(gameInfo.variant != StringToVariant(appData.variant)) {
914         // we changed variant when loading the engine; this forces us to reset
915         Reset(TRUE, savCps != &first);
916         oldMode = BeginningOfGame; // to prevent restoring old mode
917     }
918     InitChessProgram(savCps, FALSE);
919     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920     DisplayMessage("", "");
921     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923     ThawUI();
924     SetGNUMode();
925     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
926 }
927
928 void
929 ReplaceEngine (ChessProgramState *cps, int n)
930 {
931     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932     keepInfo = 1;
933     if(oldMode != BeginningOfGame) EditGameEvent();
934     keepInfo = 0;
935     UnloadEngine(cps);
936     appData.noChessProgram = FALSE;
937     appData.clockMode = TRUE;
938     InitEngine(cps, n);
939     UpdateLogos(TRUE);
940     if(n) return; // only startup first engine immediately; second can wait
941     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
942     LoadEngine();
943 }
944
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947
948 static char resetOptions[] =
949         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953
954 void
955 FloatToFront(char **list, char *engineLine)
956 {
957     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958     int i=0;
959     if(appData.recentEngines <= 0) return;
960     TidyProgramName(engineLine, "localhost", tidy+1);
961     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962     strncpy(buf+1, *list, MSG_SIZ-50);
963     if(p = strstr(buf, tidy)) { // tidy name appears in list
964         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965         while(*p++ = *++q); // squeeze out
966     }
967     strcat(tidy, buf+1); // put list behind tidy name
968     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970     ASSIGN(*list, tidy+1);
971 }
972
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974
975 void
976 Load (ChessProgramState *cps, int i)
977 {
978     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984         appData.firstProtocolVersion = PROTOVER;
985         ParseArgsFromString(buf);
986         SwapEngines(i);
987         ReplaceEngine(cps, i);
988         FloatToFront(&appData.recentEngineList, engineLine);
989         return;
990     }
991     p = engineName;
992     while(q = strchr(p, SLASH)) p = q+1;
993     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994     if(engineDir[0] != NULLCHAR) {
995         ASSIGN(appData.directory[i], engineDir); p = engineName;
996     } else if(p != engineName) { // derive directory from engine path, when not given
997         p[-1] = 0;
998         ASSIGN(appData.directory[i], engineName);
999         p[-1] = SLASH;
1000         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001     } else { ASSIGN(appData.directory[i], "."); }
1002     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003     if(params[0]) {
1004         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005         snprintf(command, MSG_SIZ, "%s %s", p, params);
1006         p = command;
1007     }
1008     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009     ASSIGN(appData.chessProgram[i], p);
1010     appData.isUCI[i] = isUCI;
1011     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012     appData.hasOwnBookUCI[i] = hasBook;
1013     if(!nickName[0]) useNick = FALSE;
1014     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1015     if(addToList) {
1016         int len;
1017         char quote;
1018         q = firstChessProgramNames;
1019         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022                         quote, p, quote, appData.directory[i],
1023                         useNick ? " -fn \"" : "",
1024                         useNick ? nickName : "",
1025                         useNick ? "\"" : "",
1026                         v1 ? " -firstProtocolVersion 1" : "",
1027                         hasBook ? "" : " -fNoOwnBookUCI",
1028                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029                         storeVariant ? " -variant " : "",
1030                         storeVariant ? VariantName(gameInfo.variant) : "");
1031         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033         if(insert != q) insert[-1] = NULLCHAR;
1034         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035         if(q)   free(q);
1036         FloatToFront(&appData.recentEngineList, buf);
1037     }
1038     ReplaceEngine(cps, i);
1039 }
1040
1041 void
1042 InitTimeControls ()
1043 {
1044     int matched, min, sec;
1045     /*
1046      * Parse timeControl resource
1047      */
1048     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049                           appData.movesPerSession)) {
1050         char buf[MSG_SIZ];
1051         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052         DisplayFatalError(buf, 0, 2);
1053     }
1054
1055     /*
1056      * Parse searchTime resource
1057      */
1058     if (*appData.searchTime != NULLCHAR) {
1059         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060         if (matched == 1) {
1061             searchTime = min * 60;
1062         } else if (matched == 2) {
1063             searchTime = min * 60 + sec;
1064         } else {
1065             char buf[MSG_SIZ];
1066             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067             DisplayFatalError(buf, 0, 2);
1068         }
1069     }
1070 }
1071
1072 void
1073 InitBackEnd1 ()
1074 {
1075
1076     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078
1079     GetTimeMark(&programStartTime);
1080     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081     appData.seedBase = random() + (random()<<15);
1082     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083
1084     ClearProgramStats();
1085     programStats.ok_to_send = 1;
1086     programStats.seen_stat = 0;
1087
1088     /*
1089      * Initialize game list
1090      */
1091     ListNew(&gameList);
1092
1093
1094     /*
1095      * Internet chess server status
1096      */
1097     if (appData.icsActive) {
1098         appData.matchMode = FALSE;
1099         appData.matchGames = 0;
1100 #if ZIPPY
1101         appData.noChessProgram = !appData.zippyPlay;
1102 #else
1103         appData.zippyPlay = FALSE;
1104         appData.zippyTalk = FALSE;
1105         appData.noChessProgram = TRUE;
1106 #endif
1107         if (*appData.icsHelper != NULLCHAR) {
1108             appData.useTelnet = TRUE;
1109             appData.telnetProgram = appData.icsHelper;
1110         }
1111     } else {
1112         appData.zippyTalk = appData.zippyPlay = FALSE;
1113     }
1114
1115     /* [AS] Initialize pv info list [HGM] and game state */
1116     {
1117         int i, j;
1118
1119         for( i=0; i<=framePtr; i++ ) {
1120             pvInfoList[i].depth = -1;
1121             boards[i][EP_STATUS] = EP_NONE;
1122             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1123         }
1124     }
1125
1126     InitTimeControls();
1127
1128     /* [AS] Adjudication threshold */
1129     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130
1131     InitEngine(&first, 0);
1132     InitEngine(&second, 1);
1133     CommonEngineInit();
1134
1135     pairing.which = "pairing"; // pairing engine
1136     pairing.pr = NoProc;
1137     pairing.isr = NULL;
1138     pairing.program = appData.pairingEngine;
1139     pairing.host = "localhost";
1140     pairing.dir = ".";
1141
1142     if (appData.icsActive) {
1143         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1144     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145         appData.clockMode = FALSE;
1146         first.sendTime = second.sendTime = 0;
1147     }
1148
1149 #if ZIPPY
1150     /* Override some settings from environment variables, for backward
1151        compatibility.  Unfortunately it's not feasible to have the env
1152        vars just set defaults, at least in xboard.  Ugh.
1153     */
1154     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1155       ZippyInit();
1156     }
1157 #endif
1158
1159     if (!appData.icsActive) {
1160       char buf[MSG_SIZ];
1161       int len;
1162
1163       /* Check for variants that are supported only in ICS mode,
1164          or not at all.  Some that are accepted here nevertheless
1165          have bugs; see comments below.
1166       */
1167       VariantClass variant = StringToVariant(appData.variant);
1168       switch (variant) {
1169       case VariantBughouse:     /* need four players and two boards */
1170       case VariantKriegspiel:   /* need to hide pieces and move details */
1171         /* case VariantFischeRandom: (Fabien: moved below) */
1172         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173         if( (len >= MSG_SIZ) && appData.debugMode )
1174           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175
1176         DisplayFatalError(buf, 0, 2);
1177         return;
1178
1179       case VariantUnknown:
1180       case VariantLoadable:
1181       case Variant29:
1182       case Variant30:
1183       case Variant31:
1184       case Variant32:
1185       case Variant33:
1186       case Variant34:
1187       case Variant35:
1188       case Variant36:
1189       default:
1190         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191         if( (len >= MSG_SIZ) && appData.debugMode )
1192           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193
1194         DisplayFatalError(buf, 0, 2);
1195         return;
1196
1197       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1198       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1199       case VariantGothic:     /* [HGM] should work */
1200       case VariantCapablanca: /* [HGM] should work */
1201       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1202       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1203       case VariantChu:        /* [HGM] experimental */
1204       case VariantKnightmate: /* [HGM] should work */
1205       case VariantCylinder:   /* [HGM] untested */
1206       case VariantFalcon:     /* [HGM] untested */
1207       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208                                  offboard interposition not understood */
1209       case VariantNormal:     /* definitely works! */
1210       case VariantWildCastle: /* pieces not automatically shuffled */
1211       case VariantNoCastle:   /* pieces not automatically shuffled */
1212       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213       case VariantLosers:     /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantSuicide:    /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantGiveaway:   /* should work except for win condition,
1218                                  and doesn't know captures are mandatory */
1219       case VariantTwoKings:   /* should work */
1220       case VariantAtomic:     /* should work except for win condition */
1221       case Variant3Check:     /* should work except for win condition */
1222       case VariantShatranj:   /* should work except for all win conditions */
1223       case VariantMakruk:     /* should work except for draw countdown */
1224       case VariantASEAN :     /* should work except for draw countdown */
1225       case VariantBerolina:   /* might work if TestLegality is off */
1226       case VariantCapaRandom: /* should work */
1227       case VariantJanus:      /* should work */
1228       case VariantSuper:      /* experimental */
1229       case VariantGreat:      /* experimental, requires legality testing to be off */
1230       case VariantSChess:     /* S-Chess, should work */
1231       case VariantGrand:      /* should work */
1232       case VariantSpartan:    /* should work */
1233       case VariantLion:       /* should work */
1234       case VariantChuChess:   /* should work */
1235         break;
1236       }
1237     }
1238
1239 }
1240
1241 int
1242 NextIntegerFromString (char ** str, long * value)
1243 {
1244     int result = -1;
1245     char * s = *str;
1246
1247     while( *s == ' ' || *s == '\t' ) {
1248         s++;
1249     }
1250
1251     *value = 0;
1252
1253     if( *s >= '0' && *s <= '9' ) {
1254         while( *s >= '0' && *s <= '9' ) {
1255             *value = *value * 10 + (*s - '0');
1256             s++;
1257         }
1258
1259         result = 0;
1260     }
1261
1262     *str = s;
1263
1264     return result;
1265 }
1266
1267 int
1268 NextTimeControlFromString (char ** str, long * value)
1269 {
1270     long temp;
1271     int result = NextIntegerFromString( str, &temp );
1272
1273     if( result == 0 ) {
1274         *value = temp * 60; /* Minutes */
1275         if( **str == ':' ) {
1276             (*str)++;
1277             result = NextIntegerFromString( str, &temp );
1278             *value += temp; /* Seconds */
1279         }
1280     }
1281
1282     return result;
1283 }
1284
1285 int
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288     int result = -1, type = 0; long temp, temp2;
1289
1290     if(**str != ':') return -1; // old params remain in force!
1291     (*str)++;
1292     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293     if( NextIntegerFromString( str, &temp ) ) return -1;
1294     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295
1296     if(**str != '/') {
1297         /* time only: incremental or sudden-death time control */
1298         if(**str == '+') { /* increment follows; read it */
1299             (*str)++;
1300             if(**str == '!') type = *(*str)++; // Bronstein TC
1301             if(result = NextIntegerFromString( str, &temp2)) return -1;
1302             *inc = temp2 * 1000;
1303             if(**str == '.') { // read fraction of increment
1304                 char *start = ++(*str);
1305                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306                 temp2 *= 1000;
1307                 while(start++ < *str) temp2 /= 10;
1308                 *inc += temp2;
1309             }
1310         } else *inc = 0;
1311         *moves = 0; *tc = temp * 1000; *incType = type;
1312         return 0;
1313     }
1314
1315     (*str)++; /* classical time control */
1316     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1317
1318     if(result == 0) {
1319         *moves = temp;
1320         *tc    = temp2 * 1000;
1321         *inc   = 0;
1322         *incType = type;
1323     }
1324     return result;
1325 }
1326
1327 int
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 {   /* [HGM] get time to add from the multi-session time-control string */
1330     int incType, moves=1; /* kludge to force reading of first session */
1331     long time, increment;
1332     char *s = tcString;
1333
1334     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335     do {
1336         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338         if(movenr == -1) return time;    /* last move before new session     */
1339         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341         if(!moves) return increment;     /* current session is incremental   */
1342         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343     } while(movenr >= -1);               /* try again for next session       */
1344
1345     return 0; // no new time quota on this move
1346 }
1347
1348 int
1349 ParseTimeControl (char *tc, float ti, int mps)
1350 {
1351   long tc1;
1352   long tc2;
1353   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354   int min, sec=0;
1355
1356   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1359   if(ti > 0) {
1360
1361     if(mps)
1362       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363     else
1364       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365   } else {
1366     if(mps)
1367       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368     else
1369       snprintf(buf, MSG_SIZ, ":%s", mytc);
1370   }
1371   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372
1373   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1374     return FALSE;
1375   }
1376
1377   if( *tc == '/' ) {
1378     /* Parse second time control */
1379     tc++;
1380
1381     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1382       return FALSE;
1383     }
1384
1385     if( tc2 == 0 ) {
1386       return FALSE;
1387     }
1388
1389     timeControl_2 = tc2 * 1000;
1390   }
1391   else {
1392     timeControl_2 = 0;
1393   }
1394
1395   if( tc1 == 0 ) {
1396     return FALSE;
1397   }
1398
1399   timeControl = tc1 * 1000;
1400
1401   if (ti >= 0) {
1402     timeIncrement = ti * 1000;  /* convert to ms */
1403     movesPerSession = 0;
1404   } else {
1405     timeIncrement = 0;
1406     movesPerSession = mps;
1407   }
1408   return TRUE;
1409 }
1410
1411 void
1412 InitBackEnd2 ()
1413 {
1414     if (appData.debugMode) {
1415 #    ifdef __GIT_VERSION
1416       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 #    else
1418       fprintf(debugFP, "Version: %s\n", programVersion);
1419 #    endif
1420     }
1421     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422
1423     set_cont_sequence(appData.wrapContSeq);
1424     if (appData.matchGames > 0) {
1425         appData.matchMode = TRUE;
1426     } else if (appData.matchMode) {
1427         appData.matchGames = 1;
1428     }
1429     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430         appData.matchGames = appData.sameColorGames;
1431     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434     }
1435     Reset(TRUE, FALSE);
1436     if (appData.noChessProgram || first.protocolVersion == 1) {
1437       InitBackEnd3();
1438     } else {
1439       /* kludge: allow timeout for initial "feature" commands */
1440       FreezeUI();
1441       DisplayMessage("", _("Starting chess program"));
1442       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1443     }
1444 }
1445
1446 int
1447 CalculateIndex (int index, int gameNr)
1448 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449     int res;
1450     if(index > 0) return index; // fixed nmber
1451     if(index == 0) return 1;
1452     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1454     return res;
1455 }
1456
1457 int
1458 LoadGameOrPosition (int gameNr)
1459 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460     if (*appData.loadGameFile != NULLCHAR) {
1461         if (!LoadGameFromFile(appData.loadGameFile,
1462                 CalculateIndex(appData.loadGameIndex, gameNr),
1463                               appData.loadGameFile, FALSE)) {
1464             DisplayFatalError(_("Bad game file"), 0, 1);
1465             return 0;
1466         }
1467     } else if (*appData.loadPositionFile != NULLCHAR) {
1468         if (!LoadPositionFromFile(appData.loadPositionFile,
1469                 CalculateIndex(appData.loadPositionIndex, gameNr),
1470                                   appData.loadPositionFile)) {
1471             DisplayFatalError(_("Bad position file"), 0, 1);
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 void
1479 ReserveGame (int gameNr, char resChar)
1480 {
1481     FILE *tf = fopen(appData.tourneyFile, "r+");
1482     char *p, *q, c, buf[MSG_SIZ];
1483     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484     safeStrCpy(buf, lastMsg, MSG_SIZ);
1485     DisplayMessage(_("Pick new game"), "");
1486     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487     ParseArgsFromFile(tf);
1488     p = q = appData.results;
1489     if(appData.debugMode) {
1490       char *r = appData.participants;
1491       fprintf(debugFP, "results = '%s'\n", p);
1492       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493       fprintf(debugFP, "\n");
1494     }
1495     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496     nextGame = q - p;
1497     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498     safeStrCpy(q, p, strlen(p) + 2);
1499     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503         q[nextGame] = '*';
1504     }
1505     fseek(tf, -(strlen(p)+4), SEEK_END);
1506     c = fgetc(tf);
1507     if(c != '"') // depending on DOS or Unix line endings we can be one off
1508          fseek(tf, -(strlen(p)+2), SEEK_END);
1509     else fseek(tf, -(strlen(p)+3), SEEK_END);
1510     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511     DisplayMessage(buf, "");
1512     free(p); appData.results = q;
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515       int round = appData.defaultMatchGames * appData.tourneyType;
1516       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1517          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518         UnloadEngine(&first);  // next game belongs to other pairing;
1519         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520     }
1521     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1522 }
1523
1524 void
1525 MatchEvent (int mode)
1526 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527         int dummy;
1528         if(matchMode) { // already in match mode: switch it off
1529             abortMatch = TRUE;
1530             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531             return;
1532         }
1533 //      if(gameMode != BeginningOfGame) {
1534 //          DisplayError(_("You can only start a match from the initial position."), 0);
1535 //          return;
1536 //      }
1537         abortMatch = FALSE;
1538         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539         /* Set up machine vs. machine match */
1540         nextGame = 0;
1541         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542         if(appData.tourneyFile[0]) {
1543             ReserveGame(-1, 0);
1544             if(nextGame > appData.matchGames) {
1545                 char buf[MSG_SIZ];
1546                 if(strchr(appData.results, '*') == NULL) {
1547                     FILE *f;
1548                     appData.tourneyCycles++;
1549                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550                         fclose(f);
1551                         NextTourneyGame(-1, &dummy);
1552                         ReserveGame(-1, 0);
1553                         if(nextGame <= appData.matchGames) {
1554                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555                             matchMode = mode;
1556                             ScheduleDelayedEvent(NextMatchGame, 10000);
1557                             return;
1558                         }
1559                     }
1560                 }
1561                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562                 DisplayError(buf, 0);
1563                 appData.tourneyFile[0] = 0;
1564                 return;
1565             }
1566         } else
1567         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1568             DisplayFatalError(_("Can't have a match with no chess programs"),
1569                               0, 2);
1570             return;
1571         }
1572         matchMode = mode;
1573         matchGame = roundNr = 1;
1574         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1575         NextMatchGame();
1576 }
1577
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579
1580 void
1581 InitBackEnd3 P((void))
1582 {
1583     GameMode initialMode;
1584     char buf[MSG_SIZ];
1585     int err, len;
1586
1587     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1588        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1589         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1590        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1591        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592         char c, *q = first.variants, *p = strchr(q, ',');
1593         if(p) *p = NULLCHAR;
1594         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595             int w, h, s;
1596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599             Reset(TRUE, FALSE);         // and re-initialize
1600         }
1601         if(p) *p = ',';
1602     }
1603
1604     InitChessProgram(&first, startedFromSetupPosition);
1605
1606     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1607         free(programVersion);
1608         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611     }
1612
1613     if (appData.icsActive) {
1614 #ifdef WIN32
1615         /* [DM] Make a console window if needed [HGM] merged ifs */
1616         ConsoleCreate();
1617 #endif
1618         err = establish();
1619         if (err != 0)
1620           {
1621             if (*appData.icsCommPort != NULLCHAR)
1622               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623                              appData.icsCommPort);
1624             else
1625               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626                         appData.icsHost, appData.icsPort);
1627
1628             if( (len >= MSG_SIZ) && appData.debugMode )
1629               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631             DisplayFatalError(buf, err, 1);
1632             return;
1633         }
1634         SetICSMode();
1635         telnetISR =
1636           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637         fromUserISR =
1638           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641     } else if (appData.noChessProgram) {
1642         SetNCPMode();
1643     } else {
1644         SetGNUMode();
1645     }
1646
1647     if (*appData.cmailGameName != NULLCHAR) {
1648         SetCmailMode();
1649         OpenLoopback(&cmailPR);
1650         cmailISR =
1651           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1652     }
1653
1654     ThawUI();
1655     DisplayMessage("", "");
1656     if (StrCaseCmp(appData.initialMode, "") == 0) {
1657       initialMode = BeginningOfGame;
1658       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662         ModeHighlight();
1663       }
1664     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665       initialMode = TwoMachinesPlay;
1666     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667       initialMode = AnalyzeFile;
1668     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669       initialMode = AnalyzeMode;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671       initialMode = MachinePlaysWhite;
1672     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673       initialMode = MachinePlaysBlack;
1674     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675       initialMode = EditGame;
1676     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677       initialMode = EditPosition;
1678     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679       initialMode = Training;
1680     } else {
1681       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682       if( (len >= MSG_SIZ) && appData.debugMode )
1683         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684
1685       DisplayFatalError(buf, 0, 2);
1686       return;
1687     }
1688
1689     if (appData.matchMode) {
1690         if(appData.tourneyFile[0]) { // start tourney from command line
1691             FILE *f;
1692             if(f = fopen(appData.tourneyFile, "r")) {
1693                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694                 fclose(f);
1695                 appData.clockMode = TRUE;
1696                 SetGNUMode();
1697             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698         }
1699         MatchEvent(TRUE);
1700     } else if (*appData.cmailGameName != NULLCHAR) {
1701         /* Set up cmail mode */
1702         ReloadCmailMsgEvent(TRUE);
1703     } else {
1704         /* Set up other modes */
1705         if (initialMode == AnalyzeFile) {
1706           if (*appData.loadGameFile == NULLCHAR) {
1707             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1708             return;
1709           }
1710         }
1711         if (*appData.loadGameFile != NULLCHAR) {
1712             (void) LoadGameFromFile(appData.loadGameFile,
1713                                     appData.loadGameIndex,
1714                                     appData.loadGameFile, TRUE);
1715         } else if (*appData.loadPositionFile != NULLCHAR) {
1716             (void) LoadPositionFromFile(appData.loadPositionFile,
1717                                         appData.loadPositionIndex,
1718                                         appData.loadPositionFile);
1719             /* [HGM] try to make self-starting even after FEN load */
1720             /* to allow automatic setup of fairy variants with wtm */
1721             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722                 gameMode = BeginningOfGame;
1723                 setboardSpoiledMachineBlack = 1;
1724             }
1725             /* [HGM] loadPos: make that every new game uses the setup */
1726             /* from file as long as we do not switch variant          */
1727             if(!blackPlaysFirst) {
1728                 startedFromPositionFile = TRUE;
1729                 CopyBoard(filePosition, boards[0]);
1730             }
1731         }
1732         if (initialMode == AnalyzeMode) {
1733           if (appData.noChessProgram) {
1734             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735             return;
1736           }
1737           if (appData.icsActive) {
1738             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1739             return;
1740           }
1741           AnalyzeModeEvent();
1742         } else if (initialMode == AnalyzeFile) {
1743           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744           ShowThinkingEvent();
1745           AnalyzeFileEvent();
1746           AnalysisPeriodicEvent(1);
1747         } else if (initialMode == MachinePlaysWhite) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1750                               0, 2);
1751             return;
1752           }
1753           if (appData.icsActive) {
1754             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1755                               0, 2);
1756             return;
1757           }
1758           MachineWhiteEvent();
1759         } else if (initialMode == MachinePlaysBlack) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineBlackEvent();
1771         } else if (initialMode == TwoMachinesPlay) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           TwoMachinesEvent();
1783         } else if (initialMode == EditGame) {
1784           EditGameEvent();
1785         } else if (initialMode == EditPosition) {
1786           EditPositionEvent();
1787         } else if (initialMode == Training) {
1788           if (*appData.loadGameFile == NULLCHAR) {
1789             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1790             return;
1791           }
1792           TrainingEvent();
1793         }
1794     }
1795 }
1796
1797 void
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 {
1800     DisplayBook(current+1);
1801
1802     MoveHistorySet( movelist, first, last, current, pvInfoList );
1803
1804     EvalGraphSet( first, last, current, pvInfoList );
1805
1806     MakeEngineOutputTitle();
1807 }
1808
1809 /*
1810  * Establish will establish a contact to a remote host.port.
1811  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812  *  used to talk to the host.
1813  * Returns 0 if okay, error code if not.
1814  */
1815 int
1816 establish ()
1817 {
1818     char buf[MSG_SIZ];
1819
1820     if (*appData.icsCommPort != NULLCHAR) {
1821         /* Talk to the host through a serial comm port */
1822         return OpenCommPort(appData.icsCommPort, &icsPR);
1823
1824     } else if (*appData.gateway != NULLCHAR) {
1825         if (*appData.remoteShell == NULLCHAR) {
1826             /* Use the rcmd protocol to run telnet program on a gateway host */
1827             snprintf(buf, sizeof(buf), "%s %s %s",
1828                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1829             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830
1831         } else {
1832             /* Use the rsh program to run telnet program on a gateway host */
1833             if (*appData.remoteUser == NULLCHAR) {
1834                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835                         appData.gateway, appData.telnetProgram,
1836                         appData.icsHost, appData.icsPort);
1837             } else {
1838                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839                         appData.remoteShell, appData.gateway,
1840                         appData.remoteUser, appData.telnetProgram,
1841                         appData.icsHost, appData.icsPort);
1842             }
1843             return StartChildProcess(buf, "", &icsPR);
1844
1845         }
1846     } else if (appData.useTelnet) {
1847         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848
1849     } else {
1850         /* TCP socket interface differs somewhat between
1851            Unix and NT; handle details in the front end.
1852            */
1853         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1854     }
1855 }
1856
1857 void
1858 EscapeExpand (char *p, char *q)
1859 {       // [HGM] initstring: routine to shape up string arguments
1860         while(*p++ = *q++) if(p[-1] == '\\')
1861             switch(*q++) {
1862                 case 'n': p[-1] = '\n'; break;
1863                 case 'r': p[-1] = '\r'; break;
1864                 case 't': p[-1] = '\t'; break;
1865                 case '\\': p[-1] = '\\'; break;
1866                 case 0: *p = 0; return;
1867                 default: p[-1] = q[-1]; break;
1868             }
1869 }
1870
1871 void
1872 show_bytes (FILE *fp, char *buf, int count)
1873 {
1874     while (count--) {
1875         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876             fprintf(fp, "\\%03o", *buf & 0xff);
1877         } else {
1878             putc(*buf, fp);
1879         }
1880         buf++;
1881     }
1882     fflush(fp);
1883 }
1884
1885 /* Returns an errno value */
1886 int
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 {
1889     char buf[8192], *p, *q, *buflim;
1890     int left, newcount, outcount;
1891
1892     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893         *appData.gateway != NULLCHAR) {
1894         if (appData.debugMode) {
1895             fprintf(debugFP, ">ICS: ");
1896             show_bytes(debugFP, message, count);
1897             fprintf(debugFP, "\n");
1898         }
1899         return OutputToProcess(pr, message, count, outError);
1900     }
1901
1902     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1903     p = message;
1904     q = buf;
1905     left = count;
1906     newcount = 0;
1907     while (left) {
1908         if (q >= buflim) {
1909             if (appData.debugMode) {
1910                 fprintf(debugFP, ">ICS: ");
1911                 show_bytes(debugFP, buf, newcount);
1912                 fprintf(debugFP, "\n");
1913             }
1914             outcount = OutputToProcess(pr, buf, newcount, outError);
1915             if (outcount < newcount) return -1; /* to be sure */
1916             q = buf;
1917             newcount = 0;
1918         }
1919         if (*p == '\n') {
1920             *q++ = '\r';
1921             newcount++;
1922         } else if (((unsigned char) *p) == TN_IAC) {
1923             *q++ = (char) TN_IAC;
1924             newcount ++;
1925         }
1926         *q++ = *p++;
1927         newcount++;
1928         left--;
1929     }
1930     if (appData.debugMode) {
1931         fprintf(debugFP, ">ICS: ");
1932         show_bytes(debugFP, buf, newcount);
1933         fprintf(debugFP, "\n");
1934     }
1935     outcount = OutputToProcess(pr, buf, newcount, outError);
1936     if (outcount < newcount) return -1; /* to be sure */
1937     return count;
1938 }
1939
1940 void
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 {
1943     int outError, outCount;
1944     static int gotEof = 0;
1945     static FILE *ini;
1946
1947     /* Pass data read from player on to ICS */
1948     if (count > 0) {
1949         gotEof = 0;
1950         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951         if (outCount < count) {
1952             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953         }
1954         if(have_sent_ICS_logon == 2) {
1955           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956             fprintf(ini, "%s", message);
1957             have_sent_ICS_logon = 3;
1958           } else
1959             have_sent_ICS_logon = 1;
1960         } else if(have_sent_ICS_logon == 3) {
1961             fprintf(ini, "%s", message);
1962             fclose(ini);
1963           have_sent_ICS_logon = 1;
1964         }
1965     } else if (count < 0) {
1966         RemoveInputSource(isr);
1967         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968     } else if (gotEof++ > 0) {
1969         RemoveInputSource(isr);
1970         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1971     }
1972 }
1973
1974 void
1975 KeepAlive ()
1976 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979     SendToICS("date\n");
1980     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 }
1982
1983 /* added routine for printf style output to ics */
1984 void
1985 ics_printf (char *format, ...)
1986 {
1987     char buffer[MSG_SIZ];
1988     va_list args;
1989
1990     va_start(args, format);
1991     vsnprintf(buffer, sizeof(buffer), format, args);
1992     buffer[sizeof(buffer)-1] = '\0';
1993     SendToICS(buffer);
1994     va_end(args);
1995 }
1996
1997 void
1998 SendToICS (char *s)
1999 {
2000     int count, outCount, outError;
2001
2002     if (icsPR == NoProc) return;
2003
2004     count = strlen(s);
2005     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006     if (outCount < count) {
2007         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2008     }
2009 }
2010
2011 /* This is used for sending logon scripts to the ICS. Sending
2012    without a delay causes problems when using timestamp on ICC
2013    (at least on my machine). */
2014 void
2015 SendToICSDelayed (char *s, long msdelay)
2016 {
2017     int count, outCount, outError;
2018
2019     if (icsPR == NoProc) return;
2020
2021     count = strlen(s);
2022     if (appData.debugMode) {
2023         fprintf(debugFP, ">ICS: ");
2024         show_bytes(debugFP, s, count);
2025         fprintf(debugFP, "\n");
2026     }
2027     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028                                       msdelay);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034
2035 /* Remove all highlighting escape sequences in s
2036    Also deletes any suffix starting with '('
2037    */
2038 char *
2039 StripHighlightAndTitle (char *s)
2040 {
2041     static char retbuf[MSG_SIZ];
2042     char *p = retbuf;
2043
2044     while (*s != NULLCHAR) {
2045         while (*s == '\033') {
2046             while (*s != NULLCHAR && !isalpha(*s)) s++;
2047             if (*s != NULLCHAR) s++;
2048         }
2049         while (*s != NULLCHAR && *s != '\033') {
2050             if (*s == '(' || *s == '[') {
2051                 *p = NULLCHAR;
2052                 return retbuf;
2053             }
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 /* Remove all highlighting escape sequences in s */
2062 char *
2063 StripHighlight (char *s)
2064 {
2065     static char retbuf[MSG_SIZ];
2066     char *p = retbuf;
2067
2068     while (*s != NULLCHAR) {
2069         while (*s == '\033') {
2070             while (*s != NULLCHAR && !isalpha(*s)) s++;
2071             if (*s != NULLCHAR) s++;
2072         }
2073         while (*s != NULLCHAR && *s != '\033') {
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2083 char *
2084 VariantName (VariantClass v)
2085 {
2086     if(v == VariantUnknown || *engineVariant) return engineVariant;
2087     return variantNames[v];
2088 }
2089
2090
2091 /* Identify a variant from the strings the chess servers use or the
2092    PGN Variant tag names we use. */
2093 VariantClass
2094 StringToVariant (char *e)
2095 {
2096     char *p;
2097     int wnum = -1;
2098     VariantClass v = VariantNormal;
2099     int i, found = FALSE;
2100     char buf[MSG_SIZ];
2101     int len;
2102
2103     if (!e) return v;
2104
2105     /* [HGM] skip over optional board-size prefixes */
2106     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108         while( *e++ != '_');
2109     }
2110
2111     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2112         v = VariantNormal;
2113         found = TRUE;
2114     } else
2115     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116       if (p = StrCaseStr(e, variantNames[i])) {
2117         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118         v = (VariantClass) i;
2119         found = TRUE;
2120         break;
2121       }
2122     }
2123
2124     if (!found) {
2125       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126           || StrCaseStr(e, "wild/fr")
2127           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128         v = VariantFischeRandom;
2129       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130                  (i = 1, p = StrCaseStr(e, "w"))) {
2131         p += i;
2132         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2133         if (isdigit(*p)) {
2134           wnum = atoi(p);
2135         } else {
2136           wnum = -1;
2137         }
2138         switch (wnum) {
2139         case 0: /* FICS only, actually */
2140         case 1:
2141           /* Castling legal even if K starts on d-file */
2142           v = VariantWildCastle;
2143           break;
2144         case 2:
2145         case 3:
2146         case 4:
2147           /* Castling illegal even if K & R happen to start in
2148              normal positions. */
2149           v = VariantNoCastle;
2150           break;
2151         case 5:
2152         case 7:
2153         case 8:
2154         case 10:
2155         case 11:
2156         case 12:
2157         case 13:
2158         case 14:
2159         case 15:
2160         case 18:
2161         case 19:
2162           /* Castling legal iff K & R start in normal positions */
2163           v = VariantNormal;
2164           break;
2165         case 6:
2166         case 20:
2167         case 21:
2168           /* Special wilds for position setup; unclear what to do here */
2169           v = VariantLoadable;
2170           break;
2171         case 9:
2172           /* Bizarre ICC game */
2173           v = VariantTwoKings;
2174           break;
2175         case 16:
2176           v = VariantKriegspiel;
2177           break;
2178         case 17:
2179           v = VariantLosers;
2180           break;
2181         case 22:
2182           v = VariantFischeRandom;
2183           break;
2184         case 23:
2185           v = VariantCrazyhouse;
2186           break;
2187         case 24:
2188           v = VariantBughouse;
2189           break;
2190         case 25:
2191           v = Variant3Check;
2192           break;
2193         case 26:
2194           /* Not quite the same as FICS suicide! */
2195           v = VariantGiveaway;
2196           break;
2197         case 27:
2198           v = VariantAtomic;
2199           break;
2200         case 28:
2201           v = VariantShatranj;
2202           break;
2203
2204         /* Temporary names for future ICC types.  The name *will* change in
2205            the next xboard/WinBoard release after ICC defines it. */
2206         case 29:
2207           v = Variant29;
2208           break;
2209         case 30:
2210           v = Variant30;
2211           break;
2212         case 31:
2213           v = Variant31;
2214           break;
2215         case 32:
2216           v = Variant32;
2217           break;
2218         case 33:
2219           v = Variant33;
2220           break;
2221         case 34:
2222           v = Variant34;
2223           break;
2224         case 35:
2225           v = Variant35;
2226           break;
2227         case 36:
2228           v = Variant36;
2229           break;
2230         case 37:
2231           v = VariantShogi;
2232           break;
2233         case 38:
2234           v = VariantXiangqi;
2235           break;
2236         case 39:
2237           v = VariantCourier;
2238           break;
2239         case 40:
2240           v = VariantGothic;
2241           break;
2242         case 41:
2243           v = VariantCapablanca;
2244           break;
2245         case 42:
2246           v = VariantKnightmate;
2247           break;
2248         case 43:
2249           v = VariantFairy;
2250           break;
2251         case 44:
2252           v = VariantCylinder;
2253           break;
2254         case 45:
2255           v = VariantFalcon;
2256           break;
2257         case 46:
2258           v = VariantCapaRandom;
2259           break;
2260         case 47:
2261           v = VariantBerolina;
2262           break;
2263         case 48:
2264           v = VariantJanus;
2265           break;
2266         case 49:
2267           v = VariantSuper;
2268           break;
2269         case 50:
2270           v = VariantGreat;
2271           break;
2272         case -1:
2273           /* Found "wild" or "w" in the string but no number;
2274              must assume it's normal chess. */
2275           v = VariantNormal;
2276           break;
2277         default:
2278           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279           if( (len >= MSG_SIZ) && appData.debugMode )
2280             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281
2282           DisplayError(buf, 0);
2283           v = VariantUnknown;
2284           break;
2285         }
2286       }
2287     }
2288     if (appData.debugMode) {
2289       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290               e, wnum, VariantName(v));
2291     }
2292     return v;
2293 }
2294
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2297
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299    advance *index beyond it, and set leftover_start to the new value of
2300    *index; else return FALSE.  If pattern contains the character '*', it
2301    matches any sequence of characters not containing '\r', '\n', or the
2302    character following the '*' (if any), and the matched sequence(s) are
2303    copied into star_match.
2304    */
2305 int
2306 looking_at ( char *buf, int *index, char *pattern)
2307 {
2308     char *bufp = &buf[*index], *patternp = pattern;
2309     int star_count = 0;
2310     char *matchp = star_match[0];
2311
2312     for (;;) {
2313         if (*patternp == NULLCHAR) {
2314             *index = leftover_start = bufp - buf;
2315             *matchp = NULLCHAR;
2316             return TRUE;
2317         }
2318         if (*bufp == NULLCHAR) return FALSE;
2319         if (*patternp == '*') {
2320             if (*bufp == *(patternp + 1)) {
2321                 *matchp = NULLCHAR;
2322                 matchp = star_match[++star_count];
2323                 patternp += 2;
2324                 bufp++;
2325                 continue;
2326             } else if (*bufp == '\n' || *bufp == '\r') {
2327                 patternp++;
2328                 if (*patternp == NULLCHAR)
2329                   continue;
2330                 else
2331                   return FALSE;
2332             } else {
2333                 *matchp++ = *bufp++;
2334                 continue;
2335             }
2336         }
2337         if (*patternp != *bufp) return FALSE;
2338         patternp++;
2339         bufp++;
2340     }
2341 }
2342
2343 void
2344 SendToPlayer (char *data, int length)
2345 {
2346     int error, outCount;
2347     outCount = OutputToProcess(NoProc, data, length, &error);
2348     if (outCount < length) {
2349         DisplayFatalError(_("Error writing to display"), error, 1);
2350     }
2351 }
2352
2353 void
2354 PackHolding (char packed[], char *holding)
2355 {
2356     char *p = holding;
2357     char *q = packed;
2358     int runlength = 0;
2359     int curr = 9999;
2360     do {
2361         if (*p == curr) {
2362             runlength++;
2363         } else {
2364             switch (runlength) {
2365               case 0:
2366                 break;
2367               case 1:
2368                 *q++ = curr;
2369                 break;
2370               case 2:
2371                 *q++ = curr;
2372                 *q++ = curr;
2373                 break;
2374               default:
2375                 sprintf(q, "%d", runlength);
2376                 while (*q) q++;
2377                 *q++ = curr;
2378                 break;
2379             }
2380             runlength = 1;
2381             curr = *p;
2382         }
2383     } while (*p++);
2384     *q = NULLCHAR;
2385 }
2386
2387 /* Telnet protocol requests from the front end */
2388 void
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2390 {
2391     unsigned char msg[3];
2392     int outCount, outError;
2393
2394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395
2396     if (appData.debugMode) {
2397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2398         switch (ddww) {
2399           case TN_DO:
2400             ddwwStr = "DO";
2401             break;
2402           case TN_DONT:
2403             ddwwStr = "DONT";
2404             break;
2405           case TN_WILL:
2406             ddwwStr = "WILL";
2407             break;
2408           case TN_WONT:
2409             ddwwStr = "WONT";
2410             break;
2411           default:
2412             ddwwStr = buf1;
2413             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2414             break;
2415         }
2416         switch (option) {
2417           case TN_ECHO:
2418             optionStr = "ECHO";
2419             break;
2420           default:
2421             optionStr = buf2;
2422             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423             break;
2424         }
2425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2426     }
2427     msg[0] = TN_IAC;
2428     msg[1] = ddww;
2429     msg[2] = option;
2430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431     if (outCount < 3) {
2432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2433     }
2434 }
2435
2436 void
2437 DoEcho ()
2438 {
2439     if (!appData.icsActive) return;
2440     TelnetRequest(TN_DO, TN_ECHO);
2441 }
2442
2443 void
2444 DontEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DONT, TN_ECHO);
2448 }
2449
2450 void
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 {
2453     /* put the holdings sent to us by the server on the board holdings area */
2454     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2455     char p;
2456     ChessSquare piece;
2457
2458     if(gameInfo.holdingsWidth < 2)  return;
2459     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460         return; // prevent overwriting by pre-board holdings
2461
2462     if( (int)lowestPiece >= BlackPawn ) {
2463         holdingsColumn = 0;
2464         countsColumn = 1;
2465         holdingsStartRow = BOARD_HEIGHT-1;
2466         direction = -1;
2467     } else {
2468         holdingsColumn = BOARD_WIDTH-1;
2469         countsColumn = BOARD_WIDTH-2;
2470         holdingsStartRow = 0;
2471         direction = 1;
2472     }
2473
2474     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475         board[i][holdingsColumn] = EmptySquare;
2476         board[i][countsColumn]   = (ChessSquare) 0;
2477     }
2478     while( (p=*holdings++) != NULLCHAR ) {
2479         piece = CharToPiece( ToUpper(p) );
2480         if(piece == EmptySquare) continue;
2481         /*j = (int) piece - (int) WhitePawn;*/
2482         j = PieceToNumber(piece);
2483         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484         if(j < 0) continue;               /* should not happen */
2485         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487         board[holdingsStartRow+j*direction][countsColumn]++;
2488     }
2489 }
2490
2491
2492 void
2493 VariantSwitch (Board board, VariantClass newVariant)
2494 {
2495    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496    static Board oldBoard;
2497
2498    startedFromPositionFile = FALSE;
2499    if(gameInfo.variant == newVariant) return;
2500
2501    /* [HGM] This routine is called each time an assignment is made to
2502     * gameInfo.variant during a game, to make sure the board sizes
2503     * are set to match the new variant. If that means adding or deleting
2504     * holdings, we shift the playing board accordingly
2505     * This kludge is needed because in ICS observe mode, we get boards
2506     * of an ongoing game without knowing the variant, and learn about the
2507     * latter only later. This can be because of the move list we requested,
2508     * in which case the game history is refilled from the beginning anyway,
2509     * but also when receiving holdings of a crazyhouse game. In the latter
2510     * case we want to add those holdings to the already received position.
2511     */
2512
2513
2514    if (appData.debugMode) {
2515      fprintf(debugFP, "Switch board from %s to %s\n",
2516              VariantName(gameInfo.variant), VariantName(newVariant));
2517      setbuf(debugFP, NULL);
2518    }
2519    shuffleOpenings = 0;       /* [HGM] shuffle */
2520    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2521    switch(newVariant)
2522      {
2523      case VariantShogi:
2524        newWidth = 9;  newHeight = 9;
2525        gameInfo.holdingsSize = 7;
2526      case VariantBughouse:
2527      case VariantCrazyhouse:
2528        newHoldingsWidth = 2; break;
2529      case VariantGreat:
2530        newWidth = 10;
2531      case VariantSuper:
2532        newHoldingsWidth = 2;
2533        gameInfo.holdingsSize = 8;
2534        break;
2535      case VariantGothic:
2536      case VariantCapablanca:
2537      case VariantCapaRandom:
2538        newWidth = 10;
2539      default:
2540        newHoldingsWidth = gameInfo.holdingsSize = 0;
2541      };
2542
2543    if(newWidth  != gameInfo.boardWidth  ||
2544       newHeight != gameInfo.boardHeight ||
2545       newHoldingsWidth != gameInfo.holdingsWidth ) {
2546
2547      /* shift position to new playing area, if needed */
2548      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549        for(i=0; i<BOARD_HEIGHT; i++)
2550          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552              board[i][j];
2553        for(i=0; i<newHeight; i++) {
2554          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556        }
2557      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558        for(i=0; i<BOARD_HEIGHT; i++)
2559          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561              board[i][j];
2562      }
2563      board[HOLDINGS_SET] = 0;
2564      gameInfo.boardWidth  = newWidth;
2565      gameInfo.boardHeight = newHeight;
2566      gameInfo.holdingsWidth = newHoldingsWidth;
2567      gameInfo.variant = newVariant;
2568      InitDrawingSizes(-2, 0);
2569    } else gameInfo.variant = newVariant;
2570    CopyBoard(oldBoard, board);   // remember correctly formatted board
2571      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2572    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 }
2574
2575 static int loggedOn = FALSE;
2576
2577 /*-- Game start info cache: --*/
2578 int gs_gamenum;
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\   ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2586
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2589
2590 // [HGM] seekgraph
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2594 #define SQUARE 0x80
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2603
2604 void
2605 PlotSeekAd (int i)
2606 {
2607         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609         if(r < minRating+100 && r >=0 ) r = minRating+100;
2610         if(r > maxRating) r = maxRating;
2611         if(tc < 1.f) tc = 1.f;
2612         if(tc > 95.f) tc = 95.f;
2613         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614         y = ((double)r - minRating)/(maxRating - minRating)
2615             * (h-vMargin-squareSize/8-1) + vMargin;
2616         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617         if(strstr(seekAdList[i], " u ")) color = 1;
2618         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619            !strstr(seekAdList[i], "bullet") &&
2620            !strstr(seekAdList[i], "blitz") &&
2621            !strstr(seekAdList[i], "standard") ) color = 2;
2622         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2624 }
2625
2626 void
2627 PlotSingleSeekAd (int i)
2628 {
2629         PlotSeekAd(i);
2630 }
2631
2632 void
2633 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2634 {
2635         char buf[MSG_SIZ], *ext = "";
2636         VariantClass v = StringToVariant(type);
2637         if(strstr(type, "wild")) {
2638             ext = type + 4; // append wild number
2639             if(v == VariantFischeRandom) type = "chess960"; else
2640             if(v == VariantLoadable) type = "setup"; else
2641             type = VariantName(v);
2642         }
2643         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649             seekNrList[nrOfSeekAds] = nr;
2650             zList[nrOfSeekAds] = 0;
2651             seekAdList[nrOfSeekAds++] = StrSave(buf);
2652             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2653         }
2654 }
2655
2656 void
2657 EraseSeekDot (int i)
2658 {
2659     int x = xList[i], y = yList[i], d=squareSize/4, k;
2660     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662     // now replot every dot that overlapped
2663     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664         int xx = xList[k], yy = yList[k];
2665         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666             DrawSeekDot(xx, yy, colorList[k]);
2667     }
2668 }
2669
2670 void
2671 RemoveSeekAd (int nr)
2672 {
2673         int i;
2674         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675             EraseSeekDot(i);
2676             if(seekAdList[i]) free(seekAdList[i]);
2677             seekAdList[i] = seekAdList[--nrOfSeekAds];
2678             seekNrList[i] = seekNrList[nrOfSeekAds];
2679             ratingList[i] = ratingList[nrOfSeekAds];
2680             colorList[i]  = colorList[nrOfSeekAds];
2681             tcList[i] = tcList[nrOfSeekAds];
2682             xList[i]  = xList[nrOfSeekAds];
2683             yList[i]  = yList[nrOfSeekAds];
2684             zList[i]  = zList[nrOfSeekAds];
2685             seekAdList[nrOfSeekAds] = NULL;
2686             break;
2687         }
2688 }
2689
2690 Boolean
2691 MatchSoughtLine (char *line)
2692 {
2693     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694     int nr, base, inc, u=0; char dummy;
2695
2696     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698        (u=1) &&
2699        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2701         // match: compact and save the line
2702         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2703         return TRUE;
2704     }
2705     return FALSE;
2706 }
2707
2708 int
2709 DrawSeekGraph ()
2710 {
2711     int i;
2712     if(!seekGraphUp) return FALSE;
2713     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2715
2716     DrawSeekBackground(0, 0, w, h);
2717     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721         yy = h-1-yy;
2722         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723         if(i%500 == 0) {
2724             char buf[MSG_SIZ];
2725             snprintf(buf, MSG_SIZ, "%d", i);
2726             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727         }
2728     }
2729     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730     for(i=1; i<100; i+=(i<10?1:5)) {
2731         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734             char buf[MSG_SIZ];
2735             snprintf(buf, MSG_SIZ, "%d", i);
2736             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737         }
2738     }
2739     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2740     return TRUE;
2741 }
2742
2743 int
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 {
2746     static int lastDown = 0, displayed = 0, lastSecond;
2747     if(y < 0) return FALSE;
2748     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750         if(!seekGraphUp) return FALSE;
2751         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752         DrawPosition(TRUE, NULL);
2753         return TRUE;
2754     }
2755     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756         if(click == Release || moving) return FALSE;
2757         nrOfSeekAds = 0;
2758         soughtPending = TRUE;
2759         SendToICS(ics_prefix);
2760         SendToICS("sought\n"); // should this be "sought all"?
2761     } else { // issue challenge based on clicked ad
2762         int dist = 10000; int i, closest = 0, second = 0;
2763         for(i=0; i<nrOfSeekAds; i++) {
2764             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2765             if(d < dist) { dist = d; closest = i; }
2766             second += (d - zList[i] < 120); // count in-range ads
2767             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2768         }
2769         if(dist < 120) {
2770             char buf[MSG_SIZ];
2771             second = (second > 1);
2772             if(displayed != closest || second != lastSecond) {
2773                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774                 lastSecond = second; displayed = closest;
2775             }
2776             if(click == Press) {
2777                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778                 lastDown = closest;
2779                 return TRUE;
2780             } // on press 'hit', only show info
2781             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783             SendToICS(ics_prefix);
2784             SendToICS(buf);
2785             return TRUE; // let incoming board of started game pop down the graph
2786         } else if(click == Release) { // release 'miss' is ignored
2787             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788             if(moving == 2) { // right up-click
2789                 nrOfSeekAds = 0; // refresh graph
2790                 soughtPending = TRUE;
2791                 SendToICS(ics_prefix);
2792                 SendToICS("sought\n"); // should this be "sought all"?
2793             }
2794             return TRUE;
2795         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796         // press miss or release hit 'pop down' seek graph
2797         seekGraphUp = FALSE;
2798         DrawPosition(TRUE, NULL);
2799     }
2800     return TRUE;
2801 }
2802
2803 void
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 {
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2815
2816     static int started = STARTED_NONE;
2817     static char parse[20000];
2818     static int parse_pos = 0;
2819     static char buf[BUF_SIZE + 1];
2820     static int firstTime = TRUE, intfSet = FALSE;
2821     static ColorClass prevColor = ColorNormal;
2822     static int savingComment = FALSE;
2823     static int cmatch = 0; // continuation sequence match
2824     char *bp;
2825     char str[MSG_SIZ];
2826     int i, oldi;
2827     int buf_len;
2828     int next_out;
2829     int tkind;
2830     int backup;    /* [DM] For zippy color lines */
2831     char *p;
2832     char talker[MSG_SIZ]; // [HGM] chat
2833     int channel, collective=0;
2834
2835     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836
2837     if (appData.debugMode) {
2838       if (!error) {
2839         fprintf(debugFP, "<ICS: ");
2840         show_bytes(debugFP, data, count);
2841         fprintf(debugFP, "\n");
2842       }
2843     }
2844
2845     if (appData.debugMode) { int f = forwardMostMove;
2846         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849     }
2850     if (count > 0) {
2851         /* If last read ended with a partial line that we couldn't parse,
2852            prepend it to the new read and try again. */
2853         if (leftover_len > 0) {
2854             for (i=0; i<leftover_len; i++)
2855               buf[i] = buf[leftover_start + i];
2856         }
2857
2858     /* copy new characters into the buffer */
2859     bp = buf + leftover_len;
2860     buf_len=leftover_len;
2861     for (i=0; i<count; i++)
2862     {
2863         // ignore these
2864         if (data[i] == '\r')
2865             continue;
2866
2867         // join lines split by ICS?
2868         if (!appData.noJoin)
2869         {
2870             /*
2871                 Joining just consists of finding matches against the
2872                 continuation sequence, and discarding that sequence
2873                 if found instead of copying it.  So, until a match
2874                 fails, there's nothing to do since it might be the
2875                 complete sequence, and thus, something we don't want
2876                 copied.
2877             */
2878             if (data[i] == cont_seq[cmatch])
2879             {
2880                 cmatch++;
2881                 if (cmatch == strlen(cont_seq))
2882                 {
2883                     cmatch = 0; // complete match.  just reset the counter
2884
2885                     /*
2886                         it's possible for the ICS to not include the space
2887                         at the end of the last word, making our [correct]
2888                         join operation fuse two separate words.  the server
2889                         does this when the space occurs at the width setting.
2890                     */
2891                     if (!buf_len || buf[buf_len-1] != ' ')
2892                     {
2893                         *bp++ = ' ';
2894                         buf_len++;
2895                     }
2896                 }
2897                 continue;
2898             }
2899             else if (cmatch)
2900             {
2901                 /*
2902                     match failed, so we have to copy what matched before
2903                     falling through and copying this character.  In reality,
2904                     this will only ever be just the newline character, but
2905                     it doesn't hurt to be precise.
2906                 */
2907                 strncpy(bp, cont_seq, cmatch);
2908                 bp += cmatch;
2909                 buf_len += cmatch;
2910                 cmatch = 0;
2911             }
2912         }
2913
2914         // copy this char
2915         *bp++ = data[i];
2916         buf_len++;
2917     }
2918
2919         buf[buf_len] = NULLCHAR;
2920 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2921         next_out = 0;
2922         leftover_start = 0;
2923
2924         i = 0;
2925         while (i < buf_len) {
2926             /* Deal with part of the TELNET option negotiation
2927                protocol.  We refuse to do anything beyond the
2928                defaults, except that we allow the WILL ECHO option,
2929                which ICS uses to turn off password echoing when we are
2930                directly connected to it.  We reject this option
2931                if localLineEditing mode is on (always on in xboard)
2932                and we are talking to port 23, which might be a real
2933                telnet server that will try to keep WILL ECHO on permanently.
2934              */
2935             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937                 unsigned char option;
2938                 oldi = i;
2939                 switch ((unsigned char) buf[++i]) {
2940                   case TN_WILL:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<WILL ");
2943                     switch (option = (unsigned char) buf[++i]) {
2944                       case TN_ECHO:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "ECHO ");
2947                         /* Reply only if this is a change, according
2948                            to the protocol rules. */
2949                         if (remoteEchoOption) break;
2950                         if (appData.localLineEditing &&
2951                             atoi(appData.icsPort) == TN_PORT) {
2952                             TelnetRequest(TN_DONT, TN_ECHO);
2953                         } else {
2954                             EchoOff();
2955                             TelnetRequest(TN_DO, TN_ECHO);
2956                             remoteEchoOption = TRUE;
2957                         }
2958                         break;
2959                       default:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         /* Whatever this is, we don't want it. */
2963                         TelnetRequest(TN_DONT, option);
2964                         break;
2965                     }
2966                     break;
2967                   case TN_WONT:
2968                     if (appData.debugMode)
2969                       fprintf(debugFP, "\n<WONT ");
2970                     switch (option = (unsigned char) buf[++i]) {
2971                       case TN_ECHO:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "ECHO ");
2974                         /* Reply only if this is a change, according
2975                            to the protocol rules. */
2976                         if (!remoteEchoOption) break;
2977                         EchoOn();
2978                         TelnetRequest(TN_DONT, TN_ECHO);
2979                         remoteEchoOption = FALSE;
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", (unsigned char) option);
2984                         /* Whatever this is, it must already be turned
2985                            off, because we never agree to turn on
2986                            anything non-default, so according to the
2987                            protocol rules, we don't reply. */
2988                         break;
2989                     }
2990                     break;
2991                   case TN_DO:
2992                     if (appData.debugMode)
2993                       fprintf(debugFP, "\n<DO ");
2994                     switch (option = (unsigned char) buf[++i]) {
2995                       default:
2996                         /* Whatever this is, we refuse to do it. */
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", option);
2999                         TelnetRequest(TN_WONT, option);
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DONT:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DONT ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         /* Whatever this is, we are already not doing
3011                            it, because we never agree to do anything
3012                            non-default, so according to the protocol
3013                            rules, we don't reply. */
3014                         break;
3015                     }
3016                     break;
3017                   case TN_IAC:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<IAC ");
3020                     /* Doubled IAC; pass it through */
3021                     i--;
3022                     break;
3023                   default:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026                     /* Drop all other telnet commands on the floor */
3027                     break;
3028                 }
3029                 if (oldi > next_out)
3030                   SendToPlayer(&buf[next_out], oldi - next_out);
3031                 if (++i > next_out)
3032                   next_out = i;
3033                 continue;
3034             }
3035
3036             /* OK, this at least will *usually* work */
3037             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3038                 loggedOn = TRUE;
3039             }
3040
3041             if (loggedOn && !intfSet) {
3042                 if (ics_type == ICS_ICC) {
3043                   snprintf(str, MSG_SIZ,
3044                           "/set-quietly interface %s\n/set-quietly style 12\n",
3045                           programVersion);
3046                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3048                 } else if (ics_type == ICS_CHESSNET) {
3049                   snprintf(str, MSG_SIZ, "/style 12\n");
3050                 } else {
3051                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052                   strcat(str, programVersion);
3053                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 #ifdef WIN32
3057                   strcat(str, "$iset nohighlight 1\n");
3058 #endif
3059                   strcat(str, "$iset lock 1\n$style 12\n");
3060                 }
3061                 SendToICS(str);
3062                 NotifyFrontendLogin();
3063                 intfSet = TRUE;
3064             }
3065
3066             if (started == STARTED_COMMENT) {
3067                 /* Accumulate characters in comment */
3068                 parse[parse_pos++] = buf[i];
3069                 if (buf[i] == '\n') {
3070                     parse[parse_pos] = NULLCHAR;
3071                     if(chattingPartner>=0) {
3072                         char mess[MSG_SIZ];
3073                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074                         OutputChatMessage(chattingPartner, mess);
3075                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3076                             int p;
3077                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080                                 OutputChatMessage(p, mess);
3081                                 break;
3082                             }
3083                         }
3084                         chattingPartner = -1;
3085                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3086                         collective = 0;
3087                     } else
3088                     if(!suppressKibitz) // [HGM] kibitz
3089                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3090                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3091                         int nrDigit = 0, nrAlph = 0, j;
3092                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3093                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3094                         parse[parse_pos] = NULLCHAR;
3095                         // try to be smart: if it does not look like search info, it should go to
3096                         // ICS interaction window after all, not to engine-output window.
3097                         for(j=0; j<parse_pos; j++) { // count letters and digits
3098                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3099                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3100                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3101                         }
3102                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3103                             int depth=0; float score;
3104                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3105                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3106                                 pvInfoList[forwardMostMove-1].depth = depth;
3107                                 pvInfoList[forwardMostMove-1].score = 100*score;
3108                             }
3109                             OutputKibitz(suppressKibitz, parse);
3110                         } else {
3111                             char tmp[MSG_SIZ];
3112                             if(gameMode == IcsObserving) // restore original ICS messages
3113                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3114                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3115                             else
3116                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3117                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3118                             SendToPlayer(tmp, strlen(tmp));
3119                         }
3120                         next_out = i+1; // [HGM] suppress printing in ICS window
3121                     }
3122                     started = STARTED_NONE;
3123                 } else {
3124                     /* Don't match patterns against characters in comment */
3125                     i++;
3126                     continue;
3127                 }
3128             }
3129             if (started == STARTED_CHATTER) {
3130                 if (buf[i] != '\n') {
3131                     /* Don't match patterns against characters in chatter */
3132                     i++;
3133                     continue;
3134                 }
3135                 started = STARTED_NONE;
3136                 if(suppressKibitz) next_out = i+1;
3137             }
3138
3139             /* Kludge to deal with rcmd protocol */
3140             if (firstTime && looking_at(buf, &i, "\001*")) {
3141                 DisplayFatalError(&buf[1], 0, 1);
3142                 continue;
3143             } else {
3144                 firstTime = FALSE;
3145             }
3146
3147             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3148                 ics_type = ICS_ICC;
3149                 ics_prefix = "/";
3150                 if (appData.debugMode)
3151                   fprintf(debugFP, "ics_type %d\n", ics_type);
3152                 continue;
3153             }
3154             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3155                 ics_type = ICS_FICS;
3156                 ics_prefix = "$";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3162                 ics_type = ICS_CHESSNET;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168
3169             if (!loggedOn &&
3170                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3171                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3172                  looking_at(buf, &i, "will be \"*\""))) {
3173               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3174               continue;
3175             }
3176
3177             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3178               char buf[MSG_SIZ];
3179               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3180               DisplayIcsInteractionTitle(buf);
3181               have_set_title = TRUE;
3182             }
3183
3184             /* skip finger notes */
3185             if (started == STARTED_NONE &&
3186                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3187                  (buf[i] == '1' && buf[i+1] == '0')) &&
3188                 buf[i+2] == ':' && buf[i+3] == ' ') {
3189               started = STARTED_CHATTER;
3190               i += 3;
3191               continue;
3192             }
3193
3194             oldi = i;
3195             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3196             if(appData.seekGraph) {
3197                 if(soughtPending && MatchSoughtLine(buf+i)) {
3198                     i = strstr(buf+i, "rated") - buf;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     next_out = leftover_start = i;
3201                     started = STARTED_CHATTER;
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3206                         && looking_at(buf, &i, "* ads displayed")) {
3207                     soughtPending = FALSE;
3208                     seekGraphUp = TRUE;
3209                     DrawSeekGraph();
3210                     continue;
3211                 }
3212                 if(appData.autoRefresh) {
3213                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3214                         int s = (ics_type == ICS_ICC); // ICC format differs
3215                         if(seekGraphUp)
3216                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3217                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3218                         looking_at(buf, &i, "*% "); // eat prompt
3219                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i; // suppress
3222                         continue;
3223                     }
3224                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3225                         char *p = star_match[0];
3226                         while(*p) {
3227                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3228                             while(*p && *p++ != ' '); // next
3229                         }
3230                         looking_at(buf, &i, "*% "); // eat prompt
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i;
3233                         continue;
3234                     }
3235                 }
3236             }
3237
3238             /* skip formula vars */
3239             if (started == STARTED_NONE &&
3240                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3241               started = STARTED_CHATTER;
3242               i += 3;
3243               continue;
3244             }
3245
3246             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3247             if (appData.autoKibitz && started == STARTED_NONE &&
3248                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3249                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3250                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3251                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3252                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3253                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3254                         suppressKibitz = TRUE;
3255                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3256                         next_out = i;
3257                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3258                                 && (gameMode == IcsPlayingWhite)) ||
3259                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3260                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3261                             started = STARTED_CHATTER; // own kibitz we simply discard
3262                         else {
3263                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3264                             parse_pos = 0; parse[0] = NULLCHAR;
3265                             savingComment = TRUE;
3266                             suppressKibitz = gameMode != IcsObserving ? 2 :
3267                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3268                         }
3269                         continue;
3270                 } else
3271                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3272                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3273                          && atoi(star_match[0])) {
3274                     // suppress the acknowledgements of our own autoKibitz
3275                     char *p;
3276                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3278                     SendToPlayer(star_match[0], strlen(star_match[0]));
3279                     if(looking_at(buf, &i, "*% ")) // eat prompt
3280                         suppressKibitz = FALSE;
3281                     next_out = i;
3282                     continue;
3283                 }
3284             } // [HGM] kibitz: end of patch
3285
3286             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3287
3288             // [HGM] chat: intercept tells by users for which we have an open chat window
3289             channel = -1;
3290             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3291                                            looking_at(buf, &i, "* whispers:") ||
3292                                            looking_at(buf, &i, "* kibitzes:") ||
3293                                            looking_at(buf, &i, "* shouts:") ||
3294                                            looking_at(buf, &i, "* c-shouts:") ||
3295                                            looking_at(buf, &i, "--> * ") ||
3296                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3297                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3300                 int p;
3301                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3302                 chattingPartner = -1; collective = 0;
3303
3304                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3305                 for(p=0; p<MAX_CHAT; p++) {
3306                     collective = 1;
3307                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3308                     talker[0] = '['; strcat(talker, "] ");
3309                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3310                     chattingPartner = p; break;
3311                     }
3312                 } else
3313                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3314                 for(p=0; p<MAX_CHAT; p++) {
3315                     collective = 1;
3316                     if(!strcmp("kibitzes", chatPartner[p])) {
3317                         talker[0] = '['; strcat(talker, "] ");
3318                         chattingPartner = p; break;
3319                     }
3320                 } else
3321                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3322                 for(p=0; p<MAX_CHAT; p++) {
3323                     collective = 1;
3324                     if(!strcmp("whispers", chatPartner[p])) {
3325                         talker[0] = '['; strcat(talker, "] ");
3326                         chattingPartner = p; break;
3327                     }
3328                 } else
3329                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3330                   if(buf[i-8] == '-' && buf[i-3] == 't')
3331                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3332                     collective = 1;
3333                     if(!strcmp("c-shouts", chatPartner[p])) {
3334                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3335                         chattingPartner = p; break;
3336                     }
3337                   }
3338                   if(chattingPartner < 0)
3339                   for(p=0; p<MAX_CHAT; p++) {
3340                     collective = 1;
3341                     if(!strcmp("shouts", chatPartner[p])) {
3342                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3343                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3344                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3345                         chattingPartner = p; break;
3346                     }
3347                   }
3348                 }
3349                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3350                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3351                     talker[0] = 0;
3352                     Colorize(ColorTell, FALSE);
3353                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3354                     collective |= 2;
3355                     chattingPartner = p; break;
3356                 }
3357                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3358                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3359                     started = STARTED_COMMENT;
3360                     parse_pos = 0; parse[0] = NULLCHAR;
3361                     savingComment = 3 + chattingPartner; // counts as TRUE
3362                     if(collective == 3) i = oldi; else {
3363                         suppressKibitz = TRUE;
3364                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3365                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3366                         continue;
3367                     }
3368                 }
3369             } // [HGM] chat: end of patch
3370
3371           backup = i;
3372             if (appData.zippyTalk || appData.zippyPlay) {
3373                 /* [DM] Backup address for color zippy lines */
3374 #if ZIPPY
3375                if (loggedOn == TRUE)
3376                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3377                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3378 #endif
3379             } // [DM] 'else { ' deleted
3380                 if (
3381                     /* Regular tells and says */
3382                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3383                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3384                     looking_at(buf, &i, "* says: ") ||
3385                     /* Don't color "message" or "messages" output */
3386                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3387                     looking_at(buf, &i, "*. * at *:*: ") ||
3388                     looking_at(buf, &i, "--* (*:*): ") ||
3389                     /* Message notifications (same color as tells) */
3390                     looking_at(buf, &i, "* has left a message ") ||
3391                     looking_at(buf, &i, "* just sent you a message:\n") ||
3392                     /* Whispers and kibitzes */
3393                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3394                     looking_at(buf, &i, "* kibitzes: ") ||
3395                     /* Channel tells */
3396                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3397
3398                   if (tkind == 1 && strchr(star_match[0], ':')) {
3399                       /* Avoid "tells you:" spoofs in channels */
3400                      tkind = 3;
3401                   }
3402                   if (star_match[0][0] == NULLCHAR ||
3403                       strchr(star_match[0], ' ') ||
3404                       (tkind == 3 && strchr(star_match[1], ' '))) {
3405                     /* Reject bogus matches */
3406                     i = oldi;
3407                   } else {
3408                     if (appData.colorize) {
3409                       if (oldi > next_out) {
3410                         SendToPlayer(&buf[next_out], oldi - next_out);
3411                         next_out = oldi;
3412                       }
3413                       switch (tkind) {
3414                       case 1:
3415                         Colorize(ColorTell, FALSE);
3416                         curColor = ColorTell;
3417                         break;
3418                       case 2:
3419                         Colorize(ColorKibitz, FALSE);
3420                         curColor = ColorKibitz;
3421                         break;
3422                       case 3:
3423                         p = strrchr(star_match[1], '(');
3424                         if (p == NULL) {
3425                           p = star_match[1];
3426                         } else {
3427                           p++;
3428                         }
3429                         if (atoi(p) == 1) {
3430                           Colorize(ColorChannel1, FALSE);
3431                           curColor = ColorChannel1;
3432                         } else {
3433                           Colorize(ColorChannel, FALSE);
3434                           curColor = ColorChannel;
3435                         }
3436                         break;
3437                       case 5:
3438                         curColor = ColorNormal;
3439                         break;
3440                       }
3441                     }
3442                     if (started == STARTED_NONE && appData.autoComment &&
3443                         (gameMode == IcsObserving ||
3444                          gameMode == IcsPlayingWhite ||
3445                          gameMode == IcsPlayingBlack)) {
3446                       parse_pos = i - oldi;
3447                       memcpy(parse, &buf[oldi], parse_pos);
3448                       parse[parse_pos] = NULLCHAR;
3449                       started = STARTED_COMMENT;
3450                       savingComment = TRUE;
3451                     } else if(collective != 3) {
3452                       started = STARTED_CHATTER;
3453                       savingComment = FALSE;
3454                     }
3455                     loggedOn = TRUE;
3456                     continue;
3457                   }
3458                 }
3459
3460                 if (looking_at(buf, &i, "* s-shouts: ") ||
3461                     looking_at(buf, &i, "* c-shouts: ")) {
3462                     if (appData.colorize) {
3463                         if (oldi > next_out) {
3464                             SendToPlayer(&buf[next_out], oldi - next_out);
3465                             next_out = oldi;
3466                         }
3467                         Colorize(ColorSShout, FALSE);
3468                         curColor = ColorSShout;
3469                     }
3470                     loggedOn = TRUE;
3471                     started = STARTED_CHATTER;
3472                     continue;
3473                 }
3474
3475                 if (looking_at(buf, &i, "--->")) {
3476                     loggedOn = TRUE;
3477                     continue;
3478                 }
3479
3480                 if (looking_at(buf, &i, "* shouts: ") ||
3481                     looking_at(buf, &i, "--> ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorShout, FALSE);
3488                         curColor = ColorShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at( buf, &i, "Challenge:")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorChallenge, FALSE);
3502                         curColor = ColorChallenge;
3503                     }
3504                     loggedOn = TRUE;
3505                     continue;
3506                 }
3507
3508                 if (looking_at(buf, &i, "* offers you") ||
3509                     looking_at(buf, &i, "* offers to be") ||
3510                     looking_at(buf, &i, "* would like to") ||
3511                     looking_at(buf, &i, "* requests to") ||
3512                     looking_at(buf, &i, "Your opponent offers") ||
3513                     looking_at(buf, &i, "Your opponent requests")) {
3514
3515                     if (appData.colorize) {
3516                         if (oldi > next_out) {
3517                             SendToPlayer(&buf[next_out], oldi - next_out);
3518                             next_out = oldi;
3519                         }
3520                         Colorize(ColorRequest, FALSE);
3521                         curColor = ColorRequest;
3522                     }
3523                     continue;
3524                 }
3525
3526                 if (looking_at(buf, &i, "* (*) seeking")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorSeek, FALSE);
3533                         curColor = ColorSeek;
3534                     }
3535                     continue;
3536             }
3537
3538           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3539
3540             if (looking_at(buf, &i, "\\   ")) {
3541                 if (prevColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(prevColor, TRUE);
3547                     curColor = prevColor;
3548                 }
3549                 if (savingComment) {
3550                     parse_pos = i - oldi;
3551                     memcpy(parse, &buf[oldi], parse_pos);
3552                     parse[parse_pos] = NULLCHAR;
3553                     started = STARTED_COMMENT;
3554                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3555                         chattingPartner = savingComment - 3; // kludge to remember the box
3556                 } else {
3557                     started = STARTED_CHATTER;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "Black Strength :") ||
3563                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3564                 looking_at(buf, &i, "<10>") ||
3565                 looking_at(buf, &i, "#@#")) {
3566                 /* Wrong board style */
3567                 loggedOn = TRUE;
3568                 SendToICS(ics_prefix);
3569                 SendToICS("set style 12\n");
3570                 SendToICS(ics_prefix);
3571                 SendToICS("refresh\n");
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "login:")) {
3576               if (!have_sent_ICS_logon) {
3577                 if(ICSInitScript())
3578                   have_sent_ICS_logon = 1;
3579                 else // no init script was found
3580                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3581               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3582                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3583               }
3584                 continue;
3585             }
3586
3587             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3588                 (looking_at(buf, &i, "\n<12> ") ||
3589                  looking_at(buf, &i, "<12> "))) {
3590                 loggedOn = TRUE;
3591                 if (oldi > next_out) {
3592                     SendToPlayer(&buf[next_out], oldi - next_out);
3593                 }
3594                 next_out = i;
3595                 started = STARTED_BOARD;
3596                 parse_pos = 0;
3597                 continue;
3598             }
3599
3600             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3601                 looking_at(buf, &i, "<b1> ")) {
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_HOLDINGS;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3612                 loggedOn = TRUE;
3613                 /* Header for a move list -- first line */
3614
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     switch (gameMode) {
3618                       case IcsIdle:
3619                       case BeginningOfGame:
3620                         /* User typed "moves" or "oldmoves" while we
3621                            were idle.  Pretend we asked for these
3622                            moves and soak them up so user can step
3623                            through them and/or save them.
3624                            */
3625                         Reset(FALSE, TRUE);
3626                         gameMode = IcsObserving;
3627                         ModeHighlight();
3628                         ics_gamenum = -1;
3629                         ics_getting_history = H_GOT_UNREQ_HEADER;
3630                         break;
3631                       case EditGame: /*?*/
3632                       case EditPosition: /*?*/
3633                         /* Should above feature work in these modes too? */
3634                         /* For now it doesn't */
3635                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3636                         break;
3637                       default:
3638                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3639                         break;
3640                     }
3641                     break;
3642                   case H_REQUESTED:
3643                     /* Is this the right one? */
3644                     if (gameInfo.white && gameInfo.black &&
3645                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3646                         strcmp(gameInfo.black, star_match[2]) == 0) {
3647                         /* All is well */
3648                         ics_getting_history = H_GOT_REQ_HEADER;
3649                     }
3650                     break;
3651                   case H_GOT_REQ_HEADER:
3652                   case H_GOT_UNREQ_HEADER:
3653                   case H_GOT_UNWANTED_HEADER:
3654                   case H_GETTING_MOVES:
3655                     /* Should not happen */
3656                     DisplayError(_("Error gathering move list: two headers"), 0);
3657                     ics_getting_history = H_FALSE;
3658                     break;
3659                 }
3660
3661                 /* Save player ratings into gameInfo if needed */
3662                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3663                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3664                     (gameInfo.whiteRating == -1 ||
3665                      gameInfo.blackRating == -1)) {
3666
3667                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3668                     gameInfo.blackRating = string_to_rating(star_match[3]);
3669                     if (appData.debugMode)
3670                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3671                               gameInfo.whiteRating, gameInfo.blackRating);
3672                 }
3673                 continue;
3674             }
3675
3676             if (looking_at(buf, &i,
3677               "* * match, initial time: * minute*, increment: * second")) {
3678                 /* Header for a move list -- second line */
3679                 /* Initial board will follow if this is a wild game */
3680                 if (gameInfo.event != NULL) free(gameInfo.event);
3681                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3682                 gameInfo.event = StrSave(str);
3683                 /* [HGM] we switched variant. Translate boards if needed. */
3684                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3685                 continue;
3686             }
3687
3688             if (looking_at(buf, &i, "Move  ")) {
3689                 /* Beginning of a move list */
3690                 switch (ics_getting_history) {
3691                   case H_FALSE:
3692                     /* Normally should not happen */
3693                     /* Maybe user hit reset while we were parsing */
3694                     break;
3695                   case H_REQUESTED:
3696                     /* Happens if we are ignoring a move list that is not
3697                      * the one we just requested.  Common if the user
3698                      * tries to observe two games without turning off
3699                      * getMoveList */
3700                     break;
3701                   case H_GETTING_MOVES:
3702                     /* Should not happen */
3703                     DisplayError(_("Error gathering move list: nested"), 0);
3704                     ics_getting_history = H_FALSE;
3705                     break;
3706                   case H_GOT_REQ_HEADER:
3707                     ics_getting_history = H_GETTING_MOVES;
3708                     started = STARTED_MOVES;
3709                     parse_pos = 0;
3710                     if (oldi > next_out) {
3711                         SendToPlayer(&buf[next_out], oldi - next_out);
3712                     }
3713                     break;
3714                   case H_GOT_UNREQ_HEADER:
3715                     ics_getting_history = H_GETTING_MOVES;
3716                     started = STARTED_MOVES_NOHIDE;
3717                     parse_pos = 0;
3718                     break;
3719                   case H_GOT_UNWANTED_HEADER:
3720                     ics_getting_history = H_FALSE;
3721                     break;
3722                 }
3723                 continue;
3724             }
3725
3726             if (looking_at(buf, &i, "% ") ||
3727                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3728                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3729                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3730                     soughtPending = FALSE;
3731                     seekGraphUp = TRUE;
3732                     DrawSeekGraph();
3733                 }
3734                 if(suppressKibitz) next_out = i;
3735                 savingComment = FALSE;
3736                 suppressKibitz = 0;
3737                 switch (started) {
3738                   case STARTED_MOVES:
3739                   case STARTED_MOVES_NOHIDE:
3740                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3741                     parse[parse_pos + i - oldi] = NULLCHAR;
3742                     ParseGameHistory(parse);
3743 #if ZIPPY
3744                     if (appData.zippyPlay && first.initDone) {
3745                         FeedMovesToProgram(&first, forwardMostMove);
3746                         if (gameMode == IcsPlayingWhite) {
3747                             if (WhiteOnMove(forwardMostMove)) {
3748                                 if (first.sendTime) {
3749                                   if (first.useColors) {
3750                                     SendToProgram("black\n", &first);
3751                                   }
3752                                   SendTimeRemaining(&first, TRUE);
3753                                 }
3754                                 if (first.useColors) {
3755                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3756                                 }
3757                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3758                                 first.maybeThinking = TRUE;
3759                             } else {
3760                                 if (first.usePlayother) {
3761                                   if (first.sendTime) {
3762                                     SendTimeRemaining(&first, TRUE);
3763                                   }
3764                                   SendToProgram("playother\n", &first);
3765                                   firstMove = FALSE;
3766                                 } else {
3767                                   firstMove = TRUE;
3768                                 }
3769                             }
3770                         } else if (gameMode == IcsPlayingBlack) {
3771                             if (!WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("white\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, FALSE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("black\n", &first);
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, FALSE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         }
3795                     }
3796 #endif
3797                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3798                         /* Moves came from oldmoves or moves command
3799                            while we weren't doing anything else.
3800                            */
3801                         currentMove = forwardMostMove;
3802                         ClearHighlights();/*!!could figure this out*/
3803                         flipView = appData.flipView;
3804                         DrawPosition(TRUE, boards[currentMove]);
3805                         DisplayBothClocks();
3806                         snprintf(str, MSG_SIZ, "%s %s %s",
3807                                 gameInfo.white, _("vs."),  gameInfo.black);
3808                         DisplayTitle(str);
3809                         gameMode = IcsIdle;
3810                     } else {
3811                         /* Moves were history of an active game */
3812                         if (gameInfo.resultDetails != NULL) {
3813                             free(gameInfo.resultDetails);
3814                             gameInfo.resultDetails = NULL;
3815                         }
3816                     }
3817                     HistorySet(parseList, backwardMostMove,
3818                                forwardMostMove, currentMove-1);
3819                     DisplayMove(currentMove - 1);
3820                     if (started == STARTED_MOVES) next_out = i;
3821                     started = STARTED_NONE;
3822                     ics_getting_history = H_FALSE;
3823                     break;
3824
3825                   case STARTED_OBSERVE:
3826                     started = STARTED_NONE;
3827                     SendToICS(ics_prefix);
3828                     SendToICS("refresh\n");
3829                     break;
3830
3831                   default:
3832                     break;
3833                 }
3834                 if(bookHit) { // [HGM] book: simulate book reply
3835                     static char bookMove[MSG_SIZ]; // a bit generous?
3836
3837                     programStats.nodes = programStats.depth = programStats.time =
3838                     programStats.score = programStats.got_only_move = 0;
3839                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3840
3841                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3842                     strcat(bookMove, bookHit);
3843                     HandleMachineMove(bookMove, &first);
3844                 }
3845                 continue;
3846             }
3847
3848             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3849                  started == STARTED_HOLDINGS ||
3850                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3851                 /* Accumulate characters in move list or board */
3852                 parse[parse_pos++] = buf[i];
3853             }
3854
3855             /* Start of game messages.  Mostly we detect start of game
3856                when the first board image arrives.  On some versions
3857                of the ICS, though, we need to do a "refresh" after starting
3858                to observe in order to get the current board right away. */
3859             if (looking_at(buf, &i, "Adding game * to observation list")) {
3860                 started = STARTED_OBSERVE;
3861                 continue;
3862             }
3863
3864             /* Handle auto-observe */
3865             if (appData.autoObserve &&
3866                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3867                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3868                 char *player;
3869                 /* Choose the player that was highlighted, if any. */
3870                 if (star_match[0][0] == '\033' ||
3871                     star_match[1][0] != '\033') {
3872                     player = star_match[0];
3873                 } else {
3874                     player = star_match[2];
3875                 }
3876                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3877                         ics_prefix, StripHighlightAndTitle(player));
3878                 SendToICS(str);
3879
3880                 /* Save ratings from notify string */
3881                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3882                 player1Rating = string_to_rating(star_match[1]);
3883                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3884                 player2Rating = string_to_rating(star_match[3]);
3885
3886                 if (appData.debugMode)
3887                   fprintf(debugFP,
3888                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3889                           player1Name, player1Rating,
3890                           player2Name, player2Rating);
3891
3892                 continue;
3893             }
3894
3895             /* Deal with automatic examine mode after a game,
3896                and with IcsObserving -> IcsExamining transition */
3897             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3898                 looking_at(buf, &i, "has made you an examiner of game *")) {
3899
3900                 int gamenum = atoi(star_match[0]);
3901                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3902                     gamenum == ics_gamenum) {
3903                     /* We were already playing or observing this game;
3904                        no need to refetch history */
3905                     gameMode = IcsExamining;
3906                     if (pausing) {
3907                         pauseExamForwardMostMove = forwardMostMove;
3908                     } else if (currentMove < forwardMostMove) {
3909                         ForwardInner(forwardMostMove);
3910                     }
3911                 } else {
3912                     /* I don't think this case really can happen */
3913                     SendToICS(ics_prefix);
3914                     SendToICS("refresh\n");
3915                 }
3916                 continue;
3917             }
3918
3919             /* Error messages */
3920 //          if (ics_user_moved) {
3921             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3922                 if (looking_at(buf, &i, "Illegal move") ||
3923                     looking_at(buf, &i, "Not a legal move") ||
3924                     looking_at(buf, &i, "Your king is in check") ||
3925                     looking_at(buf, &i, "It isn't your turn") ||
3926                     looking_at(buf, &i, "It is not your move")) {
3927                     /* Illegal move */
3928                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3929                         currentMove = forwardMostMove-1;
3930                         DisplayMove(currentMove - 1); /* before DMError */
3931                         DrawPosition(FALSE, boards[currentMove]);
3932                         SwitchClocks(forwardMostMove-1); // [HGM] race
3933                         DisplayBothClocks();
3934                     }
3935                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3936                     ics_user_moved = 0;
3937                     continue;
3938                 }
3939             }
3940
3941             if (looking_at(buf, &i, "still have time") ||
3942                 looking_at(buf, &i, "not out of time") ||
3943                 looking_at(buf, &i, "either player is out of time") ||
3944                 looking_at(buf, &i, "has timeseal; checking")) {
3945                 /* We must have called his flag a little too soon */
3946                 whiteFlag = blackFlag = FALSE;
3947                 continue;
3948             }
3949
3950             if (looking_at(buf, &i, "added * seconds to") ||
3951                 looking_at(buf, &i, "seconds were added to")) {
3952                 /* Update the clocks */
3953                 SendToICS(ics_prefix);
3954                 SendToICS("refresh\n");
3955                 continue;
3956             }
3957
3958             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3959                 ics_clock_paused = TRUE;
3960                 StopClocks();
3961                 continue;
3962             }
3963
3964             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3965                 ics_clock_paused = FALSE;
3966                 StartClocks();
3967                 continue;
3968             }
3969
3970             /* Grab player ratings from the Creating: message.
3971                Note we have to check for the special case when
3972                the ICS inserts things like [white] or [black]. */
3973             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3974                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3975                 /* star_matches:
3976                    0    player 1 name (not necessarily white)
3977                    1    player 1 rating
3978                    2    empty, white, or black (IGNORED)
3979                    3    player 2 name (not necessarily black)
3980                    4    player 2 rating
3981
3982                    The names/ratings are sorted out when the game
3983                    actually starts (below).
3984                 */
3985                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3986                 player1Rating = string_to_rating(star_match[1]);
3987                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3988                 player2Rating = string_to_rating(star_match[4]);
3989
3990                 if (appData.debugMode)
3991                   fprintf(debugFP,
3992                           "Ratings from 'Creating:' %s %d, %s %d\n",
3993                           player1Name, player1Rating,
3994                           player2Name, player2Rating);
3995
3996                 continue;
3997             }
3998
3999             /* Improved generic start/end-of-game messages */
4000             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4001                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4002                 /* If tkind == 0: */
4003                 /* star_match[0] is the game number */
4004                 /*           [1] is the white player's name */
4005                 /*           [2] is the black player's name */
4006                 /* For end-of-game: */
4007                 /*           [3] is the reason for the game end */
4008                 /*           [4] is a PGN end game-token, preceded by " " */
4009                 /* For start-of-game: */
4010                 /*           [3] begins with "Creating" or "Continuing" */
4011                 /*           [4] is " *" or empty (don't care). */
4012                 int gamenum = atoi(star_match[0]);
4013                 char *whitename, *blackname, *why, *endtoken;
4014                 ChessMove endtype = EndOfFile;
4015
4016                 if (tkind == 0) {
4017                   whitename = star_match[1];
4018                   blackname = star_match[2];
4019                   why = star_match[3];
4020                   endtoken = star_match[4];
4021                 } else {
4022                   whitename = star_match[1];
4023                   blackname = star_match[3];
4024                   why = star_match[5];
4025                   endtoken = star_match[6];
4026                 }
4027
4028                 /* Game start messages */
4029                 if (strncmp(why, "Creating ", 9) == 0 ||
4030                     strncmp(why, "Continuing ", 11) == 0) {
4031                     gs_gamenum = gamenum;
4032                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4033                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4034                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4035 #if ZIPPY
4036                     if (appData.zippyPlay) {
4037                         ZippyGameStart(whitename, blackname);
4038                     }
4039 #endif /*ZIPPY*/
4040                     partnerBoardValid = FALSE; // [HGM] bughouse
4041                     continue;
4042                 }
4043
4044                 /* Game end messages */
4045                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4046                     ics_gamenum != gamenum) {
4047                     continue;
4048                 }
4049                 while (endtoken[0] == ' ') endtoken++;
4050                 switch (endtoken[0]) {
4051                   case '*':
4052                   default:
4053                     endtype = GameUnfinished;
4054                     break;
4055                   case '0':
4056                     endtype = BlackWins;
4057                     break;
4058                   case '1':
4059                     if (endtoken[1] == '/')
4060                       endtype = GameIsDrawn;
4061                     else
4062                       endtype = WhiteWins;
4063                     break;
4064                 }
4065                 GameEnds(endtype, why, GE_ICS);
4066 #if ZIPPY
4067                 if (appData.zippyPlay && first.initDone) {
4068                     ZippyGameEnd(endtype, why);
4069                     if (first.pr == NoProc) {
4070                       /* Start the next process early so that we'll
4071                          be ready for the next challenge */
4072                       StartChessProgram(&first);
4073                     }
4074                     /* Send "new" early, in case this command takes
4075                        a long time to finish, so that we'll be ready
4076                        for the next challenge. */
4077                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4078                     Reset(TRUE, TRUE);
4079                 }
4080 #endif /*ZIPPY*/
4081                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4082                 continue;
4083             }
4084
4085             if (looking_at(buf, &i, "Removing game * from observation") ||
4086                 looking_at(buf, &i, "no longer observing game *") ||
4087                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4088                 if (gameMode == IcsObserving &&
4089                     atoi(star_match[0]) == ics_gamenum)
4090                   {
4091                       /* icsEngineAnalyze */
4092                       if (appData.icsEngineAnalyze) {
4093                             ExitAnalyzeMode();
4094                             ModeHighlight();
4095                       }
4096                       StopClocks();
4097                       gameMode = IcsIdle;
4098                       ics_gamenum = -1;
4099                       ics_user_moved = FALSE;
4100                   }
4101                 continue;
4102             }
4103
4104             if (looking_at(buf, &i, "no longer examining game *")) {
4105                 if (gameMode == IcsExamining &&
4106                     atoi(star_match[0]) == ics_gamenum)
4107                   {
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             /* Advance leftover_start past any newlines we find,
4116                so only partial lines can get reparsed */
4117             if (looking_at(buf, &i, "\n")) {
4118                 prevColor = curColor;
4119                 if (curColor != ColorNormal) {
4120                     if (oldi > next_out) {
4121                         SendToPlayer(&buf[next_out], oldi - next_out);
4122                         next_out = oldi;
4123                     }
4124                     Colorize(ColorNormal, FALSE);
4125                     curColor = ColorNormal;
4126                 }
4127                 if (started == STARTED_BOARD) {
4128                     started = STARTED_NONE;
4129                     parse[parse_pos] = NULLCHAR;
4130                     ParseBoard12(parse);
4131                     ics_user_moved = 0;
4132
4133                     /* Send premove here */
4134                     if (appData.premove) {
4135                       char str[MSG_SIZ];
4136                       if (currentMove == 0 &&
4137                           gameMode == IcsPlayingWhite &&
4138                           appData.premoveWhite) {
4139                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4140                         if (appData.debugMode)
4141                           fprintf(debugFP, "Sending premove:\n");
4142                         SendToICS(str);
4143                       } else if (currentMove == 1 &&
4144                                  gameMode == IcsPlayingBlack &&
4145                                  appData.premoveBlack) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (gotPremove) {
4151                         gotPremove = 0;
4152                         ClearPremoveHighlights();
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                           UserMoveEvent(premoveFromX, premoveFromY,
4156                                         premoveToX, premoveToY,
4157                                         premovePromoChar);
4158                       }
4159                     }
4160
4161                     /* Usually suppress following prompt */
4162                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4163                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4164                         if (looking_at(buf, &i, "*% ")) {
4165                             savingComment = FALSE;
4166                             suppressKibitz = 0;
4167                         }
4168                     }
4169                     next_out = i;
4170                 } else if (started == STARTED_HOLDINGS) {
4171                     int gamenum;
4172                     char new_piece[MSG_SIZ];
4173                     started = STARTED_NONE;
4174                     parse[parse_pos] = NULLCHAR;
4175                     if (appData.debugMode)
4176                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4177                                                         parse, currentMove);
4178                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4179                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4180                         if (gameInfo.variant == VariantNormal) {
4181                           /* [HGM] We seem to switch variant during a game!
4182                            * Presumably no holdings were displayed, so we have
4183                            * to move the position two files to the right to
4184                            * create room for them!
4185                            */
4186                           VariantClass newVariant;
4187                           switch(gameInfo.boardWidth) { // base guess on board width
4188                                 case 9:  newVariant = VariantShogi; break;
4189                                 case 10: newVariant = VariantGreat; break;
4190                                 default: newVariant = VariantCrazyhouse; break;
4191                           }
4192                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4193                           /* Get a move list just to see the header, which
4194                              will tell us whether this is really bug or zh */
4195                           if (ics_getting_history == H_FALSE) {
4196                             ics_getting_history = H_REQUESTED;
4197                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4198                             SendToICS(str);
4199                           }
4200                         }
4201                         new_piece[0] = NULLCHAR;
4202                         sscanf(parse, "game %d white [%s black [%s <- %s",
4203                                &gamenum, white_holding, black_holding,
4204                                new_piece);
4205                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4206                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4207                         /* [HGM] copy holdings to board holdings area */
4208                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4209                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4210                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4211 #if ZIPPY
4212                         if (appData.zippyPlay && first.initDone) {
4213                             ZippyHoldings(white_holding, black_holding,
4214                                           new_piece);
4215                         }
4216 #endif /*ZIPPY*/
4217                         if (tinyLayout || smallLayout) {
4218                             char wh[16], bh[16];
4219                             PackHolding(wh, white_holding);
4220                             PackHolding(bh, black_holding);
4221                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4222                                     gameInfo.white, gameInfo.black);
4223                         } else {
4224                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4225                                     gameInfo.white, white_holding, _("vs."),
4226                                     gameInfo.black, black_holding);
4227                         }
4228                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4229                         DrawPosition(FALSE, boards[currentMove]);
4230                         DisplayTitle(str);
4231                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to partner-board holdings area */
4238                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4239                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4240                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4241                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4243                       }
4244                     }
4245                     /* Suppress following prompt */
4246                     if (looking_at(buf, &i, "*% ")) {
4247                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4248                         savingComment = FALSE;
4249                         suppressKibitz = 0;
4250                     }
4251                     next_out = i;
4252                 }
4253                 continue;
4254             }
4255
4256             i++;                /* skip unparsed character and loop back */
4257         }
4258
4259         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4260 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4261 //          SendToPlayer(&buf[next_out], i - next_out);
4262             started != STARTED_HOLDINGS && leftover_start > next_out) {
4263             SendToPlayer(&buf[next_out], leftover_start - next_out);
4264             next_out = i;
4265         }
4266
4267         leftover_len = buf_len - leftover_start;
4268         /* if buffer ends with something we couldn't parse,
4269            reparse it after appending the next read */
4270
4271     } else if (count == 0) {
4272         RemoveInputSource(isr);
4273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4274     } else {
4275         DisplayFatalError(_("Error reading from ICS"), error, 1);
4276     }
4277 }
4278
4279
4280 /* Board style 12 looks like this:
4281
4282    <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
4283
4284  * The "<12> " is stripped before it gets to this routine.  The two
4285  * trailing 0's (flip state and clock ticking) are later addition, and
4286  * some chess servers may not have them, or may have only the first.
4287  * Additional trailing fields may be added in the future.
4288  */
4289
4290 #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"
4291
4292 #define RELATION_OBSERVING_PLAYED    0
4293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4294 #define RELATION_PLAYING_MYMOVE      1
4295 #define RELATION_PLAYING_NOTMYMOVE  -1
4296 #define RELATION_EXAMINING           2
4297 #define RELATION_ISOLATED_BOARD     -3
4298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4299
4300 void
4301 ParseBoard12 (char *string)
4302 {
4303 #if ZIPPY
4304     int i, takeback;
4305     char *bookHit = NULL; // [HGM] book
4306 #endif
4307     GameMode newGameMode;
4308     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4309     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4310     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4311     char to_play, board_chars[200];
4312     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4313     char black[32], white[32];
4314     Board board;
4315     int prevMove = currentMove;
4316     int ticking = 2;
4317     ChessMove moveType;
4318     int fromX, fromY, toX, toY;
4319     char promoChar;
4320     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4321     Boolean weird = FALSE, reqFlag = FALSE;
4322
4323     fromX = fromY = toX = toY = -1;
4324
4325     newGame = FALSE;
4326
4327     if (appData.debugMode)
4328       fprintf(debugFP, "Parsing board: %s\n", string);
4329
4330     move_str[0] = NULLCHAR;
4331     elapsed_time[0] = NULLCHAR;
4332     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4333         int  i = 0, j;
4334         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4335             if(string[i] == ' ') { ranks++; files = 0; }
4336             else files++;
4337             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4338             i++;
4339         }
4340         for(j = 0; j <i; j++) board_chars[j] = string[j];
4341         board_chars[i] = '\0';
4342         string += i + 1;
4343     }
4344     n = sscanf(string, PATTERN, &to_play, &double_push,
4345                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4346                &gamenum, white, black, &relation, &basetime, &increment,
4347                &white_stren, &black_stren, &white_time, &black_time,
4348                &moveNum, str, elapsed_time, move_str, &ics_flip,
4349                &ticking);
4350
4351     if (n < 21) {
4352         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4353         DisplayError(str, 0);
4354         return;
4355     }
4356
4357     /* Convert the move number to internal form */
4358     moveNum = (moveNum - 1) * 2;
4359     if (to_play == 'B') moveNum++;
4360     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4361       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4362                         0, 1);
4363       return;
4364     }
4365
4366     switch (relation) {
4367       case RELATION_OBSERVING_PLAYED:
4368       case RELATION_OBSERVING_STATIC:
4369         if (gamenum == -1) {
4370             /* Old ICC buglet */
4371             relation = RELATION_OBSERVING_STATIC;
4372         }
4373         newGameMode = IcsObserving;
4374         break;
4375       case RELATION_PLAYING_MYMOVE:
4376       case RELATION_PLAYING_NOTMYMOVE:
4377         newGameMode =
4378           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4379             IcsPlayingWhite : IcsPlayingBlack;
4380         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4381         break;
4382       case RELATION_EXAMINING:
4383         newGameMode = IcsExamining;
4384         break;
4385       case RELATION_ISOLATED_BOARD:
4386       default:
4387         /* Just display this board.  If user was doing something else,
4388            we will forget about it until the next board comes. */
4389         newGameMode = IcsIdle;
4390         break;
4391       case RELATION_STARTING_POSITION:
4392         newGameMode = gameMode;
4393         break;
4394     }
4395
4396     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4397         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4398          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4399       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4400       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4401       static int lastBgGame = -1;
4402       char *toSqr;
4403       for (k = 0; k < ranks; k++) {
4404         for (j = 0; j < files; j++)
4405           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4406         if(gameInfo.holdingsWidth > 1) {
4407              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4408              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409         }
4410       }
4411       CopyBoard(partnerBoard, board);
4412       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4413         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4414         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4415       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4416       if(toSqr = strchr(str, '-')) {
4417         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4418         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4419       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4420       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4421       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4422       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4423       if(twoBoards) {
4424           DisplayWhiteClock(white_time*fac, to_play == 'W');
4425           DisplayBlackClock(black_time*fac, to_play != 'W');
4426           activePartner = to_play;
4427           if(gamenum != lastBgGame) {
4428               char buf[MSG_SIZ];
4429               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4430               DisplayTitle(buf);
4431           }
4432           lastBgGame = gamenum;
4433           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4434                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4435       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4436                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4437       if(!twoBoards) DisplayMessage(partnerStatus, "");
4438         partnerBoardValid = TRUE;
4439       return;
4440     }
4441
4442     if(appData.dualBoard && appData.bgObserve) {
4443         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4444             SendToICS(ics_prefix), SendToICS("pobserve\n");
4445         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4446             char buf[MSG_SIZ];
4447             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4448             SendToICS(buf);
4449         }
4450     }
4451
4452     /* Modify behavior for initial board display on move listing
4453        of wild games.
4454        */
4455     switch (ics_getting_history) {
4456       case H_FALSE:
4457       case H_REQUESTED:
4458         break;
4459       case H_GOT_REQ_HEADER:
4460       case H_GOT_UNREQ_HEADER:
4461         /* This is the initial position of the current game */
4462         gamenum = ics_gamenum;
4463         moveNum = 0;            /* old ICS bug workaround */
4464         if (to_play == 'B') {
4465           startedFromSetupPosition = TRUE;
4466           blackPlaysFirst = TRUE;
4467           moveNum = 1;
4468           if (forwardMostMove == 0) forwardMostMove = 1;
4469           if (backwardMostMove == 0) backwardMostMove = 1;
4470           if (currentMove == 0) currentMove = 1;
4471         }
4472         newGameMode = gameMode;
4473         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4474         break;
4475       case H_GOT_UNWANTED_HEADER:
4476         /* This is an initial board that we don't want */
4477         return;
4478       case H_GETTING_MOVES:
4479         /* Should not happen */
4480         DisplayError(_("Error gathering move list: extra board"), 0);
4481         ics_getting_history = H_FALSE;
4482         return;
4483     }
4484
4485    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4486                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4487                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4488      /* [HGM] We seem to have switched variant unexpectedly
4489       * Try to guess new variant from board size
4490       */
4491           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4492           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4493           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4494           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4495           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4496           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4497           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4498           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4499           /* Get a move list just to see the header, which
4500              will tell us whether this is really bug or zh */
4501           if (ics_getting_history == H_FALSE) {
4502             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4503             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4504             SendToICS(str);
4505           }
4506     }
4507
4508     /* Take action if this is the first board of a new game, or of a
4509        different game than is currently being displayed.  */
4510     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4511         relation == RELATION_ISOLATED_BOARD) {
4512
4513         /* Forget the old game and get the history (if any) of the new one */
4514         if (gameMode != BeginningOfGame) {
4515           Reset(TRUE, TRUE);
4516         }
4517         newGame = TRUE;
4518         if (appData.autoRaiseBoard) BoardToTop();
4519         prevMove = -3;
4520         if (gamenum == -1) {
4521             newGameMode = IcsIdle;
4522         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4523                    appData.getMoveList && !reqFlag) {
4524             /* Need to get game history */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529
4530         /* Initially flip the board to have black on the bottom if playing
4531            black or if the ICS flip flag is set, but let the user change
4532            it with the Flip View button. */
4533         flipView = appData.autoFlipView ?
4534           (newGameMode == IcsPlayingBlack) || ics_flip :
4535           appData.flipView;
4536
4537         /* Done with values from previous mode; copy in new ones */
4538         gameMode = newGameMode;
4539         ModeHighlight();
4540         ics_gamenum = gamenum;
4541         if (gamenum == gs_gamenum) {
4542             int klen = strlen(gs_kind);
4543             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4544             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4545             gameInfo.event = StrSave(str);
4546         } else {
4547             gameInfo.event = StrSave("ICS game");
4548         }
4549         gameInfo.site = StrSave(appData.icsHost);
4550         gameInfo.date = PGNDate();
4551         gameInfo.round = StrSave("-");
4552         gameInfo.white = StrSave(white);
4553         gameInfo.black = StrSave(black);
4554         timeControl = basetime * 60 * 1000;
4555         timeControl_2 = 0;
4556         timeIncrement = increment * 1000;
4557         movesPerSession = 0;
4558         gameInfo.timeControl = TimeControlTagValue();
4559         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4560   if (appData.debugMode) {
4561     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4562     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4563     setbuf(debugFP, NULL);
4564   }
4565
4566         gameInfo.outOfBook = NULL;
4567
4568         /* Do we have the ratings? */
4569         if (strcmp(player1Name, white) == 0 &&
4570             strcmp(player2Name, black) == 0) {
4571             if (appData.debugMode)
4572               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4573                       player1Rating, player2Rating);
4574             gameInfo.whiteRating = player1Rating;
4575             gameInfo.blackRating = player2Rating;
4576         } else if (strcmp(player2Name, white) == 0 &&
4577                    strcmp(player1Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player2Rating, player1Rating);
4581             gameInfo.whiteRating = player2Rating;
4582             gameInfo.blackRating = player1Rating;
4583         }
4584         player1Name[0] = player2Name[0] = NULLCHAR;
4585
4586         /* Silence shouts if requested */
4587         if (appData.quietPlay &&
4588             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4589             SendToICS(ics_prefix);
4590             SendToICS("set shout 0\n");
4591         }
4592     }
4593
4594     /* Deal with midgame name changes */
4595     if (!newGame) {
4596         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4597             if (gameInfo.white) free(gameInfo.white);
4598             gameInfo.white = StrSave(white);
4599         }
4600         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4601             if (gameInfo.black) free(gameInfo.black);
4602             gameInfo.black = StrSave(black);
4603         }
4604     }
4605
4606     /* Throw away game result if anything actually changes in examine mode */
4607     if (gameMode == IcsExamining && !newGame) {
4608         gameInfo.result = GameUnfinished;
4609         if (gameInfo.resultDetails != NULL) {
4610             free(gameInfo.resultDetails);
4611             gameInfo.resultDetails = NULL;
4612         }
4613     }
4614
4615     /* In pausing && IcsExamining mode, we ignore boards coming
4616        in if they are in a different variation than we are. */
4617     if (pauseExamInvalid) return;
4618     if (pausing && gameMode == IcsExamining) {
4619         if (moveNum <= pauseExamForwardMostMove) {
4620             pauseExamInvalid = TRUE;
4621             forwardMostMove = pauseExamForwardMostMove;
4622             return;
4623         }
4624     }
4625
4626   if (appData.debugMode) {
4627     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4628   }
4629     /* Parse the board */
4630     for (k = 0; k < ranks; k++) {
4631       for (j = 0; j < files; j++)
4632         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4633       if(gameInfo.holdingsWidth > 1) {
4634            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4635            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4636       }
4637     }
4638     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4639       board[5][BOARD_RGHT+1] = WhiteAngel;
4640       board[6][BOARD_RGHT+1] = WhiteMarshall;
4641       board[1][0] = BlackMarshall;
4642       board[2][0] = BlackAngel;
4643       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4644     }
4645     CopyBoard(boards[moveNum], board);
4646     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4647     if (moveNum == 0) {
4648         startedFromSetupPosition =
4649           !CompareBoards(board, initialPosition);
4650         if(startedFromSetupPosition)
4651             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4652     }
4653
4654     /* [HGM] Set castling rights. Take the outermost Rooks,
4655        to make it also work for FRC opening positions. Note that board12
4656        is really defective for later FRC positions, as it has no way to
4657        indicate which Rook can castle if they are on the same side of King.
4658        For the initial position we grant rights to the outermost Rooks,
4659        and remember thos rights, and we then copy them on positions
4660        later in an FRC game. This means WB might not recognize castlings with
4661        Rooks that have moved back to their original position as illegal,
4662        but in ICS mode that is not its job anyway.
4663     */
4664     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4665     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4666
4667         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668             if(board[0][i] == WhiteRook) j = i;
4669         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671             if(board[0][i] == WhiteRook) j = i;
4672         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4675         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4678         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679
4680         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4681         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4682         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4683             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4684         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4685             if(board[BOARD_HEIGHT-1][k] == bKing)
4686                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4687         if(gameInfo.variant == VariantTwoKings) {
4688             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4689             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4690             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4691         }
4692     } else { int r;
4693         r = boards[moveNum][CASTLING][0] = initialRights[0];
4694         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4695         r = boards[moveNum][CASTLING][1] = initialRights[1];
4696         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4697         r = boards[moveNum][CASTLING][3] = initialRights[3];
4698         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4699         r = boards[moveNum][CASTLING][4] = initialRights[4];
4700         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4701         /* wildcastle kludge: always assume King has rights */
4702         r = boards[moveNum][CASTLING][2] = initialRights[2];
4703         r = boards[moveNum][CASTLING][5] = initialRights[5];
4704     }
4705     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4706     boards[moveNum][EP_STATUS] = EP_NONE;
4707     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4708     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4709     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4710
4711
4712     if (ics_getting_history == H_GOT_REQ_HEADER ||
4713         ics_getting_history == H_GOT_UNREQ_HEADER) {
4714         /* This was an initial position from a move list, not
4715            the current position */
4716         return;
4717     }
4718
4719     /* Update currentMove and known move number limits */
4720     newMove = newGame || moveNum > forwardMostMove;
4721
4722     if (newGame) {
4723         forwardMostMove = backwardMostMove = currentMove = moveNum;
4724         if (gameMode == IcsExamining && moveNum == 0) {
4725           /* Workaround for ICS limitation: we are not told the wild
4726              type when starting to examine a game.  But if we ask for
4727              the move list, the move list header will tell us */
4728             ics_getting_history = H_REQUESTED;
4729             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4730             SendToICS(str);
4731         }
4732     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4733                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4734 #if ZIPPY
4735         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4736         /* [HGM] applied this also to an engine that is silently watching        */
4737         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4738             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4739             gameInfo.variant == currentlyInitializedVariant) {
4740           takeback = forwardMostMove - moveNum;
4741           for (i = 0; i < takeback; i++) {
4742             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4743             SendToProgram("undo\n", &first);
4744           }
4745         }
4746 #endif
4747
4748         forwardMostMove = moveNum;
4749         if (!pausing || currentMove > forwardMostMove)
4750           currentMove = forwardMostMove;
4751     } else {
4752         /* New part of history that is not contiguous with old part */
4753         if (pausing && gameMode == IcsExamining) {
4754             pauseExamInvalid = TRUE;
4755             forwardMostMove = pauseExamForwardMostMove;
4756             return;
4757         }
4758         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4759 #if ZIPPY
4760             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4761                 // [HGM] when we will receive the move list we now request, it will be
4762                 // fed to the engine from the first move on. So if the engine is not
4763                 // in the initial position now, bring it there.
4764                 InitChessProgram(&first, 0);
4765             }
4766 #endif
4767             ics_getting_history = H_REQUESTED;
4768             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4769             SendToICS(str);
4770         }
4771         forwardMostMove = backwardMostMove = currentMove = moveNum;
4772     }
4773
4774     /* Update the clocks */
4775     if (strchr(elapsed_time, '.')) {
4776       /* Time is in ms */
4777       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4778       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4779     } else {
4780       /* Time is in seconds */
4781       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4782       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4783     }
4784
4785
4786 #if ZIPPY
4787     if (appData.zippyPlay && newGame &&
4788         gameMode != IcsObserving && gameMode != IcsIdle &&
4789         gameMode != IcsExamining)
4790       ZippyFirstBoard(moveNum, basetime, increment);
4791 #endif
4792
4793     /* Put the move on the move list, first converting
4794        to canonical algebraic form. */
4795     if (moveNum > 0) {
4796   if (appData.debugMode) {
4797     int f = forwardMostMove;
4798     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4799             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4800             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4801     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4802     fprintf(debugFP, "moveNum = %d\n", moveNum);
4803     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4804     setbuf(debugFP, NULL);
4805   }
4806         if (moveNum <= backwardMostMove) {
4807             /* We don't know what the board looked like before
4808                this move.  Punt. */
4809           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4810             strcat(parseList[moveNum - 1], " ");
4811             strcat(parseList[moveNum - 1], elapsed_time);
4812             moveList[moveNum - 1][0] = NULLCHAR;
4813         } else if (strcmp(move_str, "none") == 0) {
4814             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4815             /* Again, we don't know what the board looked like;
4816                this is really the start of the game. */
4817             parseList[moveNum - 1][0] = NULLCHAR;
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819             backwardMostMove = moveNum;
4820             startedFromSetupPosition = TRUE;
4821             fromX = fromY = toX = toY = -1;
4822         } else {
4823           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4824           //                 So we parse the long-algebraic move string in stead of the SAN move
4825           int valid; char buf[MSG_SIZ], *prom;
4826
4827           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4828                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4829           // str looks something like "Q/a1-a2"; kill the slash
4830           if(str[1] == '/')
4831             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4832           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4833           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4834                 strcat(buf, prom); // long move lacks promo specification!
4835           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4836                 if(appData.debugMode)
4837                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4838                 safeStrCpy(move_str, buf, MSG_SIZ);
4839           }
4840           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4841                                 &fromX, &fromY, &toX, &toY, &promoChar)
4842                || ParseOneMove(buf, moveNum - 1, &moveType,
4843                                 &fromX, &fromY, &toX, &toY, &promoChar);
4844           // end of long SAN patch
4845           if (valid) {
4846             (void) CoordsToAlgebraic(boards[moveNum - 1],
4847                                      PosFlags(moveNum - 1),
4848                                      fromY, fromX, toY, toX, promoChar,
4849                                      parseList[moveNum-1]);
4850             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4851               case MT_NONE:
4852               case MT_STALEMATE:
4853               default:
4854                 break;
4855               case MT_CHECK:
4856                 if(!IS_SHOGI(gameInfo.variant))
4857                     strcat(parseList[moveNum - 1], "+");
4858                 break;
4859               case MT_CHECKMATE:
4860               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4861                 strcat(parseList[moveNum - 1], "#");
4862                 break;
4863             }
4864             strcat(parseList[moveNum - 1], " ");
4865             strcat(parseList[moveNum - 1], elapsed_time);
4866             /* currentMoveString is set as a side-effect of ParseOneMove */
4867             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4868             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4869             strcat(moveList[moveNum - 1], "\n");
4870
4871             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4872                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4873               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4874                 ChessSquare old, new = boards[moveNum][k][j];
4875                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4876                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4877                   if(old == new) continue;
4878                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4879                   else if(new == WhiteWazir || new == BlackWazir) {
4880                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4881                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4882                       else boards[moveNum][k][j] = old; // preserve type of Gold
4883                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4884                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4885               }
4886           } else {
4887             /* Move from ICS was illegal!?  Punt. */
4888             if (appData.debugMode) {
4889               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4890               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4891             }
4892             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             moveList[moveNum - 1][0] = NULLCHAR;
4896             fromX = fromY = toX = toY = -1;
4897           }
4898         }
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4901     setbuf(debugFP, NULL);
4902   }
4903
4904 #if ZIPPY
4905         /* Send move to chess program (BEFORE animating it). */
4906         if (appData.zippyPlay && !newGame && newMove &&
4907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4908
4909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4912                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4913                             move_str);
4914                     DisplayError(str, 0);
4915                 } else {
4916                     if (first.sendTime) {
4917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4918                     }
4919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4920                     if (firstMove && !bookHit) {
4921                         firstMove = FALSE;
4922                         if (first.useColors) {
4923                           SendToProgram(gameMode == IcsPlayingWhite ?
4924                                         "white\ngo\n" :
4925                                         "black\ngo\n", &first);
4926                         } else {
4927                           SendToProgram("go\n", &first);
4928                         }
4929                         first.maybeThinking = TRUE;
4930                     }
4931                 }
4932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4933               if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4935                 DisplayError(str, 0);
4936               } else {
4937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4938                 SendMoveToProgram(moveNum - 1, &first);
4939               }
4940             }
4941         }
4942 #endif
4943     }
4944
4945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4946         /* If move comes from a remote source, animate it.  If it
4947            isn't remote, it will have already been animated. */
4948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4950         }
4951         if (!pausing && appData.highlightLastMove) {
4952             SetHighlights(fromX, fromY, toX, toY);
4953         }
4954     }
4955
4956     /* Start the clocks */
4957     whiteFlag = blackFlag = FALSE;
4958     appData.clockMode = !(basetime == 0 && increment == 0);
4959     if (ticking == 0) {
4960       ics_clock_paused = TRUE;
4961       StopClocks();
4962     } else if (ticking == 1) {
4963       ics_clock_paused = FALSE;
4964     }
4965     if (gameMode == IcsIdle ||
4966         relation == RELATION_OBSERVING_STATIC ||
4967         relation == RELATION_EXAMINING ||
4968         ics_clock_paused)
4969       DisplayBothClocks();
4970     else
4971       StartClocks();
4972
4973     /* Display opponents and material strengths */
4974     if (gameInfo.variant != VariantBughouse &&
4975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4976         if (tinyLayout || smallLayout) {
4977             if(gameInfo.variant == VariantNormal)
4978               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4980                     basetime, increment);
4981             else
4982               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4984                     basetime, increment, (int) gameInfo.variant);
4985         } else {
4986             if(gameInfo.variant == VariantNormal)
4987               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4988                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4989                     basetime, increment);
4990             else
4991               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4992                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4993                     basetime, increment, VariantName(gameInfo.variant));
4994         }
4995         DisplayTitle(str);
4996   if (appData.debugMode) {
4997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4998   }
4999     }
5000
5001
5002     /* Display the board */
5003     if (!pausing && !appData.noGUI) {
5004
5005       if (appData.premove)
5006           if (!gotPremove ||
5007              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5008              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5009               ClearPremoveHighlights();
5010
5011       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5012         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5013       DrawPosition(j, boards[currentMove]);
5014
5015       DisplayMove(moveNum - 1);
5016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5020       }
5021     }
5022
5023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5024 #if ZIPPY
5025     if(bookHit) { // [HGM] book: simulate book reply
5026         static char bookMove[MSG_SIZ]; // a bit generous?
5027
5028         programStats.nodes = programStats.depth = programStats.time =
5029         programStats.score = programStats.got_only_move = 0;
5030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5031
5032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5033         strcat(bookMove, bookHit);
5034         HandleMachineMove(bookMove, &first);
5035     }
5036 #endif
5037 }
5038
5039 void
5040 GetMoveListEvent ()
5041 {
5042     char buf[MSG_SIZ];
5043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5044         ics_getting_history = H_REQUESTED;
5045         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5046         SendToICS(buf);
5047     }
5048 }
5049
5050 void
5051 SendToBoth (char *msg)
5052 {   // to make it easy to keep two engines in step in dual analysis
5053     SendToProgram(msg, &first);
5054     if(second.analyzing) SendToProgram(msg, &second);
5055 }
5056
5057 void
5058 AnalysisPeriodicEvent (int force)
5059 {
5060     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5061          && !force) || !appData.periodicUpdates)
5062       return;
5063
5064     /* Send . command to Crafty to collect stats */
5065     SendToBoth(".\n");
5066
5067     /* Don't send another until we get a response (this makes
5068        us stop sending to old Crafty's which don't understand
5069        the "." command (sending illegal cmds resets node count & time,
5070        which looks bad)) */
5071     programStats.ok_to_send = 0;
5072 }
5073
5074 void
5075 ics_update_width (int new_width)
5076 {
5077         ics_printf("set width %d\n", new_width);
5078 }
5079
5080 void
5081 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5082 {
5083     char buf[MSG_SIZ];
5084
5085     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5086         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5087             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5088             SendToProgram(buf, cps);
5089             return;
5090         }
5091         // null move in variant where engine does not understand it (for analysis purposes)
5092         SendBoard(cps, moveNum + 1); // send position after move in stead.
5093         return;
5094     }
5095     if (cps->useUsermove) {
5096       SendToProgram("usermove ", cps);
5097     }
5098     if (cps->useSAN) {
5099       char *space;
5100       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5101         int len = space - parseList[moveNum];
5102         memcpy(buf, parseList[moveNum], len);
5103         buf[len++] = '\n';
5104         buf[len] = NULLCHAR;
5105       } else {
5106         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5107       }
5108       SendToProgram(buf, cps);
5109     } else {
5110       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5111         AlphaRank(moveList[moveNum], 4);
5112         SendToProgram(moveList[moveNum], cps);
5113         AlphaRank(moveList[moveNum], 4); // and back
5114       } else
5115       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5116        * the engine. It would be nice to have a better way to identify castle
5117        * moves here. */
5118       if(appData.fischerCastling && cps->useOOCastle) {
5119         int fromX = moveList[moveNum][0] - AAA;
5120         int fromY = moveList[moveNum][1] - ONE;
5121         int toX = moveList[moveNum][2] - AAA;
5122         int toY = moveList[moveNum][3] - ONE;
5123         if((boards[moveNum][fromY][fromX] == WhiteKing
5124             && boards[moveNum][toY][toX] == WhiteRook)
5125            || (boards[moveNum][fromY][fromX] == BlackKing
5126                && boards[moveNum][toY][toX] == BlackRook)) {
5127           if(toX > fromX) SendToProgram("O-O\n", cps);
5128           else SendToProgram("O-O-O\n", cps);
5129         }
5130         else SendToProgram(moveList[moveNum], cps);
5131       } else
5132       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5133           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5134                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5137           SendToProgram(buf, cps);
5138       } else
5139       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5140         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5141           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5142           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5143                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5144         } else
5145           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5146                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5147         SendToProgram(buf, cps);
5148       }
5149       else SendToProgram(moveList[moveNum], cps);
5150       /* End of additions by Tord */
5151     }
5152
5153     /* [HGM] setting up the opening has brought engine in force mode! */
5154     /*       Send 'go' if we are in a mode where machine should play. */
5155     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5156         (gameMode == TwoMachinesPlay   ||
5157 #if ZIPPY
5158          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5159 #endif
5160          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5161         SendToProgram("go\n", cps);
5162   if (appData.debugMode) {
5163     fprintf(debugFP, "(extra)\n");
5164   }
5165     }
5166     setboardSpoiledMachineBlack = 0;
5167 }
5168
5169 void
5170 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5171 {
5172     char user_move[MSG_SIZ];
5173     char suffix[4];
5174
5175     if(gameInfo.variant == VariantSChess && promoChar) {
5176         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5177         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5178     } else suffix[0] = NULLCHAR;
5179
5180     switch (moveType) {
5181       default:
5182         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5183                 (int)moveType, fromX, fromY, toX, toY);
5184         DisplayError(user_move + strlen("say "), 0);
5185         break;
5186       case WhiteKingSideCastle:
5187       case BlackKingSideCastle:
5188       case WhiteQueenSideCastleWild:
5189       case BlackQueenSideCastleWild:
5190       /* PUSH Fabien */
5191       case WhiteHSideCastleFR:
5192       case BlackHSideCastleFR:
5193       /* POP Fabien */
5194         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5195         break;
5196       case WhiteQueenSideCastle:
5197       case BlackQueenSideCastle:
5198       case WhiteKingSideCastleWild:
5199       case BlackKingSideCastleWild:
5200       /* PUSH Fabien */
5201       case WhiteASideCastleFR:
5202       case BlackASideCastleFR:
5203       /* POP Fabien */
5204         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5205         break;
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5209         break;
5210       case WhitePromotion:
5211       case BlackPromotion:
5212         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5213            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5214           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5215                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5216                 PieceToChar(WhiteFerz));
5217         else if(gameInfo.variant == VariantGreat)
5218           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5220                 PieceToChar(WhiteMan));
5221         else
5222           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5224                 promoChar);
5225         break;
5226       case WhiteDrop:
5227       case BlackDrop:
5228       drop:
5229         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5230                  ToUpper(PieceToChar((ChessSquare) fromX)),
5231                  AAA + toX, ONE + toY);
5232         break;
5233       case IllegalMove:  /* could be a variant we don't quite understand */
5234         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241     }
5242     SendToICS(user_move);
5243     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5244         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5245 }
5246
5247 void
5248 UploadGameEvent ()
5249 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5250     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5251     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5252     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5253       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5254       return;
5255     }
5256     if(gameMode != IcsExamining) { // is this ever not the case?
5257         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5258
5259         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5260           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5261         } else { // on FICS we must first go to general examine mode
5262           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5263         }
5264         if(gameInfo.variant != VariantNormal) {
5265             // try figure out wild number, as xboard names are not always valid on ICS
5266             for(i=1; i<=36; i++) {
5267               snprintf(buf, MSG_SIZ, "wild/%d", i);
5268                 if(StringToVariant(buf) == gameInfo.variant) break;
5269             }
5270             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5271             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5272             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5273         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5274         SendToICS(ics_prefix);
5275         SendToICS(buf);
5276         if(startedFromSetupPosition || backwardMostMove != 0) {
5277           fen = PositionToFEN(backwardMostMove, NULL, 1);
5278           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5279             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5280             SendToICS(buf);
5281           } else { // FICS: everything has to set by separate bsetup commands
5282             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5283             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5284             SendToICS(buf);
5285             if(!WhiteOnMove(backwardMostMove)) {
5286                 SendToICS("bsetup tomove black\n");
5287             }
5288             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5289             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5290             SendToICS(buf);
5291             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5292             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5293             SendToICS(buf);
5294             i = boards[backwardMostMove][EP_STATUS];
5295             if(i >= 0) { // set e.p.
5296               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5297                 SendToICS(buf);
5298             }
5299             bsetup++;
5300           }
5301         }
5302       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5303             SendToICS("bsetup done\n"); // switch to normal examining.
5304     }
5305     for(i = backwardMostMove; i<last; i++) {
5306         char buf[20];
5307         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5308         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5309             int len = strlen(moveList[i]);
5310             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5311             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5312         }
5313         SendToICS(buf);
5314     }
5315     SendToICS(ics_prefix);
5316     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5317 }
5318
5319 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5320
5321 void
5322 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5323 {
5324     if (rf == DROP_RANK) {
5325       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5326       sprintf(move, "%c@%c%c\n",
5327                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5328     } else {
5329         if (promoChar == 'x' || promoChar == NULLCHAR) {
5330           sprintf(move, "%c%c%c%c\n",
5331                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5332           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5333         } else {
5334             sprintf(move, "%c%c%c%c%c\n",
5335                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5336         }
5337     }
5338 }
5339
5340 void
5341 ProcessICSInitScript (FILE *f)
5342 {
5343     char buf[MSG_SIZ];
5344
5345     while (fgets(buf, MSG_SIZ, f)) {
5346         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5347     }
5348
5349     fclose(f);
5350 }
5351
5352
5353 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5354 int dragging;
5355 static ClickType lastClickType;
5356
5357 int
5358 Partner (ChessSquare *p)
5359 { // change piece into promotion partner if one shogi-promotes to the other
5360   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5361   ChessSquare partner;
5362   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5363   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5364   *p = partner;
5365   return 1;
5366 }
5367
5368 void
5369 Sweep (int step)
5370 {
5371     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5372     static int toggleFlag;
5373     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5374     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5375     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5376     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5377     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5378     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5379     do {
5380         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5381         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5382         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5383         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5384         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5385         if(!step) step = -1;
5386     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5387             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5388             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5389             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5390     if(toX >= 0) {
5391         int victim = boards[currentMove][toY][toX];
5392         boards[currentMove][toY][toX] = promoSweep;
5393         DrawPosition(FALSE, boards[currentMove]);
5394         boards[currentMove][toY][toX] = victim;
5395     } else
5396     ChangeDragPiece(promoSweep);
5397 }
5398
5399 int
5400 PromoScroll (int x, int y)
5401 {
5402   int step = 0;
5403
5404   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5405   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5406   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5407   if(!step) return FALSE;
5408   lastX = x; lastY = y;
5409   if((promoSweep < BlackPawn) == flipView) step = -step;
5410   if(step > 0) selectFlag = 1;
5411   if(!selectFlag) Sweep(step);
5412   return FALSE;
5413 }
5414
5415 void
5416 NextPiece (int step)
5417 {
5418     ChessSquare piece = boards[currentMove][toY][toX];
5419     do {
5420         pieceSweep -= step;
5421         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5422         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5423         if(!step) step = -1;
5424     } while(PieceToChar(pieceSweep) == '.');
5425     boards[currentMove][toY][toX] = pieceSweep;
5426     DrawPosition(FALSE, boards[currentMove]);
5427     boards[currentMove][toY][toX] = piece;
5428 }
5429 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5430 void
5431 AlphaRank (char *move, int n)
5432 {
5433 //    char *p = move, c; int x, y;
5434
5435     if (appData.debugMode) {
5436         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5437     }
5438
5439     if(move[1]=='*' &&
5440        move[2]>='0' && move[2]<='9' &&
5441        move[3]>='a' && move[3]<='x'    ) {
5442         move[1] = '@';
5443         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5444         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5445     } else
5446     if(move[0]>='0' && move[0]<='9' &&
5447        move[1]>='a' && move[1]<='x' &&
5448        move[2]>='0' && move[2]<='9' &&
5449        move[3]>='a' && move[3]<='x'    ) {
5450         /* input move, Shogi -> normal */
5451         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5452         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5453         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5454         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5455     } else
5456     if(move[1]=='@' &&
5457        move[3]>='0' && move[3]<='9' &&
5458        move[2]>='a' && move[2]<='x'    ) {
5459         move[1] = '*';
5460         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5462     } else
5463     if(
5464        move[0]>='a' && move[0]<='x' &&
5465        move[3]>='0' && move[3]<='9' &&
5466        move[2]>='a' && move[2]<='x'    ) {
5467          /* output move, normal -> Shogi */
5468         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5469         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5470         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5471         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5472         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5473     }
5474     if (appData.debugMode) {
5475         fprintf(debugFP, "   out = '%s'\n", move);
5476     }
5477 }
5478
5479 char yy_textstr[8000];
5480
5481 /* Parser for moves from gnuchess, ICS, or user typein box */
5482 Boolean
5483 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5484 {
5485     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5486
5487     switch (*moveType) {
5488       case WhitePromotion:
5489       case BlackPromotion:
5490       case WhiteNonPromotion:
5491       case BlackNonPromotion:
5492       case NormalMove:
5493       case FirstLeg:
5494       case WhiteCapturesEnPassant:
5495       case BlackCapturesEnPassant:
5496       case WhiteKingSideCastle:
5497       case WhiteQueenSideCastle:
5498       case BlackKingSideCastle:
5499       case BlackQueenSideCastle:
5500       case WhiteKingSideCastleWild:
5501       case WhiteQueenSideCastleWild:
5502       case BlackKingSideCastleWild:
5503       case BlackQueenSideCastleWild:
5504       /* Code added by Tord: */
5505       case WhiteHSideCastleFR:
5506       case WhiteASideCastleFR:
5507       case BlackHSideCastleFR:
5508       case BlackASideCastleFR:
5509       /* End of code added by Tord */
5510       case IllegalMove:         /* bug or odd chess variant */
5511         *fromX = currentMoveString[0] - AAA;
5512         *fromY = currentMoveString[1] - ONE;
5513         *toX = currentMoveString[2] - AAA;
5514         *toY = currentMoveString[3] - ONE;
5515         *promoChar = currentMoveString[4];
5516         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5517             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5518     if (appData.debugMode) {
5519         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5520     }
5521             *fromX = *fromY = *toX = *toY = 0;
5522             return FALSE;
5523         }
5524         if (appData.testLegality) {
5525           return (*moveType != IllegalMove);
5526         } else {
5527           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5528                          // [HGM] lion: if this is a double move we are less critical
5529                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5530         }
5531
5532       case WhiteDrop:
5533       case BlackDrop:
5534         *fromX = *moveType == WhiteDrop ?
5535           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536           (int) CharToPiece(ToLower(currentMoveString[0]));
5537         *fromY = DROP_RANK;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = NULLCHAR;
5541         return TRUE;
5542
5543       case AmbiguousMove:
5544       case ImpossibleMove:
5545       case EndOfFile:
5546       case ElapsedTime:
5547       case Comment:
5548       case PGNTag:
5549       case NAG:
5550       case WhiteWins:
5551       case BlackWins:
5552       case GameIsDrawn:
5553       default:
5554     if (appData.debugMode) {
5555         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5556     }
5557         /* bug? */
5558         *fromX = *fromY = *toX = *toY = 0;
5559         *promoChar = NULLCHAR;
5560         return FALSE;
5561     }
5562 }
5563
5564 Boolean pushed = FALSE;
5565 char *lastParseAttempt;
5566
5567 void
5568 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5569 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5570   int fromX, fromY, toX, toY; char promoChar;
5571   ChessMove moveType;
5572   Boolean valid;
5573   int nr = 0;
5574
5575   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5576   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5577     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5578     pushed = TRUE;
5579   }
5580   endPV = forwardMostMove;
5581   do {
5582     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5583     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5584     lastParseAttempt = pv;
5585     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5586     if(!valid && nr == 0 &&
5587        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5588         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5589         // Hande case where played move is different from leading PV move
5590         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5591         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5592         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5593         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5594           endPV += 2; // if position different, keep this
5595           moveList[endPV-1][0] = fromX + AAA;
5596           moveList[endPV-1][1] = fromY + ONE;
5597           moveList[endPV-1][2] = toX + AAA;
5598           moveList[endPV-1][3] = toY + ONE;
5599           parseList[endPV-1][0] = NULLCHAR;
5600           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5601         }
5602       }
5603     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5604     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5605     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5606     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5607         valid++; // allow comments in PV
5608         continue;
5609     }
5610     nr++;
5611     if(endPV+1 > framePtr) break; // no space, truncate
5612     if(!valid) break;
5613     endPV++;
5614     CopyBoard(boards[endPV], boards[endPV-1]);
5615     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5616     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5617     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5618     CoordsToAlgebraic(boards[endPV - 1],
5619                              PosFlags(endPV - 1),
5620                              fromY, fromX, toY, toX, promoChar,
5621                              parseList[endPV - 1]);
5622   } while(valid);
5623   if(atEnd == 2) return; // used hidden, for PV conversion
5624   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5625   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5626   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5627                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5628   DrawPosition(TRUE, boards[currentMove]);
5629 }
5630
5631 int
5632 MultiPV (ChessProgramState *cps)
5633 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5634         int i;
5635         for(i=0; i<cps->nrOptions; i++)
5636             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5637                 return i;
5638         return -1;
5639 }
5640
5641 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5642
5643 Boolean
5644 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5645 {
5646         int startPV, multi, lineStart, origIndex = index;
5647         char *p, buf2[MSG_SIZ];
5648         ChessProgramState *cps = (pane ? &second : &first);
5649
5650         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5651         lastX = x; lastY = y;
5652         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5653         lineStart = startPV = index;
5654         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5655         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5656         index = startPV;
5657         do{ while(buf[index] && buf[index] != '\n') index++;
5658         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5659         buf[index] = 0;
5660         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5661                 int n = cps->option[multi].value;
5662                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5663                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5664                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5665                 cps->option[multi].value = n;
5666                 *start = *end = 0;
5667                 return FALSE;
5668         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5669                 ExcludeClick(origIndex - lineStart);
5670                 return FALSE;
5671         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5672                 Collapse(origIndex - lineStart);
5673                 return FALSE;
5674         }
5675         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5676         *start = startPV; *end = index-1;
5677         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5678         return TRUE;
5679 }
5680
5681 char *
5682 PvToSAN (char *pv)
5683 {
5684         static char buf[10*MSG_SIZ];
5685         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5686         *buf = NULLCHAR;
5687         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5688         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5689         for(i = forwardMostMove; i<endPV; i++){
5690             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5691             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5692             k += strlen(buf+k);
5693         }
5694         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5695         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5696         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5697         endPV = savedEnd;
5698         return buf;
5699 }
5700
5701 Boolean
5702 LoadPV (int x, int y)
5703 { // called on right mouse click to load PV
5704   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5705   lastX = x; lastY = y;
5706   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5707   extendGame = FALSE;
5708   return TRUE;
5709 }
5710
5711 void
5712 UnLoadPV ()
5713 {
5714   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5715   if(endPV < 0) return;
5716   if(appData.autoCopyPV) CopyFENToClipboard();
5717   endPV = -1;
5718   if(extendGame && currentMove > forwardMostMove) {
5719         Boolean saveAnimate = appData.animate;
5720         if(pushed) {
5721             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5722                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5723             } else storedGames--; // abandon shelved tail of original game
5724         }
5725         pushed = FALSE;
5726         forwardMostMove = currentMove;
5727         currentMove = oldFMM;
5728         appData.animate = FALSE;
5729         ToNrEvent(forwardMostMove);
5730         appData.animate = saveAnimate;
5731   }
5732   currentMove = forwardMostMove;
5733   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5734   ClearPremoveHighlights();
5735   DrawPosition(TRUE, boards[currentMove]);
5736 }
5737
5738 void
5739 MovePV (int x, int y, int h)
5740 { // step through PV based on mouse coordinates (called on mouse move)
5741   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5742
5743   // we must somehow check if right button is still down (might be released off board!)
5744   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5745   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5746   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5747   if(!step) return;
5748   lastX = x; lastY = y;
5749
5750   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5751   if(endPV < 0) return;
5752   if(y < margin) step = 1; else
5753   if(y > h - margin) step = -1;
5754   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5755   currentMove += step;
5756   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5757   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5758                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5759   DrawPosition(FALSE, boards[currentMove]);
5760 }
5761
5762
5763 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5764 // All positions will have equal probability, but the current method will not provide a unique
5765 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5766 #define DARK 1
5767 #define LITE 2
5768 #define ANY 3
5769
5770 int squaresLeft[4];
5771 int piecesLeft[(int)BlackPawn];
5772 int seed, nrOfShuffles;
5773
5774 void
5775 GetPositionNumber ()
5776 {       // sets global variable seed
5777         int i;
5778
5779         seed = appData.defaultFrcPosition;
5780         if(seed < 0) { // randomize based on time for negative FRC position numbers
5781                 for(i=0; i<50; i++) seed += random();
5782                 seed = random() ^ random() >> 8 ^ random() << 8;
5783                 if(seed<0) seed = -seed;
5784         }
5785 }
5786
5787 int
5788 put (Board board, int pieceType, int rank, int n, int shade)
5789 // put the piece on the (n-1)-th empty squares of the given shade
5790 {
5791         int i;
5792
5793         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5794                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5795                         board[rank][i] = (ChessSquare) pieceType;
5796                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5797                         squaresLeft[ANY]--;
5798                         piecesLeft[pieceType]--;
5799                         return i;
5800                 }
5801         }
5802         return -1;
5803 }
5804
5805
5806 void
5807 AddOnePiece (Board board, int pieceType, int rank, int shade)
5808 // calculate where the next piece goes, (any empty square), and put it there
5809 {
5810         int i;
5811
5812         i = seed % squaresLeft[shade];
5813         nrOfShuffles *= squaresLeft[shade];
5814         seed /= squaresLeft[shade];
5815         put(board, pieceType, rank, i, shade);
5816 }
5817
5818 void
5819 AddTwoPieces (Board board, int pieceType, int rank)
5820 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5821 {
5822         int i, n=squaresLeft[ANY], j=n-1, k;
5823
5824         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5825         i = seed % k;  // pick one
5826         nrOfShuffles *= k;
5827         seed /= k;
5828         while(i >= j) i -= j--;
5829         j = n - 1 - j; i += j;
5830         put(board, pieceType, rank, j, ANY);
5831         put(board, pieceType, rank, i, ANY);
5832 }
5833
5834 void
5835 SetUpShuffle (Board board, int number)
5836 {
5837         int i, p, first=1;
5838
5839         GetPositionNumber(); nrOfShuffles = 1;
5840
5841         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5842         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5843         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5844
5845         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5846
5847         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5848             p = (int) board[0][i];
5849             if(p < (int) BlackPawn) piecesLeft[p] ++;
5850             board[0][i] = EmptySquare;
5851         }
5852
5853         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5854             // shuffles restricted to allow normal castling put KRR first
5855             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5856                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5857             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5858                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5859             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5860                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5861             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5862                 put(board, WhiteRook, 0, 0, ANY);
5863             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5864         }
5865
5866         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5867             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5868             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5869                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5870                 while(piecesLeft[p] >= 2) {
5871                     AddOnePiece(board, p, 0, LITE);
5872                     AddOnePiece(board, p, 0, DARK);
5873                 }
5874                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5875             }
5876
5877         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5878             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5879             // but we leave King and Rooks for last, to possibly obey FRC restriction
5880             if(p == (int)WhiteRook) continue;
5881             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5882             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5883         }
5884
5885         // now everything is placed, except perhaps King (Unicorn) and Rooks
5886
5887         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5888             // Last King gets castling rights
5889             while(piecesLeft[(int)WhiteUnicorn]) {
5890                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5891                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5892             }
5893
5894             while(piecesLeft[(int)WhiteKing]) {
5895                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5896                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5897             }
5898
5899
5900         } else {
5901             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5902             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5903         }
5904
5905         // Only Rooks can be left; simply place them all
5906         while(piecesLeft[(int)WhiteRook]) {
5907                 i = put(board, WhiteRook, 0, 0, ANY);
5908                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5909                         if(first) {
5910                                 first=0;
5911                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5912                         }
5913                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5914                 }
5915         }
5916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5917             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5918         }
5919
5920         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5921 }
5922
5923 int
5924 SetCharTable (char *table, const char * map)
5925 /* [HGM] moved here from winboard.c because of its general usefulness */
5926 /*       Basically a safe strcpy that uses the last character as King */
5927 {
5928     int result = FALSE; int NrPieces;
5929
5930     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5931                     && NrPieces >= 12 && !(NrPieces&1)) {
5932         int i; /* [HGM] Accept even length from 12 to 34 */
5933
5934         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5935         for( i=0; i<NrPieces/2-1; i++ ) {
5936             table[i] = map[i];
5937             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5938         }
5939         table[(int) WhiteKing]  = map[NrPieces/2-1];
5940         table[(int) BlackKing]  = map[NrPieces-1];
5941
5942         result = TRUE;
5943     }
5944
5945     return result;
5946 }
5947
5948 void
5949 Prelude (Board board)
5950 {       // [HGM] superchess: random selection of exo-pieces
5951         int i, j, k; ChessSquare p;
5952         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5953
5954         GetPositionNumber(); // use FRC position number
5955
5956         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5957             SetCharTable(pieceToChar, appData.pieceToCharTable);
5958             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5959                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5960         }
5961
5962         j = seed%4;                 seed /= 4;
5963         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5964         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5965         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5966         j = seed%3 + (seed%3 >= j); seed /= 3;
5967         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5968         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5969         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5970         j = seed%3;                 seed /= 3;
5971         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5972         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5973         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5974         j = seed%2 + (seed%2 >= j); seed /= 2;
5975         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5979         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5980         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5981         put(board, exoPieces[0],    0, 0, ANY);
5982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5983 }
5984
5985 void
5986 InitPosition (int redraw)
5987 {
5988     ChessSquare (* pieces)[BOARD_FILES];
5989     int i, j, pawnRow=1, pieceRows=1, overrule,
5990     oldx = gameInfo.boardWidth,
5991     oldy = gameInfo.boardHeight,
5992     oldh = gameInfo.holdingsWidth;
5993     static int oldv;
5994
5995     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5996
5997     /* [AS] Initialize pv info list [HGM] and game status */
5998     {
5999         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6000             pvInfoList[i].depth = 0;
6001             boards[i][EP_STATUS] = EP_NONE;
6002             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6003         }
6004
6005         initialRulePlies = 0; /* 50-move counter start */
6006
6007         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6008         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6009     }
6010
6011
6012     /* [HGM] logic here is completely changed. In stead of full positions */
6013     /* the initialized data only consist of the two backranks. The switch */
6014     /* selects which one we will use, which is than copied to the Board   */
6015     /* initialPosition, which for the rest is initialized by Pawns and    */
6016     /* empty squares. This initial position is then copied to boards[0],  */
6017     /* possibly after shuffling, so that it remains available.            */
6018
6019     gameInfo.holdingsWidth = 0; /* default board sizes */
6020     gameInfo.boardWidth    = 8;
6021     gameInfo.boardHeight   = 8;
6022     gameInfo.holdingsSize  = 0;
6023     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6024     for(i=0; i<BOARD_FILES-2; i++)
6025       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6026     initialPosition[EP_STATUS] = EP_NONE;
6027     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6028     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6029          SetCharTable(pieceNickName, appData.pieceNickNames);
6030     else SetCharTable(pieceNickName, "............");
6031     pieces = FIDEArray;
6032
6033     switch (gameInfo.variant) {
6034     case VariantFischeRandom:
6035       shuffleOpenings = TRUE;
6036       appData.fischerCastling = TRUE;
6037     default:
6038       break;
6039     case VariantShatranj:
6040       pieces = ShatranjArray;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6043       break;
6044     case VariantMakruk:
6045       pieces = makrukArray;
6046       nrCastlingRights = 0;
6047       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6048       break;
6049     case VariantASEAN:
6050       pieces = aseanArray;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6053       break;
6054     case VariantTwoKings:
6055       pieces = twoKingsArray;
6056       break;
6057     case VariantGrand:
6058       pieces = GrandArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6061       gameInfo.boardWidth = 10;
6062       gameInfo.boardHeight = 10;
6063       gameInfo.holdingsSize = 7;
6064       break;
6065     case VariantCapaRandom:
6066       shuffleOpenings = TRUE;
6067       appData.fischerCastling = TRUE;
6068     case VariantCapablanca:
6069       pieces = CapablancaArray;
6070       gameInfo.boardWidth = 10;
6071       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6072       break;
6073     case VariantGothic:
6074       pieces = GothicArray;
6075       gameInfo.boardWidth = 10;
6076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6077       break;
6078     case VariantSChess:
6079       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6080       gameInfo.holdingsSize = 7;
6081       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6082       break;
6083     case VariantJanus:
6084       pieces = JanusArray;
6085       gameInfo.boardWidth = 10;
6086       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6087       nrCastlingRights = 6;
6088         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6089         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6090         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6091         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6092         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6093         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6094       break;
6095     case VariantFalcon:
6096       pieces = FalconArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6099       break;
6100     case VariantXiangqi:
6101       pieces = XiangqiArray;
6102       gameInfo.boardWidth  = 9;
6103       gameInfo.boardHeight = 10;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6106       break;
6107     case VariantShogi:
6108       pieces = ShogiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 9;
6111       gameInfo.holdingsSize = 7;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6114       break;
6115     case VariantChu:
6116       pieces = ChuArray; pieceRows = 3;
6117       gameInfo.boardWidth  = 12;
6118       gameInfo.boardHeight = 12;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6121                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6122       break;
6123     case VariantCourier:
6124       pieces = CourierArray;
6125       gameInfo.boardWidth  = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6128       break;
6129     case VariantKnightmate:
6130       pieces = KnightmateArray;
6131       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6132       break;
6133     case VariantSpartan:
6134       pieces = SpartanArray;
6135       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6136       break;
6137     case VariantLion:
6138       pieces = lionArray;
6139       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6140       break;
6141     case VariantChuChess:
6142       pieces = ChuChessArray;
6143       gameInfo.boardWidth = 10;
6144       gameInfo.boardHeight = 10;
6145       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6146       break;
6147     case VariantFairy:
6148       pieces = fairyArray;
6149       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6150       break;
6151     case VariantGreat:
6152       pieces = GreatArray;
6153       gameInfo.boardWidth = 10;
6154       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6155       gameInfo.holdingsSize = 8;
6156       break;
6157     case VariantSuper:
6158       pieces = FIDEArray;
6159       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6160       gameInfo.holdingsSize = 8;
6161       startedFromSetupPosition = TRUE;
6162       break;
6163     case VariantCrazyhouse:
6164     case VariantBughouse:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6167       gameInfo.holdingsSize = 5;
6168       break;
6169     case VariantWildCastle:
6170       pieces = FIDEArray;
6171       /* !!?shuffle with kings guaranteed to be on d or e file */
6172       shuffleOpenings = 1;
6173       break;
6174     case VariantNoCastle:
6175       pieces = FIDEArray;
6176       nrCastlingRights = 0;
6177       /* !!?unconstrained back-rank shuffle */
6178       shuffleOpenings = 1;
6179       break;
6180     }
6181
6182     overrule = 0;
6183     if(appData.NrFiles >= 0) {
6184         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6185         gameInfo.boardWidth = appData.NrFiles;
6186     }
6187     if(appData.NrRanks >= 0) {
6188         gameInfo.boardHeight = appData.NrRanks;
6189     }
6190     if(appData.holdingsSize >= 0) {
6191         i = appData.holdingsSize;
6192         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6193         gameInfo.holdingsSize = i;
6194     }
6195     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6196     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6197         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6198
6199     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6200     if(pawnRow < 1) pawnRow = 1;
6201     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6202        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6203     if(gameInfo.variant == VariantChu) pawnRow = 3;
6204
6205     /* User pieceToChar list overrules defaults */
6206     if(appData.pieceToCharTable != NULL)
6207         SetCharTable(pieceToChar, appData.pieceToCharTable);
6208
6209     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6210
6211         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6212             s = (ChessSquare) 0; /* account holding counts in guard band */
6213         for( i=0; i<BOARD_HEIGHT; i++ )
6214             initialPosition[i][j] = s;
6215
6216         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6217         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6218         initialPosition[pawnRow][j] = WhitePawn;
6219         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6220         if(gameInfo.variant == VariantXiangqi) {
6221             if(j&1) {
6222                 initialPosition[pawnRow][j] =
6223                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6224                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6225                    initialPosition[2][j] = WhiteCannon;
6226                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6227                 }
6228             }
6229         }
6230         if(gameInfo.variant == VariantChu) {
6231              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6232                initialPosition[pawnRow+1][j] = WhiteCobra,
6233                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6234              for(i=1; i<pieceRows; i++) {
6235                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6236                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6237              }
6238         }
6239         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6240             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6241                initialPosition[0][j] = WhiteRook;
6242                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6243             }
6244         }
6245         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6246     }
6247     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6248     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6249
6250             j=BOARD_LEFT+1;
6251             initialPosition[1][j] = WhiteBishop;
6252             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6253             j=BOARD_RGHT-2;
6254             initialPosition[1][j] = WhiteRook;
6255             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6256     }
6257
6258     if( nrCastlingRights == -1) {
6259         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6260         /*       This sets default castling rights from none to normal corners   */
6261         /* Variants with other castling rights must set them themselves above    */
6262         nrCastlingRights = 6;
6263
6264         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6265         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6266         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6267         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6268         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6269         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6270      }
6271
6272      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6273      if(gameInfo.variant == VariantGreat) { // promotion commoners
6274         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6276         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6278      }
6279      if( gameInfo.variant == VariantSChess ) {
6280       initialPosition[1][0] = BlackMarshall;
6281       initialPosition[2][0] = BlackAngel;
6282       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6283       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6284       initialPosition[1][1] = initialPosition[2][1] =
6285       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6286      }
6287   if (appData.debugMode) {
6288     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6289   }
6290     if(shuffleOpenings) {
6291         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6292         startedFromSetupPosition = TRUE;
6293     }
6294     if(startedFromPositionFile) {
6295       /* [HGM] loadPos: use PositionFile for every new game */
6296       CopyBoard(initialPosition, filePosition);
6297       for(i=0; i<nrCastlingRights; i++)
6298           initialRights[i] = filePosition[CASTLING][i];
6299       startedFromSetupPosition = TRUE;
6300     }
6301
6302     CopyBoard(boards[0], initialPosition);
6303
6304     if(oldx != gameInfo.boardWidth ||
6305        oldy != gameInfo.boardHeight ||
6306        oldv != gameInfo.variant ||
6307        oldh != gameInfo.holdingsWidth
6308                                          )
6309             InitDrawingSizes(-2 ,0);
6310
6311     oldv = gameInfo.variant;
6312     if (redraw)
6313       DrawPosition(TRUE, boards[currentMove]);
6314 }
6315
6316 void
6317 SendBoard (ChessProgramState *cps, int moveNum)
6318 {
6319     char message[MSG_SIZ];
6320
6321     if (cps->useSetboard) {
6322       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6323       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6324       SendToProgram(message, cps);
6325       free(fen);
6326
6327     } else {
6328       ChessSquare *bp;
6329       int i, j, left=0, right=BOARD_WIDTH;
6330       /* Kludge to set black to move, avoiding the troublesome and now
6331        * deprecated "black" command.
6332        */
6333       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6334         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6335
6336       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6337
6338       SendToProgram("edit\n", cps);
6339       SendToProgram("#\n", cps);
6340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6341         bp = &boards[moveNum][i][left];
6342         for (j = left; j < right; j++, bp++) {
6343           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6344           if ((int) *bp < (int) BlackPawn) {
6345             if(j == BOARD_RGHT+1)
6346                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6347             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6348             if(message[0] == '+' || message[0] == '~') {
6349               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6350                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6351                         AAA + j, ONE + i);
6352             }
6353             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6354                 message[1] = BOARD_RGHT   - 1 - j + '1';
6355                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6356             }
6357             SendToProgram(message, cps);
6358           }
6359         }
6360       }
6361
6362       SendToProgram("c\n", cps);
6363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6364         bp = &boards[moveNum][i][left];
6365         for (j = left; j < right; j++, bp++) {
6366           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6367           if (((int) *bp != (int) EmptySquare)
6368               && ((int) *bp >= (int) BlackPawn)) {
6369             if(j == BOARD_LEFT-2)
6370                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6371             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6372                     AAA + j, ONE + i);
6373             if(message[0] == '+' || message[0] == '~') {
6374               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6375                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6376                         AAA + j, ONE + i);
6377             }
6378             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6379                 message[1] = BOARD_RGHT   - 1 - j + '1';
6380                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6381             }
6382             SendToProgram(message, cps);
6383           }
6384         }
6385       }
6386
6387       SendToProgram(".\n", cps);
6388     }
6389     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6390 }
6391
6392 char exclusionHeader[MSG_SIZ];
6393 int exCnt, excludePtr;
6394 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6395 static Exclusion excluTab[200];
6396 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6397
6398 static void
6399 WriteMap (int s)
6400 {
6401     int j;
6402     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6403     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6404 }
6405
6406 static void
6407 ClearMap ()
6408 {
6409     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6410     excludePtr = 24; exCnt = 0;
6411     WriteMap(0);
6412 }
6413
6414 static void
6415 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6416 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6417     char buf[2*MOVE_LEN], *p;
6418     Exclusion *e = excluTab;
6419     int i;
6420     for(i=0; i<exCnt; i++)
6421         if(e[i].ff == fromX && e[i].fr == fromY &&
6422            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6423     if(i == exCnt) { // was not in exclude list; add it
6424         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6425         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6426             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6427             return; // abort
6428         }
6429         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6430         excludePtr++; e[i].mark = excludePtr++;
6431         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6432         exCnt++;
6433     }
6434     exclusionHeader[e[i].mark] = state;
6435 }
6436
6437 static int
6438 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6439 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6440     char buf[MSG_SIZ];
6441     int j, k;
6442     ChessMove moveType;
6443     if((signed char)promoChar == -1) { // kludge to indicate best move
6444         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6445             return 1; // if unparsable, abort
6446     }
6447     // update exclusion map (resolving toggle by consulting existing state)
6448     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6449     j = k%8; k >>= 3;
6450     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6451     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6452          excludeMap[k] |=   1<<j;
6453     else excludeMap[k] &= ~(1<<j);
6454     // update header
6455     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6456     // inform engine
6457     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6459     SendToBoth(buf);
6460     return (state == '+');
6461 }
6462
6463 static void
6464 ExcludeClick (int index)
6465 {
6466     int i, j;
6467     Exclusion *e = excluTab;
6468     if(index < 25) { // none, best or tail clicked
6469         if(index < 13) { // none: include all
6470             WriteMap(0); // clear map
6471             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6472             SendToBoth("include all\n"); // and inform engine
6473         } else if(index > 18) { // tail
6474             if(exclusionHeader[19] == '-') { // tail was excluded
6475                 SendToBoth("include all\n");
6476                 WriteMap(0); // clear map completely
6477                 // now re-exclude selected moves
6478                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6479                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6480             } else { // tail was included or in mixed state
6481                 SendToBoth("exclude all\n");
6482                 WriteMap(0xFF); // fill map completely
6483                 // now re-include selected moves
6484                 j = 0; // count them
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6487                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6488             }
6489         } else { // best
6490             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6491         }
6492     } else {
6493         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6494             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6495             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6496             break;
6497         }
6498     }
6499 }
6500
6501 ChessSquare
6502 DefaultPromoChoice (int white)
6503 {
6504     ChessSquare result;
6505     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6506        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6507         result = WhiteFerz; // no choice
6508     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6509         result= WhiteKing; // in Suicide Q is the last thing we want
6510     else if(gameInfo.variant == VariantSpartan)
6511         result = white ? WhiteQueen : WhiteAngel;
6512     else result = WhiteQueen;
6513     if(!white) result = WHITE_TO_BLACK result;
6514     return result;
6515 }
6516
6517 static int autoQueen; // [HGM] oneclick
6518
6519 int
6520 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6521 {
6522     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6523     /* [HGM] add Shogi promotions */
6524     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6525     ChessSquare piece, partner;
6526     ChessMove moveType;
6527     Boolean premove;
6528
6529     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6530     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6531
6532     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6533       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6534         return FALSE;
6535
6536     piece = boards[currentMove][fromY][fromX];
6537     if(gameInfo.variant == VariantChu) {
6538         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6539         promotionZoneSize = BOARD_HEIGHT/3;
6540         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6541     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6542         promotionZoneSize = BOARD_HEIGHT/3;
6543         highestPromotingPiece = (int)WhiteAlfil;
6544     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6545         promotionZoneSize = 3;
6546     }
6547
6548     // Treat Lance as Pawn when it is not representing Amazon or Lance
6549     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6550         if(piece == WhiteLance) piece = WhitePawn; else
6551         if(piece == BlackLance) piece = BlackPawn;
6552     }
6553
6554     // next weed out all moves that do not touch the promotion zone at all
6555     if((int)piece >= BlackPawn) {
6556         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6557              return FALSE;
6558         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6559         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6560     } else {
6561         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6562            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6563         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6564              return FALSE;
6565     }
6566
6567     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6568
6569     // weed out mandatory Shogi promotions
6570     if(gameInfo.variant == VariantShogi) {
6571         if(piece >= BlackPawn) {
6572             if(toY == 0 && piece == BlackPawn ||
6573                toY == 0 && piece == BlackQueen ||
6574                toY <= 1 && piece == BlackKnight) {
6575                 *promoChoice = '+';
6576                 return FALSE;
6577             }
6578         } else {
6579             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6580                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6581                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         }
6586     }
6587
6588     // weed out obviously illegal Pawn moves
6589     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6590         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6591         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6592         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6593         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6594         // note we are not allowed to test for valid (non-)capture, due to premove
6595     }
6596
6597     // we either have a choice what to promote to, or (in Shogi) whether to promote
6598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6599        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6600         ChessSquare p=BlackFerz;  // no choice
6601         while(p < EmptySquare) {  //but make sure we use piece that exists
6602             *promoChoice = PieceToChar(p++);
6603             if(*promoChoice != '.') break;
6604         }
6605         return FALSE;
6606     }
6607     // no sense asking what we must promote to if it is going to explode...
6608     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6609         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6610         return FALSE;
6611     }
6612     // give caller the default choice even if we will not make it
6613     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6614     partner = piece; // pieces can promote if the pieceToCharTable says so
6615     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6616     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6617     if(        sweepSelect && gameInfo.variant != VariantGreat
6618                            && gameInfo.variant != VariantGrand
6619                            && gameInfo.variant != VariantSuper) return FALSE;
6620     if(autoQueen) return FALSE; // predetermined
6621
6622     // suppress promotion popup on illegal moves that are not premoves
6623     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6624               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6625     if(appData.testLegality && !premove) {
6626         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6627                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6628         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6629         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6630             return FALSE;
6631     }
6632
6633     return TRUE;
6634 }
6635
6636 int
6637 InPalace (int row, int column)
6638 {   /* [HGM] for Xiangqi */
6639     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6640          column < (BOARD_WIDTH + 4)/2 &&
6641          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6642     return FALSE;
6643 }
6644
6645 int
6646 PieceForSquare (int x, int y)
6647 {
6648   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6649      return -1;
6650   else
6651      return boards[currentMove][y][x];
6652 }
6653
6654 int
6655 OKToStartUserMove (int x, int y)
6656 {
6657     ChessSquare from_piece;
6658     int white_piece;
6659
6660     if (matchMode) return FALSE;
6661     if (gameMode == EditPosition) return TRUE;
6662
6663     if (x >= 0 && y >= 0)
6664       from_piece = boards[currentMove][y][x];
6665     else
6666       from_piece = EmptySquare;
6667
6668     if (from_piece == EmptySquare) return FALSE;
6669
6670     white_piece = (int)from_piece >= (int)WhitePawn &&
6671       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6672
6673     switch (gameMode) {
6674       case AnalyzeFile:
6675       case TwoMachinesPlay:
6676       case EndOfGame:
6677         return FALSE;
6678
6679       case IcsObserving:
6680       case IcsIdle:
6681         return FALSE;
6682
6683       case MachinePlaysWhite:
6684       case IcsPlayingBlack:
6685         if (appData.zippyPlay) return FALSE;
6686         if (white_piece) {
6687             DisplayMoveError(_("You are playing Black"));
6688             return FALSE;
6689         }
6690         break;
6691
6692       case MachinePlaysBlack:
6693       case IcsPlayingWhite:
6694         if (appData.zippyPlay) return FALSE;
6695         if (!white_piece) {
6696             DisplayMoveError(_("You are playing White"));
6697             return FALSE;
6698         }
6699         break;
6700
6701       case PlayFromGameFile:
6702             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6703       case EditGame:
6704         if (!white_piece && WhiteOnMove(currentMove)) {
6705             DisplayMoveError(_("It is White's turn"));
6706             return FALSE;
6707         }
6708         if (white_piece && !WhiteOnMove(currentMove)) {
6709             DisplayMoveError(_("It is Black's turn"));
6710             return FALSE;
6711         }
6712         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6713             /* Editing correspondence game history */
6714             /* Could disallow this or prompt for confirmation */
6715             cmailOldMove = -1;
6716         }
6717         break;
6718
6719       case BeginningOfGame:
6720         if (appData.icsActive) return FALSE;
6721         if (!appData.noChessProgram) {
6722             if (!white_piece) {
6723                 DisplayMoveError(_("You are playing White"));
6724                 return FALSE;
6725             }
6726         }
6727         break;
6728
6729       case Training:
6730         if (!white_piece && WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is White's turn"));
6732             return FALSE;
6733         }
6734         if (white_piece && !WhiteOnMove(currentMove)) {
6735             DisplayMoveError(_("It is Black's turn"));
6736             return FALSE;
6737         }
6738         break;
6739
6740       default:
6741       case IcsExamining:
6742         break;
6743     }
6744     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6745         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6746         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6747         && gameMode != AnalyzeFile && gameMode != Training) {
6748         DisplayMoveError(_("Displayed position is not current"));
6749         return FALSE;
6750     }
6751     return TRUE;
6752 }
6753
6754 Boolean
6755 OnlyMove (int *x, int *y, Boolean captures)
6756 {
6757     DisambiguateClosure cl;
6758     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6759     switch(gameMode) {
6760       case MachinePlaysBlack:
6761       case IcsPlayingWhite:
6762       case BeginningOfGame:
6763         if(!WhiteOnMove(currentMove)) return FALSE;
6764         break;
6765       case MachinePlaysWhite:
6766       case IcsPlayingBlack:
6767         if(WhiteOnMove(currentMove)) return FALSE;
6768         break;
6769       case EditGame:
6770         break;
6771       default:
6772         return FALSE;
6773     }
6774     cl.pieceIn = EmptySquare;
6775     cl.rfIn = *y;
6776     cl.ffIn = *x;
6777     cl.rtIn = -1;
6778     cl.ftIn = -1;
6779     cl.promoCharIn = NULLCHAR;
6780     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6781     if( cl.kind == NormalMove ||
6782         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6783         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6784         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6785       fromX = cl.ff;
6786       fromY = cl.rf;
6787       *x = cl.ft;
6788       *y = cl.rt;
6789       return TRUE;
6790     }
6791     if(cl.kind != ImpossibleMove) return FALSE;
6792     cl.pieceIn = EmptySquare;
6793     cl.rfIn = -1;
6794     cl.ffIn = -1;
6795     cl.rtIn = *y;
6796     cl.ftIn = *x;
6797     cl.promoCharIn = NULLCHAR;
6798     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6799     if( cl.kind == NormalMove ||
6800         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6801         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6802         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6803       fromX = cl.ff;
6804       fromY = cl.rf;
6805       *x = cl.ft;
6806       *y = cl.rt;
6807       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6808       return TRUE;
6809     }
6810     return FALSE;
6811 }
6812
6813 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6814 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6815 int lastLoadGameUseList = FALSE;
6816 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6817 ChessMove lastLoadGameStart = EndOfFile;
6818 int doubleClick;
6819
6820 void
6821 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6822 {
6823     ChessMove moveType;
6824     ChessSquare pup;
6825     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6826
6827     /* Check if the user is playing in turn.  This is complicated because we
6828        let the user "pick up" a piece before it is his turn.  So the piece he
6829        tried to pick up may have been captured by the time he puts it down!
6830        Therefore we use the color the user is supposed to be playing in this
6831        test, not the color of the piece that is currently on the starting
6832        square---except in EditGame mode, where the user is playing both
6833        sides; fortunately there the capture race can't happen.  (It can
6834        now happen in IcsExamining mode, but that's just too bad.  The user
6835        will get a somewhat confusing message in that case.)
6836        */
6837
6838     switch (gameMode) {
6839       case AnalyzeFile:
6840       case TwoMachinesPlay:
6841       case EndOfGame:
6842       case IcsObserving:
6843       case IcsIdle:
6844         /* We switched into a game mode where moves are not accepted,
6845            perhaps while the mouse button was down. */
6846         return;
6847
6848       case MachinePlaysWhite:
6849         /* User is moving for Black */
6850         if (WhiteOnMove(currentMove)) {
6851             DisplayMoveError(_("It is White's turn"));
6852             return;
6853         }
6854         break;
6855
6856       case MachinePlaysBlack:
6857         /* User is moving for White */
6858         if (!WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is Black's turn"));
6860             return;
6861         }
6862         break;
6863
6864       case PlayFromGameFile:
6865             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6866       case EditGame:
6867       case IcsExamining:
6868       case BeginningOfGame:
6869       case AnalyzeMode:
6870       case Training:
6871         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6872         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6873             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6874             /* User is moving for Black */
6875             if (WhiteOnMove(currentMove)) {
6876                 DisplayMoveError(_("It is White's turn"));
6877                 return;
6878             }
6879         } else {
6880             /* User is moving for White */
6881             if (!WhiteOnMove(currentMove)) {
6882                 DisplayMoveError(_("It is Black's turn"));
6883                 return;
6884             }
6885         }
6886         break;
6887
6888       case IcsPlayingBlack:
6889         /* User is moving for Black */
6890         if (WhiteOnMove(currentMove)) {
6891             if (!appData.premove) {
6892                 DisplayMoveError(_("It is White's turn"));
6893             } else if (toX >= 0 && toY >= 0) {
6894                 premoveToX = toX;
6895                 premoveToY = toY;
6896                 premoveFromX = fromX;
6897                 premoveFromY = fromY;
6898                 premovePromoChar = promoChar;
6899                 gotPremove = 1;
6900                 if (appData.debugMode)
6901                     fprintf(debugFP, "Got premove: fromX %d,"
6902                             "fromY %d, toX %d, toY %d\n",
6903                             fromX, fromY, toX, toY);
6904             }
6905             return;
6906         }
6907         break;
6908
6909       case IcsPlayingWhite:
6910         /* User is moving for White */
6911         if (!WhiteOnMove(currentMove)) {
6912             if (!appData.premove) {
6913                 DisplayMoveError(_("It is Black's turn"));
6914             } else if (toX >= 0 && toY >= 0) {
6915                 premoveToX = toX;
6916                 premoveToY = toY;
6917                 premoveFromX = fromX;
6918                 premoveFromY = fromY;
6919                 premovePromoChar = promoChar;
6920                 gotPremove = 1;
6921                 if (appData.debugMode)
6922                     fprintf(debugFP, "Got premove: fromX %d,"
6923                             "fromY %d, toX %d, toY %d\n",
6924                             fromX, fromY, toX, toY);
6925             }
6926             return;
6927         }
6928         break;
6929
6930       default:
6931         break;
6932
6933       case EditPosition:
6934         /* EditPosition, empty square, or different color piece;
6935            click-click move is possible */
6936         if (toX == -2 || toY == -2) {
6937             boards[0][fromY][fromX] = EmptySquare;
6938             DrawPosition(FALSE, boards[currentMove]);
6939             return;
6940         } else if (toX >= 0 && toY >= 0) {
6941             boards[0][toY][toX] = boards[0][fromY][fromX];
6942             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6943                 if(boards[0][fromY][0] != EmptySquare) {
6944                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6945                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6946                 }
6947             } else
6948             if(fromX == BOARD_RGHT+1) {
6949                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6950                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6951                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6952                 }
6953             } else
6954             boards[0][fromY][fromX] = gatingPiece;
6955             DrawPosition(FALSE, boards[currentMove]);
6956             return;
6957         }
6958         return;
6959     }
6960
6961     if(toX < 0 || toY < 0) return;
6962     pup = boards[currentMove][toY][toX];
6963
6964     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6965     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6966          if( pup != EmptySquare ) return;
6967          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6968            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6969                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6970            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6971            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6972            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6973            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6974          fromY = DROP_RANK;
6975     }
6976
6977     /* [HGM] always test for legality, to get promotion info */
6978     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6979                                          fromY, fromX, toY, toX, promoChar);
6980
6981     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6982
6983     /* [HGM] but possibly ignore an IllegalMove result */
6984     if (appData.testLegality) {
6985         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6986             DisplayMoveError(_("Illegal move"));
6987             return;
6988         }
6989     }
6990
6991     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6992         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6993              ClearPremoveHighlights(); // was included
6994         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6995         return;
6996     }
6997
6998     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6999 }
7000
7001 /* Common tail of UserMoveEvent and DropMenuEvent */
7002 int
7003 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7004 {
7005     char *bookHit = 0;
7006
7007     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7008         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7009         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7010         if(WhiteOnMove(currentMove)) {
7011             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7012         } else {
7013             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7014         }
7015     }
7016
7017     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7018        move type in caller when we know the move is a legal promotion */
7019     if(moveType == NormalMove && promoChar)
7020         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7021
7022     /* [HGM] <popupFix> The following if has been moved here from
7023        UserMoveEvent(). Because it seemed to belong here (why not allow
7024        piece drops in training games?), and because it can only be
7025        performed after it is known to what we promote. */
7026     if (gameMode == Training) {
7027       /* compare the move played on the board to the next move in the
7028        * game. If they match, display the move and the opponent's response.
7029        * If they don't match, display an error message.
7030        */
7031       int saveAnimate;
7032       Board testBoard;
7033       CopyBoard(testBoard, boards[currentMove]);
7034       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7035
7036       if (CompareBoards(testBoard, boards[currentMove+1])) {
7037         ForwardInner(currentMove+1);
7038
7039         /* Autoplay the opponent's response.
7040          * if appData.animate was TRUE when Training mode was entered,
7041          * the response will be animated.
7042          */
7043         saveAnimate = appData.animate;
7044         appData.animate = animateTraining;
7045         ForwardInner(currentMove+1);
7046         appData.animate = saveAnimate;
7047
7048         /* check for the end of the game */
7049         if (currentMove >= forwardMostMove) {
7050           gameMode = PlayFromGameFile;
7051           ModeHighlight();
7052           SetTrainingModeOff();
7053           DisplayInformation(_("End of game"));
7054         }
7055       } else {
7056         DisplayError(_("Incorrect move"), 0);
7057       }
7058       return 1;
7059     }
7060
7061   /* Ok, now we know that the move is good, so we can kill
7062      the previous line in Analysis Mode */
7063   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7064                                 && currentMove < forwardMostMove) {
7065     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7066     else forwardMostMove = currentMove;
7067   }
7068
7069   ClearMap();
7070
7071   /* If we need the chess program but it's dead, restart it */
7072   ResurrectChessProgram();
7073
7074   /* A user move restarts a paused game*/
7075   if (pausing)
7076     PauseEvent();
7077
7078   thinkOutput[0] = NULLCHAR;
7079
7080   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7081
7082   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7083     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7084     return 1;
7085   }
7086
7087   if (gameMode == BeginningOfGame) {
7088     if (appData.noChessProgram) {
7089       gameMode = EditGame;
7090       SetGameInfo();
7091     } else {
7092       char buf[MSG_SIZ];
7093       gameMode = MachinePlaysBlack;
7094       StartClocks();
7095       SetGameInfo();
7096       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7097       DisplayTitle(buf);
7098       if (first.sendName) {
7099         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7100         SendToProgram(buf, &first);
7101       }
7102       StartClocks();
7103     }
7104     ModeHighlight();
7105   }
7106
7107   /* Relay move to ICS or chess engine */
7108   if (appData.icsActive) {
7109     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7110         gameMode == IcsExamining) {
7111       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7112         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7113         SendToICS("draw ");
7114         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7115       }
7116       // also send plain move, in case ICS does not understand atomic claims
7117       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7118       ics_user_moved = 1;
7119     }
7120   } else {
7121     if (first.sendTime && (gameMode == BeginningOfGame ||
7122                            gameMode == MachinePlaysWhite ||
7123                            gameMode == MachinePlaysBlack)) {
7124       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7125     }
7126     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7127          // [HGM] book: if program might be playing, let it use book
7128         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7129         first.maybeThinking = TRUE;
7130     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7131         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7132         SendBoard(&first, currentMove+1);
7133         if(second.analyzing) {
7134             if(!second.useSetboard) SendToProgram("undo\n", &second);
7135             SendBoard(&second, currentMove+1);
7136         }
7137     } else {
7138         SendMoveToProgram(forwardMostMove-1, &first);
7139         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7140     }
7141     if (currentMove == cmailOldMove + 1) {
7142       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7143     }
7144   }
7145
7146   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7147
7148   switch (gameMode) {
7149   case EditGame:
7150     if(appData.testLegality)
7151     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7152     case MT_NONE:
7153     case MT_CHECK:
7154       break;
7155     case MT_CHECKMATE:
7156     case MT_STAINMATE:
7157       if (WhiteOnMove(currentMove)) {
7158         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7159       } else {
7160         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7161       }
7162       break;
7163     case MT_STALEMATE:
7164       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7165       break;
7166     }
7167     break;
7168
7169   case MachinePlaysBlack:
7170   case MachinePlaysWhite:
7171     /* disable certain menu options while machine is thinking */
7172     SetMachineThinkingEnables();
7173     break;
7174
7175   default:
7176     break;
7177   }
7178
7179   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7180   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7181
7182   if(bookHit) { // [HGM] book: simulate book reply
7183         static char bookMove[MSG_SIZ]; // a bit generous?
7184
7185         programStats.nodes = programStats.depth = programStats.time =
7186         programStats.score = programStats.got_only_move = 0;
7187         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7188
7189         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7190         strcat(bookMove, bookHit);
7191         HandleMachineMove(bookMove, &first);
7192   }
7193   return 1;
7194 }
7195
7196 void
7197 MarkByFEN(char *fen)
7198 {
7199         int r, f;
7200         if(!appData.markers || !appData.highlightDragging) return;
7201         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7202         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7203         while(*fen) {
7204             int s = 0;
7205             marker[r][f] = 0;
7206             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7207             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7208             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7209             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7210             if(*fen == 'T') marker[r][f++] = 0; else
7211             if(*fen == 'Y') marker[r][f++] = 1; else
7212             if(*fen == 'G') marker[r][f++] = 3; else
7213             if(*fen == 'B') marker[r][f++] = 4; else
7214             if(*fen == 'C') marker[r][f++] = 5; else
7215             if(*fen == 'M') marker[r][f++] = 6; else
7216             if(*fen == 'W') marker[r][f++] = 7; else
7217             if(*fen == 'D') marker[r][f++] = 8; else
7218             if(*fen == 'R') marker[r][f++] = 2; else {
7219                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7220               f += s; fen -= s>0;
7221             }
7222             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7223             if(r < 0) break;
7224             fen++;
7225         }
7226         DrawPosition(TRUE, NULL);
7227 }
7228
7229 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7230
7231 void
7232 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7233 {
7234     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7235     Markers *m = (Markers *) closure;
7236     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7237         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7238                          || kind == WhiteCapturesEnPassant
7239                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7240     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7241 }
7242
7243 static int hoverSavedValid;
7244
7245 void
7246 MarkTargetSquares (int clear)
7247 {
7248   int x, y, sum=0;
7249   if(clear) { // no reason to ever suppress clearing
7250     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7251     hoverSavedValid = 0;
7252     if(!sum) return; // nothing was cleared,no redraw needed
7253   } else {
7254     int capt = 0;
7255     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7256        !appData.testLegality || gameMode == EditPosition) return;
7257     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7258     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7259       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7260       if(capt)
7261       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7262     }
7263   }
7264   DrawPosition(FALSE, NULL);
7265 }
7266
7267 int
7268 Explode (Board board, int fromX, int fromY, int toX, int toY)
7269 {
7270     if(gameInfo.variant == VariantAtomic &&
7271        (board[toY][toX] != EmptySquare ||                     // capture?
7272         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7273                          board[fromY][fromX] == BlackPawn   )
7274       )) {
7275         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7276         return TRUE;
7277     }
7278     return FALSE;
7279 }
7280
7281 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7282
7283 int
7284 CanPromote (ChessSquare piece, int y)
7285 {
7286         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7287         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7288         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7289         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7290            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7291            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7292          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7293         return (piece == BlackPawn && y <= zone ||
7294                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7295                 piece == BlackLance && y == 1 ||
7296                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7297 }
7298
7299 void
7300 HoverEvent (int xPix, int yPix, int x, int y)
7301 {
7302         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7303         int r, f;
7304         if(!first.highlight) return;
7305         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7306         if(x == oldX && y == oldY) return; // only do something if we enter new square
7307         oldFromX = fromX; oldFromY = fromY;
7308         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7309           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7310             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7311           hoverSavedValid = 1;
7312         } else if(oldX != x || oldY != y) {
7313           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7314           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7315           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7316             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7317           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7318             char buf[MSG_SIZ];
7319             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7320             SendToProgram(buf, &first);
7321           }
7322           oldX = x; oldY = y;
7323 //        SetHighlights(fromX, fromY, x, y);
7324         }
7325 }
7326
7327 void ReportClick(char *action, int x, int y)
7328 {
7329         char buf[MSG_SIZ]; // Inform engine of what user does
7330         int r, f;
7331         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7332           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7333         if(!first.highlight || gameMode == EditPosition) return;
7334         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7335         SendToProgram(buf, &first);
7336 }
7337
7338 void
7339 LeftClick (ClickType clickType, int xPix, int yPix)
7340 {
7341     int x, y;
7342     Boolean saveAnimate;
7343     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7344     char promoChoice = NULLCHAR;
7345     ChessSquare piece;
7346     static TimeMark lastClickTime, prevClickTime;
7347
7348     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7349
7350     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7351
7352     if (clickType == Press) ErrorPopDown();
7353     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7354
7355     x = EventToSquare(xPix, BOARD_WIDTH);
7356     y = EventToSquare(yPix, BOARD_HEIGHT);
7357     if (!flipView && y >= 0) {
7358         y = BOARD_HEIGHT - 1 - y;
7359     }
7360     if (flipView && x >= 0) {
7361         x = BOARD_WIDTH - 1 - x;
7362     }
7363
7364     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7365         defaultPromoChoice = promoSweep;
7366         promoSweep = EmptySquare;   // terminate sweep
7367         promoDefaultAltered = TRUE;
7368         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7369     }
7370
7371     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7372         if(clickType == Release) return; // ignore upclick of click-click destination
7373         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7374         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7375         if(gameInfo.holdingsWidth &&
7376                 (WhiteOnMove(currentMove)
7377                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7378                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7379             // click in right holdings, for determining promotion piece
7380             ChessSquare p = boards[currentMove][y][x];
7381             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7382             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7383             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7384                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7385                 fromX = fromY = -1;
7386                 return;
7387             }
7388         }
7389         DrawPosition(FALSE, boards[currentMove]);
7390         return;
7391     }
7392
7393     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7394     if(clickType == Press
7395             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7396               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7397               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7398         return;
7399
7400     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7401         // could be static click on premove from-square: abort premove
7402         gotPremove = 0;
7403         ClearPremoveHighlights();
7404     }
7405
7406     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7407         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7408
7409     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7410         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7411                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7412         defaultPromoChoice = DefaultPromoChoice(side);
7413     }
7414
7415     autoQueen = appData.alwaysPromoteToQueen;
7416
7417     if (fromX == -1) {
7418       int originalY = y;
7419       gatingPiece = EmptySquare;
7420       if (clickType != Press) {
7421         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7422             DragPieceEnd(xPix, yPix); dragging = 0;
7423             DrawPosition(FALSE, NULL);
7424         }
7425         return;
7426       }
7427       doubleClick = FALSE;
7428       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7429         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7430       }
7431       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7432       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7433          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7434          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7435             /* First square */
7436             if (OKToStartUserMove(fromX, fromY)) {
7437                 second = 0;
7438                 ReportClick("lift", x, y);
7439                 MarkTargetSquares(0);
7440                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7441                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7442                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7443                     promoSweep = defaultPromoChoice;
7444                     selectFlag = 0; lastX = xPix; lastY = yPix;
7445                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7446                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7447                 }
7448                 if (appData.highlightDragging) {
7449                     SetHighlights(fromX, fromY, -1, -1);
7450                 } else {
7451                     ClearHighlights();
7452                 }
7453             } else fromX = fromY = -1;
7454             return;
7455         }
7456     }
7457
7458     /* fromX != -1 */
7459     if (clickType == Press && gameMode != EditPosition) {
7460         ChessSquare fromP;
7461         ChessSquare toP;
7462         int frc;
7463
7464         // ignore off-board to clicks
7465         if(y < 0 || x < 0) return;
7466
7467         /* Check if clicking again on the same color piece */
7468         fromP = boards[currentMove][fromY][fromX];
7469         toP = boards[currentMove][y][x];
7470         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7471         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7472            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7473              WhitePawn <= toP && toP <= WhiteKing &&
7474              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7475              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7476             (BlackPawn <= fromP && fromP <= BlackKing &&
7477              BlackPawn <= toP && toP <= BlackKing &&
7478              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7479              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7480             /* Clicked again on same color piece -- changed his mind */
7481             second = (x == fromX && y == fromY);
7482             killX = killY = -1;
7483             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7484                 second = FALSE; // first double-click rather than scond click
7485                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7486             }
7487             promoDefaultAltered = FALSE;
7488             MarkTargetSquares(1);
7489            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7490             if (appData.highlightDragging) {
7491                 SetHighlights(x, y, -1, -1);
7492             } else {
7493                 ClearHighlights();
7494             }
7495             if (OKToStartUserMove(x, y)) {
7496                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7497                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7498                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7499                  gatingPiece = boards[currentMove][fromY][fromX];
7500                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7501                 fromX = x;
7502                 fromY = y; dragging = 1;
7503                 ReportClick("lift", x, y);
7504                 MarkTargetSquares(0);
7505                 DragPieceBegin(xPix, yPix, FALSE);
7506                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7507                     promoSweep = defaultPromoChoice;
7508                     selectFlag = 0; lastX = xPix; lastY = yPix;
7509                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7510                 }
7511             }
7512            }
7513            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7514            second = FALSE;
7515         }
7516         // ignore clicks on holdings
7517         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7518     }
7519
7520     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7521         DragPieceEnd(xPix, yPix); dragging = 0;
7522         if(clearFlag) {
7523             // a deferred attempt to click-click move an empty square on top of a piece
7524             boards[currentMove][y][x] = EmptySquare;
7525             ClearHighlights();
7526             DrawPosition(FALSE, boards[currentMove]);
7527             fromX = fromY = -1; clearFlag = 0;
7528             return;
7529         }
7530         if (appData.animateDragging) {
7531             /* Undo animation damage if any */
7532             DrawPosition(FALSE, NULL);
7533         }
7534         if (second || sweepSelecting) {
7535             /* Second up/down in same square; just abort move */
7536             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7537             second = sweepSelecting = 0;
7538             fromX = fromY = -1;
7539             gatingPiece = EmptySquare;
7540             MarkTargetSquares(1);
7541             ClearHighlights();
7542             gotPremove = 0;
7543             ClearPremoveHighlights();
7544         } else {
7545             /* First upclick in same square; start click-click mode */
7546             SetHighlights(x, y, -1, -1);
7547         }
7548         return;
7549     }
7550
7551     clearFlag = 0;
7552
7553     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7554         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7555         DisplayMessage(_("only marked squares are legal"),"");
7556         DrawPosition(TRUE, NULL);
7557         return; // ignore to-click
7558     }
7559
7560     /* we now have a different from- and (possibly off-board) to-square */
7561     /* Completed move */
7562     if(!sweepSelecting) {
7563         toX = x;
7564         toY = y;
7565     }
7566
7567     piece = boards[currentMove][fromY][fromX];
7568
7569     saveAnimate = appData.animate;
7570     if (clickType == Press) {
7571         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7572         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7573             // must be Edit Position mode with empty-square selected
7574             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7575             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7576             return;
7577         }
7578         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7579             return;
7580         }
7581         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7582             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7583         } else
7584         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7585         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7586           if(appData.sweepSelect) {
7587             promoSweep = defaultPromoChoice;
7588             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7589             selectFlag = 0; lastX = xPix; lastY = yPix;
7590             Sweep(0); // Pawn that is going to promote: preview promotion piece
7591             sweepSelecting = 1;
7592             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7593             MarkTargetSquares(1);
7594           }
7595           return; // promo popup appears on up-click
7596         }
7597         /* Finish clickclick move */
7598         if (appData.animate || appData.highlightLastMove) {
7599             SetHighlights(fromX, fromY, toX, toY);
7600         } else {
7601             ClearHighlights();
7602         }
7603     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7604         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7605         if (appData.animate || appData.highlightLastMove) {
7606             SetHighlights(fromX, fromY, toX, toY);
7607         } else {
7608             ClearHighlights();
7609         }
7610     } else {
7611 #if 0
7612 // [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
7613         /* Finish drag move */
7614         if (appData.highlightLastMove) {
7615             SetHighlights(fromX, fromY, toX, toY);
7616         } else {
7617             ClearHighlights();
7618         }
7619 #endif
7620         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7621         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7622           dragging *= 2;            // flag button-less dragging if we are dragging
7623           MarkTargetSquares(1);
7624           if(x == killX && y == killY) killX = killY = -1; else {
7625             killX = x; killY = y;     //remeber this square as intermediate
7626             ReportClick("put", x, y); // and inform engine
7627             ReportClick("lift", x, y);
7628             MarkTargetSquares(0);
7629             return;
7630           }
7631         }
7632         DragPieceEnd(xPix, yPix); dragging = 0;
7633         /* Don't animate move and drag both */
7634         appData.animate = FALSE;
7635     }
7636
7637     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7638     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7639         ChessSquare piece = boards[currentMove][fromY][fromX];
7640         if(gameMode == EditPosition && piece != EmptySquare &&
7641            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7642             int n;
7643
7644             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7645                 n = PieceToNumber(piece - (int)BlackPawn);
7646                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7647                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7648                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7649             } else
7650             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7651                 n = PieceToNumber(piece);
7652                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7653                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7654                 boards[currentMove][n][BOARD_WIDTH-2]++;
7655             }
7656             boards[currentMove][fromY][fromX] = EmptySquare;
7657         }
7658         ClearHighlights();
7659         fromX = fromY = -1;
7660         MarkTargetSquares(1);
7661         DrawPosition(TRUE, boards[currentMove]);
7662         return;
7663     }
7664
7665     // off-board moves should not be highlighted
7666     if(x < 0 || y < 0) ClearHighlights();
7667     else ReportClick("put", x, y);
7668
7669     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7670
7671     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7672         SetHighlights(fromX, fromY, toX, toY);
7673         MarkTargetSquares(1);
7674         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7675             // [HGM] super: promotion to captured piece selected from holdings
7676             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7677             promotionChoice = TRUE;
7678             // kludge follows to temporarily execute move on display, without promoting yet
7679             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7680             boards[currentMove][toY][toX] = p;
7681             DrawPosition(FALSE, boards[currentMove]);
7682             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7683             boards[currentMove][toY][toX] = q;
7684             DisplayMessage("Click in holdings to choose piece", "");
7685             return;
7686         }
7687         PromotionPopUp(promoChoice);
7688     } else {
7689         int oldMove = currentMove;
7690         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7691         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7692         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7693         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7694            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7695             DrawPosition(TRUE, boards[currentMove]);
7696         MarkTargetSquares(1);
7697         fromX = fromY = -1;
7698     }
7699     appData.animate = saveAnimate;
7700     if (appData.animate || appData.animateDragging) {
7701         /* Undo animation damage if needed */
7702         DrawPosition(FALSE, NULL);
7703     }
7704 }
7705
7706 int
7707 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7708 {   // front-end-free part taken out of PieceMenuPopup
7709     int whichMenu; int xSqr, ySqr;
7710
7711     if(seekGraphUp) { // [HGM] seekgraph
7712         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7713         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7714         return -2;
7715     }
7716
7717     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7718          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7719         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7720         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7721         if(action == Press)   {
7722             originalFlip = flipView;
7723             flipView = !flipView; // temporarily flip board to see game from partners perspective
7724             DrawPosition(TRUE, partnerBoard);
7725             DisplayMessage(partnerStatus, "");
7726             partnerUp = TRUE;
7727         } else if(action == Release) {
7728             flipView = originalFlip;
7729             DrawPosition(TRUE, boards[currentMove]);
7730             partnerUp = FALSE;
7731         }
7732         return -2;
7733     }
7734
7735     xSqr = EventToSquare(x, BOARD_WIDTH);
7736     ySqr = EventToSquare(y, BOARD_HEIGHT);
7737     if (action == Release) {
7738         if(pieceSweep != EmptySquare) {
7739             EditPositionMenuEvent(pieceSweep, toX, toY);
7740             pieceSweep = EmptySquare;
7741         } else UnLoadPV(); // [HGM] pv
7742     }
7743     if (action != Press) return -2; // return code to be ignored
7744     switch (gameMode) {
7745       case IcsExamining:
7746         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7747       case EditPosition:
7748         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7749         if (xSqr < 0 || ySqr < 0) return -1;
7750         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7751         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7752         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7753         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7754         NextPiece(0);
7755         return 2; // grab
7756       case IcsObserving:
7757         if(!appData.icsEngineAnalyze) return -1;
7758       case IcsPlayingWhite:
7759       case IcsPlayingBlack:
7760         if(!appData.zippyPlay) goto noZip;
7761       case AnalyzeMode:
7762       case AnalyzeFile:
7763       case MachinePlaysWhite:
7764       case MachinePlaysBlack:
7765       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7766         if (!appData.dropMenu) {
7767           LoadPV(x, y);
7768           return 2; // flag front-end to grab mouse events
7769         }
7770         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7771            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7772       case EditGame:
7773       noZip:
7774         if (xSqr < 0 || ySqr < 0) return -1;
7775         if (!appData.dropMenu || appData.testLegality &&
7776             gameInfo.variant != VariantBughouse &&
7777             gameInfo.variant != VariantCrazyhouse) return -1;
7778         whichMenu = 1; // drop menu
7779         break;
7780       default:
7781         return -1;
7782     }
7783
7784     if (((*fromX = xSqr) < 0) ||
7785         ((*fromY = ySqr) < 0)) {
7786         *fromX = *fromY = -1;
7787         return -1;
7788     }
7789     if (flipView)
7790       *fromX = BOARD_WIDTH - 1 - *fromX;
7791     else
7792       *fromY = BOARD_HEIGHT - 1 - *fromY;
7793
7794     return whichMenu;
7795 }
7796
7797 void
7798 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7799 {
7800 //    char * hint = lastHint;
7801     FrontEndProgramStats stats;
7802
7803     stats.which = cps == &first ? 0 : 1;
7804     stats.depth = cpstats->depth;
7805     stats.nodes = cpstats->nodes;
7806     stats.score = cpstats->score;
7807     stats.time = cpstats->time;
7808     stats.pv = cpstats->movelist;
7809     stats.hint = lastHint;
7810     stats.an_move_index = 0;
7811     stats.an_move_count = 0;
7812
7813     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7814         stats.hint = cpstats->move_name;
7815         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7816         stats.an_move_count = cpstats->nr_moves;
7817     }
7818
7819     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
7820
7821     SetProgramStats( &stats );
7822 }
7823
7824 void
7825 ClearEngineOutputPane (int which)
7826 {
7827     static FrontEndProgramStats dummyStats;
7828     dummyStats.which = which;
7829     dummyStats.pv = "#";
7830     SetProgramStats( &dummyStats );
7831 }
7832
7833 #define MAXPLAYERS 500
7834
7835 char *
7836 TourneyStandings (int display)
7837 {
7838     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7839     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7840     char result, *p, *names[MAXPLAYERS];
7841
7842     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7843         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7844     names[0] = p = strdup(appData.participants);
7845     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7846
7847     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7848
7849     while(result = appData.results[nr]) {
7850         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7851         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7852         wScore = bScore = 0;
7853         switch(result) {
7854           case '+': wScore = 2; break;
7855           case '-': bScore = 2; break;
7856           case '=': wScore = bScore = 1; break;
7857           case ' ':
7858           case '*': return strdup("busy"); // tourney not finished
7859         }
7860         score[w] += wScore;
7861         score[b] += bScore;
7862         games[w]++;
7863         games[b]++;
7864         nr++;
7865     }
7866     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7867     for(w=0; w<nPlayers; w++) {
7868         bScore = -1;
7869         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7870         ranking[w] = b; points[w] = bScore; score[b] = -2;
7871     }
7872     p = malloc(nPlayers*34+1);
7873     for(w=0; w<nPlayers && w<display; w++)
7874         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7875     free(names[0]);
7876     return p;
7877 }
7878
7879 void
7880 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7881 {       // count all piece types
7882         int p, f, r;
7883         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7884         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7885         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7886                 p = board[r][f];
7887                 pCnt[p]++;
7888                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7889                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7890                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7891                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7892                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7893                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7894         }
7895 }
7896
7897 int
7898 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7899 {
7900         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7901         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7902
7903         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7904         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7905         if(myPawns == 2 && nMine == 3) // KPP
7906             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7907         if(myPawns == 1 && nMine == 2) // KP
7908             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7909         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7910             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7911         if(myPawns) return FALSE;
7912         if(pCnt[WhiteRook+side])
7913             return pCnt[BlackRook-side] ||
7914                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7915                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7916                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7917         if(pCnt[WhiteCannon+side]) {
7918             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7919             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7920         }
7921         if(pCnt[WhiteKnight+side])
7922             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7923         return FALSE;
7924 }
7925
7926 int
7927 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7928 {
7929         VariantClass v = gameInfo.variant;
7930
7931         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7932         if(v == VariantShatranj) return TRUE; // always winnable through baring
7933         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7934         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7935
7936         if(v == VariantXiangqi) {
7937                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7938
7939                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7940                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7941                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7942                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7943                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7944                 if(stale) // we have at least one last-rank P plus perhaps C
7945                     return majors // KPKX
7946                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7947                 else // KCA*E*
7948                     return pCnt[WhiteFerz+side] // KCAK
7949                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7950                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7951                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7952
7953         } else if(v == VariantKnightmate) {
7954                 if(nMine == 1) return FALSE;
7955                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7956         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7957                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7958
7959                 if(nMine == 1) return FALSE; // bare King
7960                 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
7961                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7962                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7963                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7964                 if(pCnt[WhiteKnight+side])
7965                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7966                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7967                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7968                 if(nBishops)
7969                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7970                 if(pCnt[WhiteAlfil+side])
7971                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7972                 if(pCnt[WhiteWazir+side])
7973                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7974         }
7975
7976         return TRUE;
7977 }
7978
7979 int
7980 CompareWithRights (Board b1, Board b2)
7981 {
7982     int rights = 0;
7983     if(!CompareBoards(b1, b2)) return FALSE;
7984     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7985     /* compare castling rights */
7986     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7987            rights++; /* King lost rights, while rook still had them */
7988     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7989         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7990            rights++; /* but at least one rook lost them */
7991     }
7992     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7993            rights++;
7994     if( b1[CASTLING][5] != NoRights ) {
7995         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7996            rights++;
7997     }
7998     return rights == 0;
7999 }
8000
8001 int
8002 Adjudicate (ChessProgramState *cps)
8003 {       // [HGM] some adjudications useful with buggy engines
8004         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8005         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8006         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8007         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8008         int k, drop, count = 0; static int bare = 1;
8009         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8010         Boolean canAdjudicate = !appData.icsActive;
8011
8012         // most tests only when we understand the game, i.e. legality-checking on
8013             if( appData.testLegality )
8014             {   /* [HGM] Some more adjudications for obstinate engines */
8015                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8016                 static int moveCount = 6;
8017                 ChessMove result;
8018                 char *reason = NULL;
8019
8020                 /* Count what is on board. */
8021                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8022
8023                 /* Some material-based adjudications that have to be made before stalemate test */
8024                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8025                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8026                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8027                      if(canAdjudicate && appData.checkMates) {
8028                          if(engineOpponent)
8029                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8030                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8031                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8032                          return 1;
8033                      }
8034                 }
8035
8036                 /* Bare King in Shatranj (loses) or Losers (wins) */
8037                 if( nrW == 1 || nrB == 1) {
8038                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8039                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8040                      if(canAdjudicate && appData.checkMates) {
8041                          if(engineOpponent)
8042                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8043                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8044                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8045                          return 1;
8046                      }
8047                   } else
8048                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8049                   {    /* bare King */
8050                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8051                         if(canAdjudicate && appData.checkMates) {
8052                             /* but only adjudicate if adjudication enabled */
8053                             if(engineOpponent)
8054                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8055                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8056                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8057                             return 1;
8058                         }
8059                   }
8060                 } else bare = 1;
8061
8062
8063             // don't wait for engine to announce game end if we can judge ourselves
8064             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8065               case MT_CHECK:
8066                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8067                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8068                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8069                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8070                             checkCnt++;
8071                         if(checkCnt >= 2) {
8072                             reason = "Xboard adjudication: 3rd check";
8073                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8074                             break;
8075                         }
8076                     }
8077                 }
8078               case MT_NONE:
8079               default:
8080                 break;
8081               case MT_STEALMATE:
8082               case MT_STALEMATE:
8083               case MT_STAINMATE:
8084                 reason = "Xboard adjudication: Stalemate";
8085                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8086                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8087                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8088                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8089                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8090                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8091                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8092                                                                         EP_CHECKMATE : EP_WINS);
8093                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8094                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8095                 }
8096                 break;
8097               case MT_CHECKMATE:
8098                 reason = "Xboard adjudication: Checkmate";
8099                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8100                 if(gameInfo.variant == VariantShogi) {
8101                     if(forwardMostMove > backwardMostMove
8102                        && moveList[forwardMostMove-1][1] == '@'
8103                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8104                         reason = "XBoard adjudication: pawn-drop mate";
8105                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8106                     }
8107                 }
8108                 break;
8109             }
8110
8111                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8112                     case EP_STALEMATE:
8113                         result = GameIsDrawn; break;
8114                     case EP_CHECKMATE:
8115                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8116                     case EP_WINS:
8117                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8118                     default:
8119                         result = EndOfFile;
8120                 }
8121                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8122                     if(engineOpponent)
8123                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8124                     GameEnds( result, reason, GE_XBOARD );
8125                     return 1;
8126                 }
8127
8128                 /* Next absolutely insufficient mating material. */
8129                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8130                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8131                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8132
8133                      /* always flag draws, for judging claims */
8134                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8135
8136                      if(canAdjudicate && appData.materialDraws) {
8137                          /* but only adjudicate them if adjudication enabled */
8138                          if(engineOpponent) {
8139                            SendToProgram("force\n", engineOpponent); // suppress reply
8140                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8141                          }
8142                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8143                          return 1;
8144                      }
8145                 }
8146
8147                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8148                 if(gameInfo.variant == VariantXiangqi ?
8149                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8150                  : nrW + nrB == 4 &&
8151                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8152                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8153                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8154                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8155                    ) ) {
8156                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8157                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8158                           if(engineOpponent) {
8159                             SendToProgram("force\n", engineOpponent); // suppress reply
8160                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8161                           }
8162                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8163                           return 1;
8164                      }
8165                 } else moveCount = 6;
8166             }
8167
8168         // Repetition draws and 50-move rule can be applied independently of legality testing
8169
8170                 /* Check for rep-draws */
8171                 count = 0;
8172                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8173                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8174                 for(k = forwardMostMove-2;
8175                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8176                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8177                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8178                     k-=2)
8179                 {   int rights=0;
8180                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8181                         /* compare castling rights */
8182                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8183                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8184                                 rights++; /* King lost rights, while rook still had them */
8185                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8186                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8187                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8188                                    rights++; /* but at least one rook lost them */
8189                         }
8190                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8191                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8192                                 rights++;
8193                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8194                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8195                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8196                                    rights++;
8197                         }
8198                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8199                             && appData.drawRepeats > 1) {
8200                              /* adjudicate after user-specified nr of repeats */
8201                              int result = GameIsDrawn;
8202                              char *details = "XBoard adjudication: repetition draw";
8203                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8204                                 // [HGM] xiangqi: check for forbidden perpetuals
8205                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8206                                 for(m=forwardMostMove; m>k; m-=2) {
8207                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8208                                         ourPerpetual = 0; // the current mover did not always check
8209                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8210                                         hisPerpetual = 0; // the opponent did not always check
8211                                 }
8212                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8213                                                                         ourPerpetual, hisPerpetual);
8214                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8215                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8216                                     details = "Xboard adjudication: perpetual checking";
8217                                 } else
8218                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8219                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8220                                 } else
8221                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8222                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8223                                         result = BlackWins;
8224                                         details = "Xboard adjudication: repetition";
8225                                     }
8226                                 } else // it must be XQ
8227                                 // Now check for perpetual chases
8228                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8229                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8230                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8231                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8232                                         static char resdet[MSG_SIZ];
8233                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8234                                         details = resdet;
8235                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8236                                     } else
8237                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8238                                         break; // Abort repetition-checking loop.
8239                                 }
8240                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8241                              }
8242                              if(engineOpponent) {
8243                                SendToProgram("force\n", engineOpponent); // suppress reply
8244                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8245                              }
8246                              GameEnds( result, details, GE_XBOARD );
8247                              return 1;
8248                         }
8249                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8250                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8251                     }
8252                 }
8253
8254                 /* Now we test for 50-move draws. Determine ply count */
8255                 count = forwardMostMove;
8256                 /* look for last irreversble move */
8257                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8258                     count--;
8259                 /* if we hit starting position, add initial plies */
8260                 if( count == backwardMostMove )
8261                     count -= initialRulePlies;
8262                 count = forwardMostMove - count;
8263                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8264                         // adjust reversible move counter for checks in Xiangqi
8265                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8266                         if(i < backwardMostMove) i = backwardMostMove;
8267                         while(i <= forwardMostMove) {
8268                                 lastCheck = inCheck; // check evasion does not count
8269                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8270                                 if(inCheck || lastCheck) count--; // check does not count
8271                                 i++;
8272                         }
8273                 }
8274                 if( count >= 100)
8275                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8276                          /* this is used to judge if draw claims are legal */
8277                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8278                          if(engineOpponent) {
8279                            SendToProgram("force\n", engineOpponent); // suppress reply
8280                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8281                          }
8282                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8283                          return 1;
8284                 }
8285
8286                 /* if draw offer is pending, treat it as a draw claim
8287                  * when draw condition present, to allow engines a way to
8288                  * claim draws before making their move to avoid a race
8289                  * condition occurring after their move
8290                  */
8291                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8292                          char *p = NULL;
8293                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8294                              p = "Draw claim: 50-move rule";
8295                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8296                              p = "Draw claim: 3-fold repetition";
8297                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8298                              p = "Draw claim: insufficient mating material";
8299                          if( p != NULL && canAdjudicate) {
8300                              if(engineOpponent) {
8301                                SendToProgram("force\n", engineOpponent); // suppress reply
8302                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8303                              }
8304                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8305                              return 1;
8306                          }
8307                 }
8308
8309                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8310                     if(engineOpponent) {
8311                       SendToProgram("force\n", engineOpponent); // suppress reply
8312                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8313                     }
8314                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8315                     return 1;
8316                 }
8317         return 0;
8318 }
8319
8320 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8321 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8322 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8323
8324 static int
8325 BitbaseProbe ()
8326 {
8327     int pieces[10], squares[10], cnt=0, r, f, res;
8328     static int loaded;
8329     static PPROBE_EGBB probeBB;
8330     if(!appData.testLegality) return 10;
8331     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8332     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8333     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8334     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8335         ChessSquare piece = boards[forwardMostMove][r][f];
8336         int black = (piece >= BlackPawn);
8337         int type = piece - black*BlackPawn;
8338         if(piece == EmptySquare) continue;
8339         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8340         if(type == WhiteKing) type = WhiteQueen + 1;
8341         type = egbbCode[type];
8342         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8343         pieces[cnt] = type + black*6;
8344         if(++cnt > 5) return 11;
8345     }
8346     pieces[cnt] = squares[cnt] = 0;
8347     // probe EGBB
8348     if(loaded == 2) return 13; // loading failed before
8349     if(loaded == 0) {
8350         loaded = 2; // prepare for failure
8351         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8352         HMODULE lib;
8353         PLOAD_EGBB loadBB;
8354         if(!path) return 13; // no egbb installed
8355         strncpy(buf, path + 8, MSG_SIZ);
8356         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8357         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8358         lib = LoadLibrary(buf);
8359         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8360         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8361         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8362         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8363         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8364         loaded = 1; // success!
8365     }
8366     res = probeBB(forwardMostMove & 1, pieces, squares);
8367     return res > 0 ? 1 : res < 0 ? -1 : 0;
8368 }
8369
8370 char *
8371 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8372 {   // [HGM] book: this routine intercepts moves to simulate book replies
8373     char *bookHit = NULL;
8374
8375     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8376         char buf[MSG_SIZ];
8377         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8378         SendToProgram(buf, cps);
8379     }
8380     //first determine if the incoming move brings opponent into his book
8381     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8382         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8383     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8384     if(bookHit != NULL && !cps->bookSuspend) {
8385         // make sure opponent is not going to reply after receiving move to book position
8386         SendToProgram("force\n", cps);
8387         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8388     }
8389     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8390     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8391     // now arrange restart after book miss
8392     if(bookHit) {
8393         // after a book hit we never send 'go', and the code after the call to this routine
8394         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8395         char buf[MSG_SIZ], *move = bookHit;
8396         if(cps->useSAN) {
8397             int fromX, fromY, toX, toY;
8398             char promoChar;
8399             ChessMove moveType;
8400             move = buf + 30;
8401             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8402                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8403                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8404                                     PosFlags(forwardMostMove),
8405                                     fromY, fromX, toY, toX, promoChar, move);
8406             } else {
8407                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8408                 bookHit = NULL;
8409             }
8410         }
8411         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8412         SendToProgram(buf, cps);
8413         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8414     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8415         SendToProgram("go\n", cps);
8416         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8417     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8418         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8419             SendToProgram("go\n", cps);
8420         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8421     }
8422     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8423 }
8424
8425 int
8426 LoadError (char *errmess, ChessProgramState *cps)
8427 {   // unloads engine and switches back to -ncp mode if it was first
8428     if(cps->initDone) return FALSE;
8429     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8430     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8431     cps->pr = NoProc;
8432     if(cps == &first) {
8433         appData.noChessProgram = TRUE;
8434         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8435         gameMode = BeginningOfGame; ModeHighlight();
8436         SetNCPMode();
8437     }
8438     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8439     DisplayMessage("", ""); // erase waiting message
8440     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8441     return TRUE;
8442 }
8443
8444 char *savedMessage;
8445 ChessProgramState *savedState;
8446 void
8447 DeferredBookMove (void)
8448 {
8449         if(savedState->lastPing != savedState->lastPong)
8450                     ScheduleDelayedEvent(DeferredBookMove, 10);
8451         else
8452         HandleMachineMove(savedMessage, savedState);
8453 }
8454
8455 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8456 static ChessProgramState *stalledEngine;
8457 static char stashedInputMove[MSG_SIZ];
8458
8459 void
8460 HandleMachineMove (char *message, ChessProgramState *cps)
8461 {
8462     static char firstLeg[20];
8463     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8464     char realname[MSG_SIZ];
8465     int fromX, fromY, toX, toY;
8466     ChessMove moveType;
8467     char promoChar, roar;
8468     char *p, *pv=buf1;
8469     int machineWhite, oldError;
8470     char *bookHit;
8471
8472     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8473         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8474         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8475             DisplayError(_("Invalid pairing from pairing engine"), 0);
8476             return;
8477         }
8478         pairingReceived = 1;
8479         NextMatchGame();
8480         return; // Skim the pairing messages here.
8481     }
8482
8483     oldError = cps->userError; cps->userError = 0;
8484
8485 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8486     /*
8487      * Kludge to ignore BEL characters
8488      */
8489     while (*message == '\007') message++;
8490
8491     /*
8492      * [HGM] engine debug message: ignore lines starting with '#' character
8493      */
8494     if(cps->debug && *message == '#') return;
8495
8496     /*
8497      * Look for book output
8498      */
8499     if (cps == &first && bookRequested) {
8500         if (message[0] == '\t' || message[0] == ' ') {
8501             /* Part of the book output is here; append it */
8502             strcat(bookOutput, message);
8503             strcat(bookOutput, "  \n");
8504             return;
8505         } else if (bookOutput[0] != NULLCHAR) {
8506             /* All of book output has arrived; display it */
8507             char *p = bookOutput;
8508             while (*p != NULLCHAR) {
8509                 if (*p == '\t') *p = ' ';
8510                 p++;
8511             }
8512             DisplayInformation(bookOutput);
8513             bookRequested = FALSE;
8514             /* Fall through to parse the current output */
8515         }
8516     }
8517
8518     /*
8519      * Look for machine move.
8520      */
8521     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8522         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8523     {
8524         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8525             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8526             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8527             stalledEngine = cps;
8528             if(appData.ponderNextMove) { // bring opponent out of ponder
8529                 if(gameMode == TwoMachinesPlay) {
8530                     if(cps->other->pause)
8531                         PauseEngine(cps->other);
8532                     else
8533                         SendToProgram("easy\n", cps->other);
8534                 }
8535             }
8536             StopClocks();
8537             return;
8538         }
8539
8540         /* This method is only useful on engines that support ping */
8541         if (cps->lastPing != cps->lastPong) {
8542           if (gameMode == BeginningOfGame) {
8543             /* Extra move from before last new; ignore */
8544             if (appData.debugMode) {
8545                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8546             }
8547           } else {
8548             if (appData.debugMode) {
8549                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8550                         cps->which, gameMode);
8551             }
8552
8553             SendToProgram("undo\n", cps);
8554           }
8555           return;
8556         }
8557
8558         switch (gameMode) {
8559           case BeginningOfGame:
8560             /* Extra move from before last reset; ignore */
8561             if (appData.debugMode) {
8562                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8563             }
8564             return;
8565
8566           case EndOfGame:
8567           case IcsIdle:
8568           default:
8569             /* Extra move after we tried to stop.  The mode test is
8570                not a reliable way of detecting this problem, but it's
8571                the best we can do on engines that don't support ping.
8572             */
8573             if (appData.debugMode) {
8574                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8575                         cps->which, gameMode);
8576             }
8577             SendToProgram("undo\n", cps);
8578             return;
8579
8580           case MachinePlaysWhite:
8581           case IcsPlayingWhite:
8582             machineWhite = TRUE;
8583             break;
8584
8585           case MachinePlaysBlack:
8586           case IcsPlayingBlack:
8587             machineWhite = FALSE;
8588             break;
8589
8590           case TwoMachinesPlay:
8591             machineWhite = (cps->twoMachinesColor[0] == 'w');
8592             break;
8593         }
8594         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8595             if (appData.debugMode) {
8596                 fprintf(debugFP,
8597                         "Ignoring move out of turn by %s, gameMode %d"
8598                         ", forwardMost %d\n",
8599                         cps->which, gameMode, forwardMostMove);
8600             }
8601             return;
8602         }
8603
8604         if(cps->alphaRank) AlphaRank(machineMove, 4);
8605
8606         // [HGM] lion: (some very limited) support for Alien protocol
8607         killX = killY = -1;
8608         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8609             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8610             return;
8611         } else if(firstLeg[0]) { // there was a previous leg;
8612             // only support case where same piece makes two step (and don't even test that!)
8613             char buf[20], *p = machineMove+1, *q = buf+1, f;
8614             safeStrCpy(buf, machineMove, 20);
8615             while(isdigit(*q)) q++; // find start of to-square
8616             safeStrCpy(machineMove, firstLeg, 20);
8617             while(isdigit(*p)) p++;
8618             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8619             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8620             firstLeg[0] = NULLCHAR;
8621         }
8622
8623         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8624                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8625             /* Machine move could not be parsed; ignore it. */
8626           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8627                     machineMove, _(cps->which));
8628             DisplayMoveError(buf1);
8629             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8630                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8631             if (gameMode == TwoMachinesPlay) {
8632               GameEnds(machineWhite ? BlackWins : WhiteWins,
8633                        buf1, GE_XBOARD);
8634             }
8635             return;
8636         }
8637
8638         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8639         /* So we have to redo legality test with true e.p. status here,  */
8640         /* to make sure an illegal e.p. capture does not slip through,   */
8641         /* to cause a forfeit on a justified illegal-move complaint      */
8642         /* of the opponent.                                              */
8643         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8644            ChessMove moveType;
8645            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8646                              fromY, fromX, toY, toX, promoChar);
8647             if(moveType == IllegalMove) {
8648               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8649                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8650                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8651                            buf1, GE_XBOARD);
8652                 return;
8653            } else if(!appData.fischerCastling)
8654            /* [HGM] Kludge to handle engines that send FRC-style castling
8655               when they shouldn't (like TSCP-Gothic) */
8656            switch(moveType) {
8657              case WhiteASideCastleFR:
8658              case BlackASideCastleFR:
8659                toX+=2;
8660                currentMoveString[2]++;
8661                break;
8662              case WhiteHSideCastleFR:
8663              case BlackHSideCastleFR:
8664                toX--;
8665                currentMoveString[2]--;
8666                break;
8667              default: ; // nothing to do, but suppresses warning of pedantic compilers
8668            }
8669         }
8670         hintRequested = FALSE;
8671         lastHint[0] = NULLCHAR;
8672         bookRequested = FALSE;
8673         /* Program may be pondering now */
8674         cps->maybeThinking = TRUE;
8675         if (cps->sendTime == 2) cps->sendTime = 1;
8676         if (cps->offeredDraw) cps->offeredDraw--;
8677
8678         /* [AS] Save move info*/
8679         pvInfoList[ forwardMostMove ].score = programStats.score;
8680         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8681         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8682
8683         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8684
8685         /* Test suites abort the 'game' after one move */
8686         if(*appData.finger) {
8687            static FILE *f;
8688            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8689            if(!f) f = fopen(appData.finger, "w");
8690            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8691            else { DisplayFatalError("Bad output file", errno, 0); return; }
8692            free(fen);
8693            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8694         }
8695
8696         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8697         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8698             int count = 0;
8699
8700             while( count < adjudicateLossPlies ) {
8701                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8702
8703                 if( count & 1 ) {
8704                     score = -score; /* Flip score for winning side */
8705                 }
8706
8707                 if( score > adjudicateLossThreshold ) {
8708                     break;
8709                 }
8710
8711                 count++;
8712             }
8713
8714             if( count >= adjudicateLossPlies ) {
8715                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8716
8717                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8718                     "Xboard adjudication",
8719                     GE_XBOARD );
8720
8721                 return;
8722             }
8723         }
8724
8725         if(Adjudicate(cps)) {
8726             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8727             return; // [HGM] adjudicate: for all automatic game ends
8728         }
8729
8730 #if ZIPPY
8731         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8732             first.initDone) {
8733           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8734                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8735                 SendToICS("draw ");
8736                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8737           }
8738           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8739           ics_user_moved = 1;
8740           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8741                 char buf[3*MSG_SIZ];
8742
8743                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8744                         programStats.score / 100.,
8745                         programStats.depth,
8746                         programStats.time / 100.,
8747                         (unsigned int)programStats.nodes,
8748                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8749                         programStats.movelist);
8750                 SendToICS(buf);
8751 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8752           }
8753         }
8754 #endif
8755
8756         /* [AS] Clear stats for next move */
8757         ClearProgramStats();
8758         thinkOutput[0] = NULLCHAR;
8759         hiddenThinkOutputState = 0;
8760
8761         bookHit = NULL;
8762         if (gameMode == TwoMachinesPlay) {
8763             /* [HGM] relaying draw offers moved to after reception of move */
8764             /* and interpreting offer as claim if it brings draw condition */
8765             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8766                 SendToProgram("draw\n", cps->other);
8767             }
8768             if (cps->other->sendTime) {
8769                 SendTimeRemaining(cps->other,
8770                                   cps->other->twoMachinesColor[0] == 'w');
8771             }
8772             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8773             if (firstMove && !bookHit) {
8774                 firstMove = FALSE;
8775                 if (cps->other->useColors) {
8776                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8777                 }
8778                 SendToProgram("go\n", cps->other);
8779             }
8780             cps->other->maybeThinking = TRUE;
8781         }
8782
8783         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8784
8785         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8786
8787         if (!pausing && appData.ringBellAfterMoves) {
8788             if(!roar) RingBell();
8789         }
8790
8791         /*
8792          * Reenable menu items that were disabled while
8793          * machine was thinking
8794          */
8795         if (gameMode != TwoMachinesPlay)
8796             SetUserThinkingEnables();
8797
8798         // [HGM] book: after book hit opponent has received move and is now in force mode
8799         // force the book reply into it, and then fake that it outputted this move by jumping
8800         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8801         if(bookHit) {
8802                 static char bookMove[MSG_SIZ]; // a bit generous?
8803
8804                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8805                 strcat(bookMove, bookHit);
8806                 message = bookMove;
8807                 cps = cps->other;
8808                 programStats.nodes = programStats.depth = programStats.time =
8809                 programStats.score = programStats.got_only_move = 0;
8810                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8811
8812                 if(cps->lastPing != cps->lastPong) {
8813                     savedMessage = message; // args for deferred call
8814                     savedState = cps;
8815                     ScheduleDelayedEvent(DeferredBookMove, 10);
8816                     return;
8817                 }
8818                 goto FakeBookMove;
8819         }
8820
8821         return;
8822     }
8823
8824     /* Set special modes for chess engines.  Later something general
8825      *  could be added here; for now there is just one kludge feature,
8826      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8827      *  when "xboard" is given as an interactive command.
8828      */
8829     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8830         cps->useSigint = FALSE;
8831         cps->useSigterm = FALSE;
8832     }
8833     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8834       ParseFeatures(message+8, cps);
8835       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8836     }
8837
8838     if (!strncmp(message, "setup ", 6) && 
8839         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8840           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8841                                         ) { // [HGM] allow first engine to define opening position
8842       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8843       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8844       *buf = NULLCHAR;
8845       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8846       if(startedFromSetupPosition) return;
8847       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8848       if(dummy >= 3) {
8849         while(message[s] && message[s++] != ' ');
8850         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8851            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8852             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8853             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8854           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8855           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8856         }
8857       }
8858       ParseFEN(boards[0], &dummy, message+s, FALSE);
8859       DrawPosition(TRUE, boards[0]);
8860       startedFromSetupPosition = TRUE;
8861       return;
8862     }
8863     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8864      * want this, I was asked to put it in, and obliged.
8865      */
8866     if (!strncmp(message, "setboard ", 9)) {
8867         Board initial_position;
8868
8869         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8870
8871         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8872             DisplayError(_("Bad FEN received from engine"), 0);
8873             return ;
8874         } else {
8875            Reset(TRUE, FALSE);
8876            CopyBoard(boards[0], initial_position);
8877            initialRulePlies = FENrulePlies;
8878            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8879            else gameMode = MachinePlaysBlack;
8880            DrawPosition(FALSE, boards[currentMove]);
8881         }
8882         return;
8883     }
8884
8885     /*
8886      * Look for communication commands
8887      */
8888     if (!strncmp(message, "telluser ", 9)) {
8889         if(message[9] == '\\' && message[10] == '\\')
8890             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8891         PlayTellSound();
8892         DisplayNote(message + 9);
8893         return;
8894     }
8895     if (!strncmp(message, "tellusererror ", 14)) {
8896         cps->userError = 1;
8897         if(message[14] == '\\' && message[15] == '\\')
8898             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8899         PlayTellSound();
8900         DisplayError(message + 14, 0);
8901         return;
8902     }
8903     if (!strncmp(message, "tellopponent ", 13)) {
8904       if (appData.icsActive) {
8905         if (loggedOn) {
8906           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8907           SendToICS(buf1);
8908         }
8909       } else {
8910         DisplayNote(message + 13);
8911       }
8912       return;
8913     }
8914     if (!strncmp(message, "tellothers ", 11)) {
8915       if (appData.icsActive) {
8916         if (loggedOn) {
8917           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8918           SendToICS(buf1);
8919         }
8920       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8921       return;
8922     }
8923     if (!strncmp(message, "tellall ", 8)) {
8924       if (appData.icsActive) {
8925         if (loggedOn) {
8926           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8927           SendToICS(buf1);
8928         }
8929       } else {
8930         DisplayNote(message + 8);
8931       }
8932       return;
8933     }
8934     if (strncmp(message, "warning", 7) == 0) {
8935         /* Undocumented feature, use tellusererror in new code */
8936         DisplayError(message, 0);
8937         return;
8938     }
8939     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8940         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8941         strcat(realname, " query");
8942         AskQuestion(realname, buf2, buf1, cps->pr);
8943         return;
8944     }
8945     /* Commands from the engine directly to ICS.  We don't allow these to be
8946      *  sent until we are logged on. Crafty kibitzes have been known to
8947      *  interfere with the login process.
8948      */
8949     if (loggedOn) {
8950         if (!strncmp(message, "tellics ", 8)) {
8951             SendToICS(message + 8);
8952             SendToICS("\n");
8953             return;
8954         }
8955         if (!strncmp(message, "tellicsnoalias ", 15)) {
8956             SendToICS(ics_prefix);
8957             SendToICS(message + 15);
8958             SendToICS("\n");
8959             return;
8960         }
8961         /* The following are for backward compatibility only */
8962         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8963             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8964             SendToICS(ics_prefix);
8965             SendToICS(message);
8966             SendToICS("\n");
8967             return;
8968         }
8969     }
8970     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8971         if(initPing == cps->lastPong) {
8972             if(gameInfo.variant == VariantUnknown) {
8973                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8974                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8975                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8976             }
8977             initPing = -1;
8978         }
8979         return;
8980     }
8981     if(!strncmp(message, "highlight ", 10)) {
8982         if(appData.testLegality && appData.markers) return;
8983         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8984         return;
8985     }
8986     if(!strncmp(message, "click ", 6)) {
8987         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8988         if(appData.testLegality || !appData.oneClick) return;
8989         sscanf(message+6, "%c%d%c", &f, &y, &c);
8990         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8991         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8992         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8993         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8994         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8995         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8996             LeftClick(Release, lastLeftX, lastLeftY);
8997         controlKey  = (c == ',');
8998         LeftClick(Press, x, y);
8999         LeftClick(Release, x, y);
9000         first.highlight = f;
9001         return;
9002     }
9003     /*
9004      * If the move is illegal, cancel it and redraw the board.
9005      * Also deal with other error cases.  Matching is rather loose
9006      * here to accommodate engines written before the spec.
9007      */
9008     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9009         strncmp(message, "Error", 5) == 0) {
9010         if (StrStr(message, "name") ||
9011             StrStr(message, "rating") || StrStr(message, "?") ||
9012             StrStr(message, "result") || StrStr(message, "board") ||
9013             StrStr(message, "bk") || StrStr(message, "computer") ||
9014             StrStr(message, "variant") || StrStr(message, "hint") ||
9015             StrStr(message, "random") || StrStr(message, "depth") ||
9016             StrStr(message, "accepted")) {
9017             return;
9018         }
9019         if (StrStr(message, "protover")) {
9020           /* Program is responding to input, so it's apparently done
9021              initializing, and this error message indicates it is
9022              protocol version 1.  So we don't need to wait any longer
9023              for it to initialize and send feature commands. */
9024           FeatureDone(cps, 1);
9025           cps->protocolVersion = 1;
9026           return;
9027         }
9028         cps->maybeThinking = FALSE;
9029
9030         if (StrStr(message, "draw")) {
9031             /* Program doesn't have "draw" command */
9032             cps->sendDrawOffers = 0;
9033             return;
9034         }
9035         if (cps->sendTime != 1 &&
9036             (StrStr(message, "time") || StrStr(message, "otim"))) {
9037           /* Program apparently doesn't have "time" or "otim" command */
9038           cps->sendTime = 0;
9039           return;
9040         }
9041         if (StrStr(message, "analyze")) {
9042             cps->analysisSupport = FALSE;
9043             cps->analyzing = FALSE;
9044 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9045             EditGameEvent(); // [HGM] try to preserve loaded game
9046             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9047             DisplayError(buf2, 0);
9048             return;
9049         }
9050         if (StrStr(message, "(no matching move)st")) {
9051           /* Special kludge for GNU Chess 4 only */
9052           cps->stKludge = TRUE;
9053           SendTimeControl(cps, movesPerSession, timeControl,
9054                           timeIncrement, appData.searchDepth,
9055                           searchTime);
9056           return;
9057         }
9058         if (StrStr(message, "(no matching move)sd")) {
9059           /* Special kludge for GNU Chess 4 only */
9060           cps->sdKludge = TRUE;
9061           SendTimeControl(cps, movesPerSession, timeControl,
9062                           timeIncrement, appData.searchDepth,
9063                           searchTime);
9064           return;
9065         }
9066         if (!StrStr(message, "llegal")) {
9067             return;
9068         }
9069         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9070             gameMode == IcsIdle) return;
9071         if (forwardMostMove <= backwardMostMove) return;
9072         if (pausing) PauseEvent();
9073       if(appData.forceIllegal) {
9074             // [HGM] illegal: machine refused move; force position after move into it
9075           SendToProgram("force\n", cps);
9076           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9077                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9078                 // when black is to move, while there might be nothing on a2 or black
9079                 // might already have the move. So send the board as if white has the move.
9080                 // But first we must change the stm of the engine, as it refused the last move
9081                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9082                 if(WhiteOnMove(forwardMostMove)) {
9083                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9084                     SendBoard(cps, forwardMostMove); // kludgeless board
9085                 } else {
9086                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9087                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9088                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9089                 }
9090           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9091             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9092                  gameMode == TwoMachinesPlay)
9093               SendToProgram("go\n", cps);
9094             return;
9095       } else
9096         if (gameMode == PlayFromGameFile) {
9097             /* Stop reading this game file */
9098             gameMode = EditGame;
9099             ModeHighlight();
9100         }
9101         /* [HGM] illegal-move claim should forfeit game when Xboard */
9102         /* only passes fully legal moves                            */
9103         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9104             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9105                                 "False illegal-move claim", GE_XBOARD );
9106             return; // do not take back move we tested as valid
9107         }
9108         currentMove = forwardMostMove-1;
9109         DisplayMove(currentMove-1); /* before DisplayMoveError */
9110         SwitchClocks(forwardMostMove-1); // [HGM] race
9111         DisplayBothClocks();
9112         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9113                 parseList[currentMove], _(cps->which));
9114         DisplayMoveError(buf1);
9115         DrawPosition(FALSE, boards[currentMove]);
9116
9117         SetUserThinkingEnables();
9118         return;
9119     }
9120     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9121         /* Program has a broken "time" command that
9122            outputs a string not ending in newline.
9123            Don't use it. */
9124         cps->sendTime = 0;
9125     }
9126
9127     /*
9128      * If chess program startup fails, exit with an error message.
9129      * Attempts to recover here are futile. [HGM] Well, we try anyway
9130      */
9131     if ((StrStr(message, "unknown host") != NULL)
9132         || (StrStr(message, "No remote directory") != NULL)
9133         || (StrStr(message, "not found") != NULL)
9134         || (StrStr(message, "No such file") != NULL)
9135         || (StrStr(message, "can't alloc") != NULL)
9136         || (StrStr(message, "Permission denied") != NULL)) {
9137
9138         cps->maybeThinking = FALSE;
9139         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9140                 _(cps->which), cps->program, cps->host, message);
9141         RemoveInputSource(cps->isr);
9142         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9143             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9144             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9145         }
9146         return;
9147     }
9148
9149     /*
9150      * Look for hint output
9151      */
9152     if (sscanf(message, "Hint: %s", buf1) == 1) {
9153         if (cps == &first && hintRequested) {
9154             hintRequested = FALSE;
9155             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9156                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9157                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9158                                     PosFlags(forwardMostMove),
9159                                     fromY, fromX, toY, toX, promoChar, buf1);
9160                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9161                 DisplayInformation(buf2);
9162             } else {
9163                 /* Hint move could not be parsed!? */
9164               snprintf(buf2, sizeof(buf2),
9165                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9166                         buf1, _(cps->which));
9167                 DisplayError(buf2, 0);
9168             }
9169         } else {
9170           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9171         }
9172         return;
9173     }
9174
9175     /*
9176      * Ignore other messages if game is not in progress
9177      */
9178     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9179         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9180
9181     /*
9182      * look for win, lose, draw, or draw offer
9183      */
9184     if (strncmp(message, "1-0", 3) == 0) {
9185         char *p, *q, *r = "";
9186         p = strchr(message, '{');
9187         if (p) {
9188             q = strchr(p, '}');
9189             if (q) {
9190                 *q = NULLCHAR;
9191                 r = p + 1;
9192             }
9193         }
9194         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9195         return;
9196     } else if (strncmp(message, "0-1", 3) == 0) {
9197         char *p, *q, *r = "";
9198         p = strchr(message, '{');
9199         if (p) {
9200             q = strchr(p, '}');
9201             if (q) {
9202                 *q = NULLCHAR;
9203                 r = p + 1;
9204             }
9205         }
9206         /* Kludge for Arasan 4.1 bug */
9207         if (strcmp(r, "Black resigns") == 0) {
9208             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9209             return;
9210         }
9211         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9212         return;
9213     } else if (strncmp(message, "1/2", 3) == 0) {
9214         char *p, *q, *r = "";
9215         p = strchr(message, '{');
9216         if (p) {
9217             q = strchr(p, '}');
9218             if (q) {
9219                 *q = NULLCHAR;
9220                 r = p + 1;
9221             }
9222         }
9223
9224         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9225         return;
9226
9227     } else if (strncmp(message, "White resign", 12) == 0) {
9228         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9229         return;
9230     } else if (strncmp(message, "Black resign", 12) == 0) {
9231         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9232         return;
9233     } else if (strncmp(message, "White matches", 13) == 0 ||
9234                strncmp(message, "Black matches", 13) == 0   ) {
9235         /* [HGM] ignore GNUShogi noises */
9236         return;
9237     } else if (strncmp(message, "White", 5) == 0 &&
9238                message[5] != '(' &&
9239                StrStr(message, "Black") == NULL) {
9240         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9241         return;
9242     } else if (strncmp(message, "Black", 5) == 0 &&
9243                message[5] != '(') {
9244         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9245         return;
9246     } else if (strcmp(message, "resign") == 0 ||
9247                strcmp(message, "computer resigns") == 0) {
9248         switch (gameMode) {
9249           case MachinePlaysBlack:
9250           case IcsPlayingBlack:
9251             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9252             break;
9253           case MachinePlaysWhite:
9254           case IcsPlayingWhite:
9255             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9256             break;
9257           case TwoMachinesPlay:
9258             if (cps->twoMachinesColor[0] == 'w')
9259               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9260             else
9261               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262             break;
9263           default:
9264             /* can't happen */
9265             break;
9266         }
9267         return;
9268     } else if (strncmp(message, "opponent mates", 14) == 0) {
9269         switch (gameMode) {
9270           case MachinePlaysBlack:
9271           case IcsPlayingBlack:
9272             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9273             break;
9274           case MachinePlaysWhite:
9275           case IcsPlayingWhite:
9276             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9277             break;
9278           case TwoMachinesPlay:
9279             if (cps->twoMachinesColor[0] == 'w')
9280               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9281             else
9282               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9283             break;
9284           default:
9285             /* can't happen */
9286             break;
9287         }
9288         return;
9289     } else if (strncmp(message, "computer mates", 14) == 0) {
9290         switch (gameMode) {
9291           case MachinePlaysBlack:
9292           case IcsPlayingBlack:
9293             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9294             break;
9295           case MachinePlaysWhite:
9296           case IcsPlayingWhite:
9297             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9298             break;
9299           case TwoMachinesPlay:
9300             if (cps->twoMachinesColor[0] == 'w')
9301               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9302             else
9303               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9304             break;
9305           default:
9306             /* can't happen */
9307             break;
9308         }
9309         return;
9310     } else if (strncmp(message, "checkmate", 9) == 0) {
9311         if (WhiteOnMove(forwardMostMove)) {
9312             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9313         } else {
9314             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9315         }
9316         return;
9317     } else if (strstr(message, "Draw") != NULL ||
9318                strstr(message, "game is a draw") != NULL) {
9319         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9320         return;
9321     } else if (strstr(message, "offer") != NULL &&
9322                strstr(message, "draw") != NULL) {
9323 #if ZIPPY
9324         if (appData.zippyPlay && first.initDone) {
9325             /* Relay offer to ICS */
9326             SendToICS(ics_prefix);
9327             SendToICS("draw\n");
9328         }
9329 #endif
9330         cps->offeredDraw = 2; /* valid until this engine moves twice */
9331         if (gameMode == TwoMachinesPlay) {
9332             if (cps->other->offeredDraw) {
9333                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9334             /* [HGM] in two-machine mode we delay relaying draw offer      */
9335             /* until after we also have move, to see if it is really claim */
9336             }
9337         } else if (gameMode == MachinePlaysWhite ||
9338                    gameMode == MachinePlaysBlack) {
9339           if (userOfferedDraw) {
9340             DisplayInformation(_("Machine accepts your draw offer"));
9341             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9342           } else {
9343             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9344           }
9345         }
9346     }
9347
9348
9349     /*
9350      * Look for thinking output
9351      */
9352     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9353           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9354                                 ) {
9355         int plylev, mvleft, mvtot, curscore, time;
9356         char mvname[MOVE_LEN];
9357         u64 nodes; // [DM]
9358         char plyext;
9359         int ignore = FALSE;
9360         int prefixHint = FALSE;
9361         mvname[0] = NULLCHAR;
9362
9363         switch (gameMode) {
9364           case MachinePlaysBlack:
9365           case IcsPlayingBlack:
9366             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9367             break;
9368           case MachinePlaysWhite:
9369           case IcsPlayingWhite:
9370             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9371             break;
9372           case AnalyzeMode:
9373           case AnalyzeFile:
9374             break;
9375           case IcsObserving: /* [DM] icsEngineAnalyze */
9376             if (!appData.icsEngineAnalyze) ignore = TRUE;
9377             break;
9378           case TwoMachinesPlay:
9379             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9380                 ignore = TRUE;
9381             }
9382             break;
9383           default:
9384             ignore = TRUE;
9385             break;
9386         }
9387
9388         if (!ignore) {
9389             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9390             buf1[0] = NULLCHAR;
9391             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9392                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9393
9394                 if (plyext != ' ' && plyext != '\t') {
9395                     time *= 100;
9396                 }
9397
9398                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9399                 if( cps->scoreIsAbsolute &&
9400                     ( gameMode == MachinePlaysBlack ||
9401                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9402                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9403                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9404                      !WhiteOnMove(currentMove)
9405                     ) )
9406                 {
9407                     curscore = -curscore;
9408                 }
9409
9410                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9411
9412                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9413                         char buf[MSG_SIZ];
9414                         FILE *f;
9415                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9416                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9417                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9418                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9419                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9420                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9421                                 fclose(f);
9422                         }
9423                         else
9424                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9425                           DisplayError(_("failed writing PV"), 0);
9426                 }
9427
9428                 tempStats.depth = plylev;
9429                 tempStats.nodes = nodes;
9430                 tempStats.time = time;
9431                 tempStats.score = curscore;
9432                 tempStats.got_only_move = 0;
9433
9434                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9435                         int ticklen;
9436
9437                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9438                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9439                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9440                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9441                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9442                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9443                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9444                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9445                 }
9446
9447                 /* Buffer overflow protection */
9448                 if (pv[0] != NULLCHAR) {
9449                     if (strlen(pv) >= sizeof(tempStats.movelist)
9450                         && appData.debugMode) {
9451                         fprintf(debugFP,
9452                                 "PV is too long; using the first %u bytes.\n",
9453                                 (unsigned) sizeof(tempStats.movelist) - 1);
9454                     }
9455
9456                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9457                 } else {
9458                     sprintf(tempStats.movelist, " no PV\n");
9459                 }
9460
9461                 if (tempStats.seen_stat) {
9462                     tempStats.ok_to_send = 1;
9463                 }
9464
9465                 if (strchr(tempStats.movelist, '(') != NULL) {
9466                     tempStats.line_is_book = 1;
9467                     tempStats.nr_moves = 0;
9468                     tempStats.moves_left = 0;
9469                 } else {
9470                     tempStats.line_is_book = 0;
9471                 }
9472
9473                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9474                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9475
9476                 SendProgramStatsToFrontend( cps, &tempStats );
9477
9478                 /*
9479                     [AS] Protect the thinkOutput buffer from overflow... this
9480                     is only useful if buf1 hasn't overflowed first!
9481                 */
9482                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9483                          plylev,
9484                          (gameMode == TwoMachinesPlay ?
9485                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9486                          ((double) curscore) / 100.0,
9487                          prefixHint ? lastHint : "",
9488                          prefixHint ? " " : "" );
9489
9490                 if( buf1[0] != NULLCHAR ) {
9491                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9492
9493                     if( strlen(pv) > max_len ) {
9494                         if( appData.debugMode) {
9495                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9496                         }
9497                         pv[max_len+1] = '\0';
9498                     }
9499
9500                     strcat( thinkOutput, pv);
9501                 }
9502
9503                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9504                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9505                     DisplayMove(currentMove - 1);
9506                 }
9507                 return;
9508
9509             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9510                 /* crafty (9.25+) says "(only move) <move>"
9511                  * if there is only 1 legal move
9512                  */
9513                 sscanf(p, "(only move) %s", buf1);
9514                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9515                 sprintf(programStats.movelist, "%s (only move)", buf1);
9516                 programStats.depth = 1;
9517                 programStats.nr_moves = 1;
9518                 programStats.moves_left = 1;
9519                 programStats.nodes = 1;
9520                 programStats.time = 1;
9521                 programStats.got_only_move = 1;
9522
9523                 /* Not really, but we also use this member to
9524                    mean "line isn't going to change" (Crafty
9525                    isn't searching, so stats won't change) */
9526                 programStats.line_is_book = 1;
9527
9528                 SendProgramStatsToFrontend( cps, &programStats );
9529
9530                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9531                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9532                     DisplayMove(currentMove - 1);
9533                 }
9534                 return;
9535             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9536                               &time, &nodes, &plylev, &mvleft,
9537                               &mvtot, mvname) >= 5) {
9538                 /* The stat01: line is from Crafty (9.29+) in response
9539                    to the "." command */
9540                 programStats.seen_stat = 1;
9541                 cps->maybeThinking = TRUE;
9542
9543                 if (programStats.got_only_move || !appData.periodicUpdates)
9544                   return;
9545
9546                 programStats.depth = plylev;
9547                 programStats.time = time;
9548                 programStats.nodes = nodes;
9549                 programStats.moves_left = mvleft;
9550                 programStats.nr_moves = mvtot;
9551                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9552                 programStats.ok_to_send = 1;
9553                 programStats.movelist[0] = '\0';
9554
9555                 SendProgramStatsToFrontend( cps, &programStats );
9556
9557                 return;
9558
9559             } else if (strncmp(message,"++",2) == 0) {
9560                 /* Crafty 9.29+ outputs this */
9561                 programStats.got_fail = 2;
9562                 return;
9563
9564             } else if (strncmp(message,"--",2) == 0) {
9565                 /* Crafty 9.29+ outputs this */
9566                 programStats.got_fail = 1;
9567                 return;
9568
9569             } else if (thinkOutput[0] != NULLCHAR &&
9570                        strncmp(message, "    ", 4) == 0) {
9571                 unsigned message_len;
9572
9573                 p = message;
9574                 while (*p && *p == ' ') p++;
9575
9576                 message_len = strlen( p );
9577
9578                 /* [AS] Avoid buffer overflow */
9579                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9580                     strcat(thinkOutput, " ");
9581                     strcat(thinkOutput, p);
9582                 }
9583
9584                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9585                     strcat(programStats.movelist, " ");
9586                     strcat(programStats.movelist, p);
9587                 }
9588
9589                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9590                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9591                     DisplayMove(currentMove - 1);
9592                 }
9593                 return;
9594             }
9595         }
9596         else {
9597             buf1[0] = NULLCHAR;
9598
9599             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9600                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9601             {
9602                 ChessProgramStats cpstats;
9603
9604                 if (plyext != ' ' && plyext != '\t') {
9605                     time *= 100;
9606                 }
9607
9608                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9609                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9610                     curscore = -curscore;
9611                 }
9612
9613                 cpstats.depth = plylev;
9614                 cpstats.nodes = nodes;
9615                 cpstats.time = time;
9616                 cpstats.score = curscore;
9617                 cpstats.got_only_move = 0;
9618                 cpstats.movelist[0] = '\0';
9619
9620                 if (buf1[0] != NULLCHAR) {
9621                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9622                 }
9623
9624                 cpstats.ok_to_send = 0;
9625                 cpstats.line_is_book = 0;
9626                 cpstats.nr_moves = 0;
9627                 cpstats.moves_left = 0;
9628
9629                 SendProgramStatsToFrontend( cps, &cpstats );
9630             }
9631         }
9632     }
9633 }
9634
9635
9636 /* Parse a game score from the character string "game", and
9637    record it as the history of the current game.  The game
9638    score is NOT assumed to start from the standard position.
9639    The display is not updated in any way.
9640    */
9641 void
9642 ParseGameHistory (char *game)
9643 {
9644     ChessMove moveType;
9645     int fromX, fromY, toX, toY, boardIndex;
9646     char promoChar;
9647     char *p, *q;
9648     char buf[MSG_SIZ];
9649
9650     if (appData.debugMode)
9651       fprintf(debugFP, "Parsing game history: %s\n", game);
9652
9653     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9654     gameInfo.site = StrSave(appData.icsHost);
9655     gameInfo.date = PGNDate();
9656     gameInfo.round = StrSave("-");
9657
9658     /* Parse out names of players */
9659     while (*game == ' ') game++;
9660     p = buf;
9661     while (*game != ' ') *p++ = *game++;
9662     *p = NULLCHAR;
9663     gameInfo.white = StrSave(buf);
9664     while (*game == ' ') game++;
9665     p = buf;
9666     while (*game != ' ' && *game != '\n') *p++ = *game++;
9667     *p = NULLCHAR;
9668     gameInfo.black = StrSave(buf);
9669
9670     /* Parse moves */
9671     boardIndex = blackPlaysFirst ? 1 : 0;
9672     yynewstr(game);
9673     for (;;) {
9674         yyboardindex = boardIndex;
9675         moveType = (ChessMove) Myylex();
9676         switch (moveType) {
9677           case IllegalMove:             /* maybe suicide chess, etc. */
9678   if (appData.debugMode) {
9679     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9680     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9681     setbuf(debugFP, NULL);
9682   }
9683           case WhitePromotion:
9684           case BlackPromotion:
9685           case WhiteNonPromotion:
9686           case BlackNonPromotion:
9687           case NormalMove:
9688           case FirstLeg:
9689           case WhiteCapturesEnPassant:
9690           case BlackCapturesEnPassant:
9691           case WhiteKingSideCastle:
9692           case WhiteQueenSideCastle:
9693           case BlackKingSideCastle:
9694           case BlackQueenSideCastle:
9695           case WhiteKingSideCastleWild:
9696           case WhiteQueenSideCastleWild:
9697           case BlackKingSideCastleWild:
9698           case BlackQueenSideCastleWild:
9699           /* PUSH Fabien */
9700           case WhiteHSideCastleFR:
9701           case WhiteASideCastleFR:
9702           case BlackHSideCastleFR:
9703           case BlackASideCastleFR:
9704           /* POP Fabien */
9705             fromX = currentMoveString[0] - AAA;
9706             fromY = currentMoveString[1] - ONE;
9707             toX = currentMoveString[2] - AAA;
9708             toY = currentMoveString[3] - ONE;
9709             promoChar = currentMoveString[4];
9710             break;
9711           case WhiteDrop:
9712           case BlackDrop:
9713             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9714             fromX = moveType == WhiteDrop ?
9715               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9716             (int) CharToPiece(ToLower(currentMoveString[0]));
9717             fromY = DROP_RANK;
9718             toX = currentMoveString[2] - AAA;
9719             toY = currentMoveString[3] - ONE;
9720             promoChar = NULLCHAR;
9721             break;
9722           case AmbiguousMove:
9723             /* bug? */
9724             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9725   if (appData.debugMode) {
9726     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9727     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9728     setbuf(debugFP, NULL);
9729   }
9730             DisplayError(buf, 0);
9731             return;
9732           case ImpossibleMove:
9733             /* bug? */
9734             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9735   if (appData.debugMode) {
9736     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9737     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9738     setbuf(debugFP, NULL);
9739   }
9740             DisplayError(buf, 0);
9741             return;
9742           case EndOfFile:
9743             if (boardIndex < backwardMostMove) {
9744                 /* Oops, gap.  How did that happen? */
9745                 DisplayError(_("Gap in move list"), 0);
9746                 return;
9747             }
9748             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9749             if (boardIndex > forwardMostMove) {
9750                 forwardMostMove = boardIndex;
9751             }
9752             return;
9753           case ElapsedTime:
9754             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9755                 strcat(parseList[boardIndex-1], " ");
9756                 strcat(parseList[boardIndex-1], yy_text);
9757             }
9758             continue;
9759           case Comment:
9760           case PGNTag:
9761           case NAG:
9762           default:
9763             /* ignore */
9764             continue;
9765           case WhiteWins:
9766           case BlackWins:
9767           case GameIsDrawn:
9768           case GameUnfinished:
9769             if (gameMode == IcsExamining) {
9770                 if (boardIndex < backwardMostMove) {
9771                     /* Oops, gap.  How did that happen? */
9772                     return;
9773                 }
9774                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9775                 return;
9776             }
9777             gameInfo.result = moveType;
9778             p = strchr(yy_text, '{');
9779             if (p == NULL) p = strchr(yy_text, '(');
9780             if (p == NULL) {
9781                 p = yy_text;
9782                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9783             } else {
9784                 q = strchr(p, *p == '{' ? '}' : ')');
9785                 if (q != NULL) *q = NULLCHAR;
9786                 p++;
9787             }
9788             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9789             gameInfo.resultDetails = StrSave(p);
9790             continue;
9791         }
9792         if (boardIndex >= forwardMostMove &&
9793             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9794             backwardMostMove = blackPlaysFirst ? 1 : 0;
9795             return;
9796         }
9797         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9798                                  fromY, fromX, toY, toX, promoChar,
9799                                  parseList[boardIndex]);
9800         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9801         /* currentMoveString is set as a side-effect of yylex */
9802         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9803         strcat(moveList[boardIndex], "\n");
9804         boardIndex++;
9805         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9806         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9807           case MT_NONE:
9808           case MT_STALEMATE:
9809           default:
9810             break;
9811           case MT_CHECK:
9812             if(!IS_SHOGI(gameInfo.variant))
9813                 strcat(parseList[boardIndex - 1], "+");
9814             break;
9815           case MT_CHECKMATE:
9816           case MT_STAINMATE:
9817             strcat(parseList[boardIndex - 1], "#");
9818             break;
9819         }
9820     }
9821 }
9822
9823
9824 /* Apply a move to the given board  */
9825 void
9826 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9827 {
9828   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9829   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9830
9831     /* [HGM] compute & store e.p. status and castling rights for new position */
9832     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9833
9834       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9835       oldEP = (signed char)board[EP_STATUS];
9836       board[EP_STATUS] = EP_NONE;
9837
9838   if (fromY == DROP_RANK) {
9839         /* must be first */
9840         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9841             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9842             return;
9843         }
9844         piece = board[toY][toX] = (ChessSquare) fromX;
9845   } else {
9846 //      ChessSquare victim;
9847       int i;
9848
9849       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9850 //           victim = board[killY][killX],
9851            board[killY][killX] = EmptySquare,
9852            board[EP_STATUS] = EP_CAPTURE;
9853
9854       if( board[toY][toX] != EmptySquare ) {
9855            board[EP_STATUS] = EP_CAPTURE;
9856            if( (fromX != toX || fromY != toY) && // not igui!
9857                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9858                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9859                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9860            }
9861       }
9862
9863       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9864            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9865                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9866       } else
9867       if( board[fromY][fromX] == WhitePawn ) {
9868            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9869                board[EP_STATUS] = EP_PAWN_MOVE;
9870            if( toY-fromY==2) {
9871                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9872                         gameInfo.variant != VariantBerolina || toX < fromX)
9873                       board[EP_STATUS] = toX | berolina;
9874                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9875                         gameInfo.variant != VariantBerolina || toX > fromX)
9876                       board[EP_STATUS] = toX;
9877            }
9878       } else
9879       if( board[fromY][fromX] == BlackPawn ) {
9880            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9881                board[EP_STATUS] = EP_PAWN_MOVE;
9882            if( toY-fromY== -2) {
9883                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9884                         gameInfo.variant != VariantBerolina || toX < fromX)
9885                       board[EP_STATUS] = toX | berolina;
9886                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9887                         gameInfo.variant != VariantBerolina || toX > fromX)
9888                       board[EP_STATUS] = toX;
9889            }
9890        }
9891
9892        for(i=0; i<nrCastlingRights; i++) {
9893            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9894               board[CASTLING][i] == toX   && castlingRank[i] == toY
9895              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9896        }
9897
9898        if(gameInfo.variant == VariantSChess) { // update virginity
9899            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9900            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9901            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9902            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9903        }
9904
9905      if (fromX == toX && fromY == toY) return;
9906
9907      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9908      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9909      if(gameInfo.variant == VariantKnightmate)
9910          king += (int) WhiteUnicorn - (int) WhiteKing;
9911
9912     /* Code added by Tord: */
9913     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9914     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9915         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9916       board[fromY][fromX] = EmptySquare;
9917       board[toY][toX] = EmptySquare;
9918       if((toX > fromX) != (piece == WhiteRook)) {
9919         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9920       } else {
9921         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9922       }
9923     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9924                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9925       board[fromY][fromX] = EmptySquare;
9926       board[toY][toX] = EmptySquare;
9927       if((toX > fromX) != (piece == BlackRook)) {
9928         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9929       } else {
9930         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9931       }
9932     /* End of code added by Tord */
9933
9934     } else if (board[fromY][fromX] == king
9935         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9936         && toY == fromY && toX > fromX+1) {
9937         board[fromY][fromX] = EmptySquare;
9938         board[toY][toX] = king;
9939         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9940         board[fromY][BOARD_RGHT-1] = EmptySquare;
9941     } else if (board[fromY][fromX] == king
9942         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9943                && toY == fromY && toX < fromX-1) {
9944         board[fromY][fromX] = EmptySquare;
9945         board[toY][toX] = king;
9946         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9947         board[fromY][BOARD_LEFT] = EmptySquare;
9948     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9949                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9950                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9951                ) {
9952         /* white pawn promotion */
9953         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9954         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9955             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9956         board[fromY][fromX] = EmptySquare;
9957     } else if ((fromY >= BOARD_HEIGHT>>1)
9958                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9959                && (toX != fromX)
9960                && gameInfo.variant != VariantXiangqi
9961                && gameInfo.variant != VariantBerolina
9962                && (board[fromY][fromX] == WhitePawn)
9963                && (board[toY][toX] == EmptySquare)) {
9964         board[fromY][fromX] = EmptySquare;
9965         board[toY][toX] = WhitePawn;
9966         captured = board[toY - 1][toX];
9967         board[toY - 1][toX] = EmptySquare;
9968     } else if ((fromY == BOARD_HEIGHT-4)
9969                && (toX == fromX)
9970                && gameInfo.variant == VariantBerolina
9971                && (board[fromY][fromX] == WhitePawn)
9972                && (board[toY][toX] == EmptySquare)) {
9973         board[fromY][fromX] = EmptySquare;
9974         board[toY][toX] = WhitePawn;
9975         if(oldEP & EP_BEROLIN_A) {
9976                 captured = board[fromY][fromX-1];
9977                 board[fromY][fromX-1] = EmptySquare;
9978         }else{  captured = board[fromY][fromX+1];
9979                 board[fromY][fromX+1] = EmptySquare;
9980         }
9981     } else if (board[fromY][fromX] == king
9982         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9983                && toY == fromY && toX > fromX+1) {
9984         board[fromY][fromX] = EmptySquare;
9985         board[toY][toX] = king;
9986         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9987         board[fromY][BOARD_RGHT-1] = EmptySquare;
9988     } else if (board[fromY][fromX] == king
9989         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9990                && toY == fromY && toX < fromX-1) {
9991         board[fromY][fromX] = EmptySquare;
9992         board[toY][toX] = king;
9993         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9994         board[fromY][BOARD_LEFT] = EmptySquare;
9995     } else if (fromY == 7 && fromX == 3
9996                && board[fromY][fromX] == BlackKing
9997                && toY == 7 && toX == 5) {
9998         board[fromY][fromX] = EmptySquare;
9999         board[toY][toX] = BlackKing;
10000         board[fromY][7] = EmptySquare;
10001         board[toY][4] = BlackRook;
10002     } else if (fromY == 7 && fromX == 3
10003                && board[fromY][fromX] == BlackKing
10004                && toY == 7 && toX == 1) {
10005         board[fromY][fromX] = EmptySquare;
10006         board[toY][toX] = BlackKing;
10007         board[fromY][0] = EmptySquare;
10008         board[toY][2] = BlackRook;
10009     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10010                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10011                && toY < promoRank && promoChar
10012                ) {
10013         /* black pawn promotion */
10014         board[toY][toX] = CharToPiece(ToLower(promoChar));
10015         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10016             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10017         board[fromY][fromX] = EmptySquare;
10018     } else if ((fromY < BOARD_HEIGHT>>1)
10019                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10020                && (toX != fromX)
10021                && gameInfo.variant != VariantXiangqi
10022                && gameInfo.variant != VariantBerolina
10023                && (board[fromY][fromX] == BlackPawn)
10024                && (board[toY][toX] == EmptySquare)) {
10025         board[fromY][fromX] = EmptySquare;
10026         board[toY][toX] = BlackPawn;
10027         captured = board[toY + 1][toX];
10028         board[toY + 1][toX] = EmptySquare;
10029     } else if ((fromY == 3)
10030                && (toX == fromX)
10031                && gameInfo.variant == VariantBerolina
10032                && (board[fromY][fromX] == BlackPawn)
10033                && (board[toY][toX] == EmptySquare)) {
10034         board[fromY][fromX] = EmptySquare;
10035         board[toY][toX] = BlackPawn;
10036         if(oldEP & EP_BEROLIN_A) {
10037                 captured = board[fromY][fromX-1];
10038                 board[fromY][fromX-1] = EmptySquare;
10039         }else{  captured = board[fromY][fromX+1];
10040                 board[fromY][fromX+1] = EmptySquare;
10041         }
10042     } else {
10043         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10044         board[fromY][fromX] = EmptySquare;
10045         board[toY][toX] = piece;
10046     }
10047   }
10048
10049     if (gameInfo.holdingsWidth != 0) {
10050
10051       /* !!A lot more code needs to be written to support holdings  */
10052       /* [HGM] OK, so I have written it. Holdings are stored in the */
10053       /* penultimate board files, so they are automaticlly stored   */
10054       /* in the game history.                                       */
10055       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10056                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10057         /* Delete from holdings, by decreasing count */
10058         /* and erasing image if necessary            */
10059         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10060         if(p < (int) BlackPawn) { /* white drop */
10061              p -= (int)WhitePawn;
10062                  p = PieceToNumber((ChessSquare)p);
10063              if(p >= gameInfo.holdingsSize) p = 0;
10064              if(--board[p][BOARD_WIDTH-2] <= 0)
10065                   board[p][BOARD_WIDTH-1] = EmptySquare;
10066              if((int)board[p][BOARD_WIDTH-2] < 0)
10067                         board[p][BOARD_WIDTH-2] = 0;
10068         } else {                  /* black drop */
10069              p -= (int)BlackPawn;
10070                  p = PieceToNumber((ChessSquare)p);
10071              if(p >= gameInfo.holdingsSize) p = 0;
10072              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10073                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10074              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10075                         board[BOARD_HEIGHT-1-p][1] = 0;
10076         }
10077       }
10078       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10079           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10080         /* [HGM] holdings: Add to holdings, if holdings exist */
10081         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10082                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10083                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10084         }
10085         p = (int) captured;
10086         if (p >= (int) BlackPawn) {
10087           p -= (int)BlackPawn;
10088           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10089                   /* in Shogi restore piece to its original  first */
10090                   captured = (ChessSquare) (DEMOTED captured);
10091                   p = DEMOTED p;
10092           }
10093           p = PieceToNumber((ChessSquare)p);
10094           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10095           board[p][BOARD_WIDTH-2]++;
10096           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10097         } else {
10098           p -= (int)WhitePawn;
10099           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10100                   captured = (ChessSquare) (DEMOTED captured);
10101                   p = DEMOTED p;
10102           }
10103           p = PieceToNumber((ChessSquare)p);
10104           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10105           board[BOARD_HEIGHT-1-p][1]++;
10106           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10107         }
10108       }
10109     } else if (gameInfo.variant == VariantAtomic) {
10110       if (captured != EmptySquare) {
10111         int y, x;
10112         for (y = toY-1; y <= toY+1; y++) {
10113           for (x = toX-1; x <= toX+1; x++) {
10114             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10115                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10116               board[y][x] = EmptySquare;
10117             }
10118           }
10119         }
10120         board[toY][toX] = EmptySquare;
10121       }
10122     }
10123
10124     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10125         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10126     } else
10127     if(promoChar == '+') {
10128         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10129         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10130         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10131           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10132     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10133         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10134         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10135            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10136         board[toY][toX] = newPiece;
10137     }
10138     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10139                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10140         // [HGM] superchess: take promotion piece out of holdings
10141         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10142         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10143             if(!--board[k][BOARD_WIDTH-2])
10144                 board[k][BOARD_WIDTH-1] = EmptySquare;
10145         } else {
10146             if(!--board[BOARD_HEIGHT-1-k][1])
10147                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10148         }
10149     }
10150 }
10151
10152 /* Updates forwardMostMove */
10153 void
10154 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10155 {
10156     int x = toX, y = toY;
10157     char *s = parseList[forwardMostMove];
10158     ChessSquare p = boards[forwardMostMove][toY][toX];
10159 //    forwardMostMove++; // [HGM] bare: moved downstream
10160
10161     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10162     (void) CoordsToAlgebraic(boards[forwardMostMove],
10163                              PosFlags(forwardMostMove),
10164                              fromY, fromX, y, x, promoChar,
10165                              s);
10166     if(killX >= 0 && killY >= 0)
10167         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10168
10169     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10170         int timeLeft; static int lastLoadFlag=0; int king, piece;
10171         piece = boards[forwardMostMove][fromY][fromX];
10172         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10173         if(gameInfo.variant == VariantKnightmate)
10174             king += (int) WhiteUnicorn - (int) WhiteKing;
10175         if(forwardMostMove == 0) {
10176             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10177                 fprintf(serverMoves, "%s;", UserName());
10178             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10179                 fprintf(serverMoves, "%s;", second.tidy);
10180             fprintf(serverMoves, "%s;", first.tidy);
10181             if(gameMode == MachinePlaysWhite)
10182                 fprintf(serverMoves, "%s;", UserName());
10183             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10184                 fprintf(serverMoves, "%s;", second.tidy);
10185         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10186         lastLoadFlag = loadFlag;
10187         // print base move
10188         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10189         // print castling suffix
10190         if( toY == fromY && piece == king ) {
10191             if(toX-fromX > 1)
10192                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10193             if(fromX-toX >1)
10194                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10195         }
10196         // e.p. suffix
10197         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10198              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10199              boards[forwardMostMove][toY][toX] == EmptySquare
10200              && fromX != toX && fromY != toY)
10201                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10202         // promotion suffix
10203         if(promoChar != NULLCHAR) {
10204             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10205                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10206                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10207             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10208         }
10209         if(!loadFlag) {
10210                 char buf[MOVE_LEN*2], *p; int len;
10211             fprintf(serverMoves, "/%d/%d",
10212                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10213             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10214             else                      timeLeft = blackTimeRemaining/1000;
10215             fprintf(serverMoves, "/%d", timeLeft);
10216                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10217                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10218                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10219                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10220             fprintf(serverMoves, "/%s", buf);
10221         }
10222         fflush(serverMoves);
10223     }
10224
10225     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10226         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10227       return;
10228     }
10229     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10230     if (commentList[forwardMostMove+1] != NULL) {
10231         free(commentList[forwardMostMove+1]);
10232         commentList[forwardMostMove+1] = NULL;
10233     }
10234     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10235     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10236     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10237     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10238     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10239     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10240     adjustedClock = FALSE;
10241     gameInfo.result = GameUnfinished;
10242     if (gameInfo.resultDetails != NULL) {
10243         free(gameInfo.resultDetails);
10244         gameInfo.resultDetails = NULL;
10245     }
10246     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10247                               moveList[forwardMostMove - 1]);
10248     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10249       case MT_NONE:
10250       case MT_STALEMATE:
10251       default:
10252         break;
10253       case MT_CHECK:
10254         if(!IS_SHOGI(gameInfo.variant))
10255             strcat(parseList[forwardMostMove - 1], "+");
10256         break;
10257       case MT_CHECKMATE:
10258       case MT_STAINMATE:
10259         strcat(parseList[forwardMostMove - 1], "#");
10260         break;
10261     }
10262 }
10263
10264 /* Updates currentMove if not pausing */
10265 void
10266 ShowMove (int fromX, int fromY, int toX, int toY)
10267 {
10268     int instant = (gameMode == PlayFromGameFile) ?
10269         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10270     if(appData.noGUI) return;
10271     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10272         if (!instant) {
10273             if (forwardMostMove == currentMove + 1) {
10274                 AnimateMove(boards[forwardMostMove - 1],
10275                             fromX, fromY, toX, toY);
10276             }
10277         }
10278         currentMove = forwardMostMove;
10279     }
10280
10281     killX = killY = -1; // [HGM] lion: used up
10282
10283     if (instant) return;
10284
10285     DisplayMove(currentMove - 1);
10286     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10287             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10288                 SetHighlights(fromX, fromY, toX, toY);
10289             }
10290     }
10291     DrawPosition(FALSE, boards[currentMove]);
10292     DisplayBothClocks();
10293     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10294 }
10295
10296 void
10297 SendEgtPath (ChessProgramState *cps)
10298 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10299         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10300
10301         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10302
10303         while(*p) {
10304             char c, *q = name+1, *r, *s;
10305
10306             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10307             while(*p && *p != ',') *q++ = *p++;
10308             *q++ = ':'; *q = 0;
10309             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10310                 strcmp(name, ",nalimov:") == 0 ) {
10311                 // take nalimov path from the menu-changeable option first, if it is defined
10312               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10313                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10314             } else
10315             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10316                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10317                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10318                 s = r = StrStr(s, ":") + 1; // beginning of path info
10319                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10320                 c = *r; *r = 0;             // temporarily null-terminate path info
10321                     *--q = 0;               // strip of trailig ':' from name
10322                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10323                 *r = c;
10324                 SendToProgram(buf,cps);     // send egtbpath command for this format
10325             }
10326             if(*p == ',') p++; // read away comma to position for next format name
10327         }
10328 }
10329
10330 static int
10331 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10332 {
10333       int width = 8, height = 8, holdings = 0;             // most common sizes
10334       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10335       // correct the deviations default for each variant
10336       if( v == VariantXiangqi ) width = 9,  height = 10;
10337       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10338       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10339       if( v == VariantCapablanca || v == VariantCapaRandom ||
10340           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10341                                 width = 10;
10342       if( v == VariantCourier ) width = 12;
10343       if( v == VariantSuper )                            holdings = 8;
10344       if( v == VariantGreat )   width = 10,              holdings = 8;
10345       if( v == VariantSChess )                           holdings = 7;
10346       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10347       if( v == VariantChuChess) width = 10, height = 10;
10348       if( v == VariantChu )     width = 12, height = 12;
10349       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10350              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10351              holdingsSize >= 0 && holdingsSize != holdings;
10352 }
10353
10354 char variantError[MSG_SIZ];
10355
10356 char *
10357 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10358 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10359       char *p, *variant = VariantName(v);
10360       static char b[MSG_SIZ];
10361       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10362            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10363                                                holdingsSize, variant); // cook up sized variant name
10364            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10365            if(StrStr(list, b) == NULL) {
10366                // specific sized variant not known, check if general sizing allowed
10367                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10368                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10369                             boardWidth, boardHeight, holdingsSize, engine);
10370                    return NULL;
10371                }
10372                /* [HGM] here we really should compare with the maximum supported board size */
10373            }
10374       } else snprintf(b, MSG_SIZ,"%s", variant);
10375       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10376       p = StrStr(list, b);
10377       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10378       if(p == NULL) {
10379           // occurs not at all in list, or only as sub-string
10380           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10381           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10382               int l = strlen(variantError);
10383               char *q;
10384               while(p != list && p[-1] != ',') p--;
10385               q = strchr(p, ',');
10386               if(q) *q = NULLCHAR;
10387               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10388               if(q) *q= ',';
10389           }
10390           return NULL;
10391       }
10392       return b;
10393 }
10394
10395 void
10396 InitChessProgram (ChessProgramState *cps, int setup)
10397 /* setup needed to setup FRC opening position */
10398 {
10399     char buf[MSG_SIZ], *b;
10400     if (appData.noChessProgram) return;
10401     hintRequested = FALSE;
10402     bookRequested = FALSE;
10403
10404     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10405     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10406     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10407     if(cps->memSize) { /* [HGM] memory */
10408       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10409         SendToProgram(buf, cps);
10410     }
10411     SendEgtPath(cps); /* [HGM] EGT */
10412     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10413       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10414         SendToProgram(buf, cps);
10415     }
10416
10417     SendToProgram(cps->initString, cps);
10418     if (gameInfo.variant != VariantNormal &&
10419         gameInfo.variant != VariantLoadable
10420         /* [HGM] also send variant if board size non-standard */
10421         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10422
10423       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10424                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10425       if (b == NULL) {
10426         DisplayFatalError(variantError, 0, 1);
10427         return;
10428       }
10429
10430       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10431       SendToProgram(buf, cps);
10432     }
10433     currentlyInitializedVariant = gameInfo.variant;
10434
10435     /* [HGM] send opening position in FRC to first engine */
10436     if(setup) {
10437           SendToProgram("force\n", cps);
10438           SendBoard(cps, 0);
10439           /* engine is now in force mode! Set flag to wake it up after first move. */
10440           setboardSpoiledMachineBlack = 1;
10441     }
10442
10443     if (cps->sendICS) {
10444       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10445       SendToProgram(buf, cps);
10446     }
10447     cps->maybeThinking = FALSE;
10448     cps->offeredDraw = 0;
10449     if (!appData.icsActive) {
10450         SendTimeControl(cps, movesPerSession, timeControl,
10451                         timeIncrement, appData.searchDepth,
10452                         searchTime);
10453     }
10454     if (appData.showThinking
10455         // [HGM] thinking: four options require thinking output to be sent
10456         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10457                                 ) {
10458         SendToProgram("post\n", cps);
10459     }
10460     SendToProgram("hard\n", cps);
10461     if (!appData.ponderNextMove) {
10462         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10463            it without being sure what state we are in first.  "hard"
10464            is not a toggle, so that one is OK.
10465          */
10466         SendToProgram("easy\n", cps);
10467     }
10468     if (cps->usePing) {
10469       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10470       SendToProgram(buf, cps);
10471     }
10472     cps->initDone = TRUE;
10473     ClearEngineOutputPane(cps == &second);
10474 }
10475
10476
10477 void
10478 ResendOptions (ChessProgramState *cps)
10479 { // send the stored value of the options
10480   int i;
10481   char buf[MSG_SIZ];
10482   Option *opt = cps->option;
10483   for(i=0; i<cps->nrOptions; i++, opt++) {
10484       switch(opt->type) {
10485         case Spin:
10486         case Slider:
10487         case CheckBox:
10488             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10489           break;
10490         case ComboBox:
10491           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10492           break;
10493         default:
10494             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10495           break;
10496         case Button:
10497         case SaveButton:
10498           continue;
10499       }
10500       SendToProgram(buf, cps);
10501   }
10502 }
10503
10504 void
10505 StartChessProgram (ChessProgramState *cps)
10506 {
10507     char buf[MSG_SIZ];
10508     int err;
10509
10510     if (appData.noChessProgram) return;
10511     cps->initDone = FALSE;
10512
10513     if (strcmp(cps->host, "localhost") == 0) {
10514         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10515     } else if (*appData.remoteShell == NULLCHAR) {
10516         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10517     } else {
10518         if (*appData.remoteUser == NULLCHAR) {
10519           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10520                     cps->program);
10521         } else {
10522           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10523                     cps->host, appData.remoteUser, cps->program);
10524         }
10525         err = StartChildProcess(buf, "", &cps->pr);
10526     }
10527
10528     if (err != 0) {
10529       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10530         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10531         if(cps != &first) return;
10532         appData.noChessProgram = TRUE;
10533         ThawUI();
10534         SetNCPMode();
10535 //      DisplayFatalError(buf, err, 1);
10536 //      cps->pr = NoProc;
10537 //      cps->isr = NULL;
10538         return;
10539     }
10540
10541     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10542     if (cps->protocolVersion > 1) {
10543       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10544       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10545         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10546         cps->comboCnt = 0;  //                and values of combo boxes
10547       }
10548       SendToProgram(buf, cps);
10549       if(cps->reload) ResendOptions(cps);
10550     } else {
10551       SendToProgram("xboard\n", cps);
10552     }
10553 }
10554
10555 void
10556 TwoMachinesEventIfReady P((void))
10557 {
10558   static int curMess = 0;
10559   if (first.lastPing != first.lastPong) {
10560     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10561     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10562     return;
10563   }
10564   if (second.lastPing != second.lastPong) {
10565     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10566     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10567     return;
10568   }
10569   DisplayMessage("", ""); curMess = 0;
10570   TwoMachinesEvent();
10571 }
10572
10573 char *
10574 MakeName (char *template)
10575 {
10576     time_t clock;
10577     struct tm *tm;
10578     static char buf[MSG_SIZ];
10579     char *p = buf;
10580     int i;
10581
10582     clock = time((time_t *)NULL);
10583     tm = localtime(&clock);
10584
10585     while(*p++ = *template++) if(p[-1] == '%') {
10586         switch(*template++) {
10587           case 0:   *p = 0; return buf;
10588           case 'Y': i = tm->tm_year+1900; break;
10589           case 'y': i = tm->tm_year-100; break;
10590           case 'M': i = tm->tm_mon+1; break;
10591           case 'd': i = tm->tm_mday; break;
10592           case 'h': i = tm->tm_hour; break;
10593           case 'm': i = tm->tm_min; break;
10594           case 's': i = tm->tm_sec; break;
10595           default:  i = 0;
10596         }
10597         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10598     }
10599     return buf;
10600 }
10601
10602 int
10603 CountPlayers (char *p)
10604 {
10605     int n = 0;
10606     while(p = strchr(p, '\n')) p++, n++; // count participants
10607     return n;
10608 }
10609
10610 FILE *
10611 WriteTourneyFile (char *results, FILE *f)
10612 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10613     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10614     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10615         // create a file with tournament description
10616         fprintf(f, "-participants {%s}\n", appData.participants);
10617         fprintf(f, "-seedBase %d\n", appData.seedBase);
10618         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10619         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10620         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10621         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10622         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10623         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10624         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10625         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10626         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10627         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10628         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10629         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10630         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10631         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10632         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10633         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10634         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10635         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10636         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10637         fprintf(f, "-smpCores %d\n", appData.smpCores);
10638         if(searchTime > 0)
10639                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10640         else {
10641                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10642                 fprintf(f, "-tc %s\n", appData.timeControl);
10643                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10644         }
10645         fprintf(f, "-results \"%s\"\n", results);
10646     }
10647     return f;
10648 }
10649
10650 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10651
10652 void
10653 Substitute (char *participants, int expunge)
10654 {
10655     int i, changed, changes=0, nPlayers=0;
10656     char *p, *q, *r, buf[MSG_SIZ];
10657     if(participants == NULL) return;
10658     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10659     r = p = participants; q = appData.participants;
10660     while(*p && *p == *q) {
10661         if(*p == '\n') r = p+1, nPlayers++;
10662         p++; q++;
10663     }
10664     if(*p) { // difference
10665         while(*p && *p++ != '\n');
10666         while(*q && *q++ != '\n');
10667       changed = nPlayers;
10668         changes = 1 + (strcmp(p, q) != 0);
10669     }
10670     if(changes == 1) { // a single engine mnemonic was changed
10671         q = r; while(*q) nPlayers += (*q++ == '\n');
10672         p = buf; while(*r && (*p = *r++) != '\n') p++;
10673         *p = NULLCHAR;
10674         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10675         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10676         if(mnemonic[i]) { // The substitute is valid
10677             FILE *f;
10678             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10679                 flock(fileno(f), LOCK_EX);
10680                 ParseArgsFromFile(f);
10681                 fseek(f, 0, SEEK_SET);
10682                 FREE(appData.participants); appData.participants = participants;
10683                 if(expunge) { // erase results of replaced engine
10684                     int len = strlen(appData.results), w, b, dummy;
10685                     for(i=0; i<len; i++) {
10686                         Pairing(i, nPlayers, &w, &b, &dummy);
10687                         if((w == changed || b == changed) && appData.results[i] == '*') {
10688                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10689                             fclose(f);
10690                             return;
10691                         }
10692                     }
10693                     for(i=0; i<len; i++) {
10694                         Pairing(i, nPlayers, &w, &b, &dummy);
10695                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10696                     }
10697                 }
10698                 WriteTourneyFile(appData.results, f);
10699                 fclose(f); // release lock
10700                 return;
10701             }
10702         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10703     }
10704     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10705     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10706     free(participants);
10707     return;
10708 }
10709
10710 int
10711 CheckPlayers (char *participants)
10712 {
10713         int i;
10714         char buf[MSG_SIZ], *p;
10715         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10716         while(p = strchr(participants, '\n')) {
10717             *p = NULLCHAR;
10718             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10719             if(!mnemonic[i]) {
10720                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10721                 *p = '\n';
10722                 DisplayError(buf, 0);
10723                 return 1;
10724             }
10725             *p = '\n';
10726             participants = p + 1;
10727         }
10728         return 0;
10729 }
10730
10731 int
10732 CreateTourney (char *name)
10733 {
10734         FILE *f;
10735         if(matchMode && strcmp(name, appData.tourneyFile)) {
10736              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10737         }
10738         if(name[0] == NULLCHAR) {
10739             if(appData.participants[0])
10740                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10741             return 0;
10742         }
10743         f = fopen(name, "r");
10744         if(f) { // file exists
10745             ASSIGN(appData.tourneyFile, name);
10746             ParseArgsFromFile(f); // parse it
10747         } else {
10748             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10749             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10750                 DisplayError(_("Not enough participants"), 0);
10751                 return 0;
10752             }
10753             if(CheckPlayers(appData.participants)) return 0;
10754             ASSIGN(appData.tourneyFile, name);
10755             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10756             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10757         }
10758         fclose(f);
10759         appData.noChessProgram = FALSE;
10760         appData.clockMode = TRUE;
10761         SetGNUMode();
10762         return 1;
10763 }
10764
10765 int
10766 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10767 {
10768     char buf[MSG_SIZ], *p, *q;
10769     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10770     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10771     skip = !all && group[0]; // if group requested, we start in skip mode
10772     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10773         p = names; q = buf; header = 0;
10774         while(*p && *p != '\n') *q++ = *p++;
10775         *q = 0;
10776         if(*p == '\n') p++;
10777         if(buf[0] == '#') {
10778             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10779             depth++; // we must be entering a new group
10780             if(all) continue; // suppress printing group headers when complete list requested
10781             header = 1;
10782             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10783         }
10784         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10785         if(engineList[i]) free(engineList[i]);
10786         engineList[i] = strdup(buf);
10787         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10788         if(engineMnemonic[i]) free(engineMnemonic[i]);
10789         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10790             strcat(buf, " (");
10791             sscanf(q + 8, "%s", buf + strlen(buf));
10792             strcat(buf, ")");
10793         }
10794         engineMnemonic[i] = strdup(buf);
10795         i++;
10796     }
10797     engineList[i] = engineMnemonic[i] = NULL;
10798     return i;
10799 }
10800
10801 // following implemented as macro to avoid type limitations
10802 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10803
10804 void
10805 SwapEngines (int n)
10806 {   // swap settings for first engine and other engine (so far only some selected options)
10807     int h;
10808     char *p;
10809     if(n == 0) return;
10810     SWAP(directory, p)
10811     SWAP(chessProgram, p)
10812     SWAP(isUCI, h)
10813     SWAP(hasOwnBookUCI, h)
10814     SWAP(protocolVersion, h)
10815     SWAP(reuse, h)
10816     SWAP(scoreIsAbsolute, h)
10817     SWAP(timeOdds, h)
10818     SWAP(logo, p)
10819     SWAP(pgnName, p)
10820     SWAP(pvSAN, h)
10821     SWAP(engOptions, p)
10822     SWAP(engInitString, p)
10823     SWAP(computerString, p)
10824     SWAP(features, p)
10825     SWAP(fenOverride, p)
10826     SWAP(NPS, h)
10827     SWAP(accumulateTC, h)
10828     SWAP(drawDepth, h)
10829     SWAP(host, p)
10830 }
10831
10832 int
10833 GetEngineLine (char *s, int n)
10834 {
10835     int i;
10836     char buf[MSG_SIZ];
10837     extern char *icsNames;
10838     if(!s || !*s) return 0;
10839     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10840     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10841     if(!mnemonic[i]) return 0;
10842     if(n == 11) return 1; // just testing if there was a match
10843     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10844     if(n == 1) SwapEngines(n);
10845     ParseArgsFromString(buf);
10846     if(n == 1) SwapEngines(n);
10847     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10848         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10849         ParseArgsFromString(buf);
10850     }
10851     return 1;
10852 }
10853
10854 int
10855 SetPlayer (int player, char *p)
10856 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10857     int i;
10858     char buf[MSG_SIZ], *engineName;
10859     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10860     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10861     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10862     if(mnemonic[i]) {
10863         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10864         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10865         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10866         ParseArgsFromString(buf);
10867     } else { // no engine with this nickname is installed!
10868         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10869         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10870         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10871         ModeHighlight();
10872         DisplayError(buf, 0);
10873         return 0;
10874     }
10875     free(engineName);
10876     return i;
10877 }
10878
10879 char *recentEngines;
10880
10881 void
10882 RecentEngineEvent (int nr)
10883 {
10884     int n;
10885 //    SwapEngines(1); // bump first to second
10886 //    ReplaceEngine(&second, 1); // and load it there
10887     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10888     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10889     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10890         ReplaceEngine(&first, 0);
10891         FloatToFront(&appData.recentEngineList, command[n]);
10892     }
10893 }
10894
10895 int
10896 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10897 {   // determine players from game number
10898     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10899
10900     if(appData.tourneyType == 0) {
10901         roundsPerCycle = (nPlayers - 1) | 1;
10902         pairingsPerRound = nPlayers / 2;
10903     } else if(appData.tourneyType > 0) {
10904         roundsPerCycle = nPlayers - appData.tourneyType;
10905         pairingsPerRound = appData.tourneyType;
10906     }
10907     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10908     gamesPerCycle = gamesPerRound * roundsPerCycle;
10909     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10910     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10911     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10912     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10913     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10914     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10915
10916     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10917     if(appData.roundSync) *syncInterval = gamesPerRound;
10918
10919     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10920
10921     if(appData.tourneyType == 0) {
10922         if(curPairing == (nPlayers-1)/2 ) {
10923             *whitePlayer = curRound;
10924             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10925         } else {
10926             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10927             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10928             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10929             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10930         }
10931     } else if(appData.tourneyType > 1) {
10932         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10933         *whitePlayer = curRound + appData.tourneyType;
10934     } else if(appData.tourneyType > 0) {
10935         *whitePlayer = curPairing;
10936         *blackPlayer = curRound + appData.tourneyType;
10937     }
10938
10939     // take care of white/black alternation per round.
10940     // For cycles and games this is already taken care of by default, derived from matchGame!
10941     return curRound & 1;
10942 }
10943
10944 int
10945 NextTourneyGame (int nr, int *swapColors)
10946 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10947     char *p, *q;
10948     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10949     FILE *tf;
10950     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10951     tf = fopen(appData.tourneyFile, "r");
10952     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10953     ParseArgsFromFile(tf); fclose(tf);
10954     InitTimeControls(); // TC might be altered from tourney file
10955
10956     nPlayers = CountPlayers(appData.participants); // count participants
10957     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10958     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10959
10960     if(syncInterval) {
10961         p = q = appData.results;
10962         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10963         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10964             DisplayMessage(_("Waiting for other game(s)"),"");
10965             waitingForGame = TRUE;
10966             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10967             return 0;
10968         }
10969         waitingForGame = FALSE;
10970     }
10971
10972     if(appData.tourneyType < 0) {
10973         if(nr>=0 && !pairingReceived) {
10974             char buf[1<<16];
10975             if(pairing.pr == NoProc) {
10976                 if(!appData.pairingEngine[0]) {
10977                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10978                     return 0;
10979                 }
10980                 StartChessProgram(&pairing); // starts the pairing engine
10981             }
10982             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10983             SendToProgram(buf, &pairing);
10984             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10985             SendToProgram(buf, &pairing);
10986             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10987         }
10988         pairingReceived = 0;                              // ... so we continue here
10989         *swapColors = 0;
10990         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10991         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10992         matchGame = 1; roundNr = nr / syncInterval + 1;
10993     }
10994
10995     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10996
10997     // redefine engines, engine dir, etc.
10998     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10999     if(first.pr == NoProc) {
11000       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11001       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11002     }
11003     if(second.pr == NoProc) {
11004       SwapEngines(1);
11005       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11006       SwapEngines(1);         // and make that valid for second engine by swapping
11007       InitEngine(&second, 1);
11008     }
11009     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11010     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11011     return OK;
11012 }
11013
11014 void
11015 NextMatchGame ()
11016 {   // performs game initialization that does not invoke engines, and then tries to start the game
11017     int res, firstWhite, swapColors = 0;
11018     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11019     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
11020         char buf[MSG_SIZ];
11021         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11022         if(strcmp(buf, currentDebugFile)) { // name has changed
11023             FILE *f = fopen(buf, "w");
11024             if(f) { // if opening the new file failed, just keep using the old one
11025                 ASSIGN(currentDebugFile, buf);
11026                 fclose(debugFP);
11027                 debugFP = f;
11028             }
11029             if(appData.serverFileName) {
11030                 if(serverFP) fclose(serverFP);
11031                 serverFP = fopen(appData.serverFileName, "w");
11032                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11033                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11034             }
11035         }
11036     }
11037     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11038     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11039     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11040     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11041     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11042     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11043     Reset(FALSE, first.pr != NoProc);
11044     res = LoadGameOrPosition(matchGame); // setup game
11045     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11046     if(!res) return; // abort when bad game/pos file
11047     TwoMachinesEvent();
11048 }
11049
11050 void
11051 UserAdjudicationEvent (int result)
11052 {
11053     ChessMove gameResult = GameIsDrawn;
11054
11055     if( result > 0 ) {
11056         gameResult = WhiteWins;
11057     }
11058     else if( result < 0 ) {
11059         gameResult = BlackWins;
11060     }
11061
11062     if( gameMode == TwoMachinesPlay ) {
11063         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11064     }
11065 }
11066
11067
11068 // [HGM] save: calculate checksum of game to make games easily identifiable
11069 int
11070 StringCheckSum (char *s)
11071 {
11072         int i = 0;
11073         if(s==NULL) return 0;
11074         while(*s) i = i*259 + *s++;
11075         return i;
11076 }
11077
11078 int
11079 GameCheckSum ()
11080 {
11081         int i, sum=0;
11082         for(i=backwardMostMove; i<forwardMostMove; i++) {
11083                 sum += pvInfoList[i].depth;
11084                 sum += StringCheckSum(parseList[i]);
11085                 sum += StringCheckSum(commentList[i]);
11086                 sum *= 261;
11087         }
11088         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11089         return sum + StringCheckSum(commentList[i]);
11090 } // end of save patch
11091
11092 void
11093 GameEnds (ChessMove result, char *resultDetails, int whosays)
11094 {
11095     GameMode nextGameMode;
11096     int isIcsGame;
11097     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11098
11099     if(endingGame) return; /* [HGM] crash: forbid recursion */
11100     endingGame = 1;
11101     if(twoBoards) { // [HGM] dual: switch back to one board
11102         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11103         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11104     }
11105     if (appData.debugMode) {
11106       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11107               result, resultDetails ? resultDetails : "(null)", whosays);
11108     }
11109
11110     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11111
11112     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11113
11114     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11115         /* If we are playing on ICS, the server decides when the
11116            game is over, but the engine can offer to draw, claim
11117            a draw, or resign.
11118          */
11119 #if ZIPPY
11120         if (appData.zippyPlay && first.initDone) {
11121             if (result == GameIsDrawn) {
11122                 /* In case draw still needs to be claimed */
11123                 SendToICS(ics_prefix);
11124                 SendToICS("draw\n");
11125             } else if (StrCaseStr(resultDetails, "resign")) {
11126                 SendToICS(ics_prefix);
11127                 SendToICS("resign\n");
11128             }
11129         }
11130 #endif
11131         endingGame = 0; /* [HGM] crash */
11132         return;
11133     }
11134
11135     /* If we're loading the game from a file, stop */
11136     if (whosays == GE_FILE) {
11137       (void) StopLoadGameTimer();
11138       gameFileFP = NULL;
11139     }
11140
11141     /* Cancel draw offers */
11142     first.offeredDraw = second.offeredDraw = 0;
11143
11144     /* If this is an ICS game, only ICS can really say it's done;
11145        if not, anyone can. */
11146     isIcsGame = (gameMode == IcsPlayingWhite ||
11147                  gameMode == IcsPlayingBlack ||
11148                  gameMode == IcsObserving    ||
11149                  gameMode == IcsExamining);
11150
11151     if (!isIcsGame || whosays == GE_ICS) {
11152         /* OK -- not an ICS game, or ICS said it was done */
11153         StopClocks();
11154         if (!isIcsGame && !appData.noChessProgram)
11155           SetUserThinkingEnables();
11156
11157         /* [HGM] if a machine claims the game end we verify this claim */
11158         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11159             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11160                 char claimer;
11161                 ChessMove trueResult = (ChessMove) -1;
11162
11163                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11164                                             first.twoMachinesColor[0] :
11165                                             second.twoMachinesColor[0] ;
11166
11167                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11168                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11169                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11170                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11171                 } else
11172                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11173                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11174                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11175                 } else
11176                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11177                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11178                 }
11179
11180                 // now verify win claims, but not in drop games, as we don't understand those yet
11181                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11182                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11183                     (result == WhiteWins && claimer == 'w' ||
11184                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11185                       if (appData.debugMode) {
11186                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11187                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11188                       }
11189                       if(result != trueResult) {
11190                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11191                               result = claimer == 'w' ? BlackWins : WhiteWins;
11192                               resultDetails = buf;
11193                       }
11194                 } else
11195                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11196                     && (forwardMostMove <= backwardMostMove ||
11197                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11198                         (claimer=='b')==(forwardMostMove&1))
11199                                                                                   ) {
11200                       /* [HGM] verify: draws that were not flagged are false claims */
11201                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11202                       result = claimer == 'w' ? BlackWins : WhiteWins;
11203                       resultDetails = buf;
11204                 }
11205                 /* (Claiming a loss is accepted no questions asked!) */
11206             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11207                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11208                 result = GameUnfinished;
11209                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11210             }
11211             /* [HGM] bare: don't allow bare King to win */
11212             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11213                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11214                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11215                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11216                && result != GameIsDrawn)
11217             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11218                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11219                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11220                         if(p >= 0 && p <= (int)WhiteKing) k++;
11221                 }
11222                 if (appData.debugMode) {
11223                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11224                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11225                 }
11226                 if(k <= 1) {
11227                         result = GameIsDrawn;
11228                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11229                         resultDetails = buf;
11230                 }
11231             }
11232         }
11233
11234
11235         if(serverMoves != NULL && !loadFlag) { char c = '=';
11236             if(result==WhiteWins) c = '+';
11237             if(result==BlackWins) c = '-';
11238             if(resultDetails != NULL)
11239                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11240         }
11241         if (resultDetails != NULL) {
11242             gameInfo.result = result;
11243             gameInfo.resultDetails = StrSave(resultDetails);
11244
11245             /* display last move only if game was not loaded from file */
11246             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11247                 DisplayMove(currentMove - 1);
11248
11249             if (forwardMostMove != 0) {
11250                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11251                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11252                                                                 ) {
11253                     if (*appData.saveGameFile != NULLCHAR) {
11254                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11255                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11256                         else
11257                         SaveGameToFile(appData.saveGameFile, TRUE);
11258                     } else if (appData.autoSaveGames) {
11259                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11260                     }
11261                     if (*appData.savePositionFile != NULLCHAR) {
11262                         SavePositionToFile(appData.savePositionFile);
11263                     }
11264                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11265                 }
11266             }
11267
11268             /* Tell program how game ended in case it is learning */
11269             /* [HGM] Moved this to after saving the PGN, just in case */
11270             /* engine died and we got here through time loss. In that */
11271             /* case we will get a fatal error writing the pipe, which */
11272             /* would otherwise lose us the PGN.                       */
11273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11274             /* output during GameEnds should never be fatal anymore   */
11275             if (gameMode == MachinePlaysWhite ||
11276                 gameMode == MachinePlaysBlack ||
11277                 gameMode == TwoMachinesPlay ||
11278                 gameMode == IcsPlayingWhite ||
11279                 gameMode == IcsPlayingBlack ||
11280                 gameMode == BeginningOfGame) {
11281                 char buf[MSG_SIZ];
11282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11283                         resultDetails);
11284                 if (first.pr != NoProc) {
11285                     SendToProgram(buf, &first);
11286                 }
11287                 if (second.pr != NoProc &&
11288                     gameMode == TwoMachinesPlay) {
11289                     SendToProgram(buf, &second);
11290                 }
11291             }
11292         }
11293
11294         if (appData.icsActive) {
11295             if (appData.quietPlay &&
11296                 (gameMode == IcsPlayingWhite ||
11297                  gameMode == IcsPlayingBlack)) {
11298                 SendToICS(ics_prefix);
11299                 SendToICS("set shout 1\n");
11300             }
11301             nextGameMode = IcsIdle;
11302             ics_user_moved = FALSE;
11303             /* clean up premove.  It's ugly when the game has ended and the
11304              * premove highlights are still on the board.
11305              */
11306             if (gotPremove) {
11307               gotPremove = FALSE;
11308               ClearPremoveHighlights();
11309               DrawPosition(FALSE, boards[currentMove]);
11310             }
11311             if (whosays == GE_ICS) {
11312                 switch (result) {
11313                 case WhiteWins:
11314                     if (gameMode == IcsPlayingWhite)
11315                         PlayIcsWinSound();
11316                     else if(gameMode == IcsPlayingBlack)
11317                         PlayIcsLossSound();
11318                     break;
11319                 case BlackWins:
11320                     if (gameMode == IcsPlayingBlack)
11321                         PlayIcsWinSound();
11322                     else if(gameMode == IcsPlayingWhite)
11323                         PlayIcsLossSound();
11324                     break;
11325                 case GameIsDrawn:
11326                     PlayIcsDrawSound();
11327                     break;
11328                 default:
11329                     PlayIcsUnfinishedSound();
11330                 }
11331             }
11332             if(appData.quitNext) { ExitEvent(0); return; }
11333         } else if (gameMode == EditGame ||
11334                    gameMode == PlayFromGameFile ||
11335                    gameMode == AnalyzeMode ||
11336                    gameMode == AnalyzeFile) {
11337             nextGameMode = gameMode;
11338         } else {
11339             nextGameMode = EndOfGame;
11340         }
11341         pausing = FALSE;
11342         ModeHighlight();
11343     } else {
11344         nextGameMode = gameMode;
11345     }
11346
11347     if (appData.noChessProgram) {
11348         gameMode = nextGameMode;
11349         ModeHighlight();
11350         endingGame = 0; /* [HGM] crash */
11351         return;
11352     }
11353
11354     if (first.reuse) {
11355         /* Put first chess program into idle state */
11356         if (first.pr != NoProc &&
11357             (gameMode == MachinePlaysWhite ||
11358              gameMode == MachinePlaysBlack ||
11359              gameMode == TwoMachinesPlay ||
11360              gameMode == IcsPlayingWhite ||
11361              gameMode == IcsPlayingBlack ||
11362              gameMode == BeginningOfGame)) {
11363             SendToProgram("force\n", &first);
11364             if (first.usePing) {
11365               char buf[MSG_SIZ];
11366               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11367               SendToProgram(buf, &first);
11368             }
11369         }
11370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11371         /* Kill off first chess program */
11372         if (first.isr != NULL)
11373           RemoveInputSource(first.isr);
11374         first.isr = NULL;
11375
11376         if (first.pr != NoProc) {
11377             ExitAnalyzeMode();
11378             DoSleep( appData.delayBeforeQuit );
11379             SendToProgram("quit\n", &first);
11380             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11381             first.reload = TRUE;
11382         }
11383         first.pr = NoProc;
11384     }
11385     if (second.reuse) {
11386         /* Put second chess program into idle state */
11387         if (second.pr != NoProc &&
11388             gameMode == TwoMachinesPlay) {
11389             SendToProgram("force\n", &second);
11390             if (second.usePing) {
11391               char buf[MSG_SIZ];
11392               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11393               SendToProgram(buf, &second);
11394             }
11395         }
11396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11397         /* Kill off second chess program */
11398         if (second.isr != NULL)
11399           RemoveInputSource(second.isr);
11400         second.isr = NULL;
11401
11402         if (second.pr != NoProc) {
11403             DoSleep( appData.delayBeforeQuit );
11404             SendToProgram("quit\n", &second);
11405             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11406             second.reload = TRUE;
11407         }
11408         second.pr = NoProc;
11409     }
11410
11411     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11412         char resChar = '=';
11413         switch (result) {
11414         case WhiteWins:
11415           resChar = '+';
11416           if (first.twoMachinesColor[0] == 'w') {
11417             first.matchWins++;
11418           } else {
11419             second.matchWins++;
11420           }
11421           break;
11422         case BlackWins:
11423           resChar = '-';
11424           if (first.twoMachinesColor[0] == 'b') {
11425             first.matchWins++;
11426           } else {
11427             second.matchWins++;
11428           }
11429           break;
11430         case GameUnfinished:
11431           resChar = ' ';
11432         default:
11433           break;
11434         }
11435
11436         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11437         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11438             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11439             ReserveGame(nextGame, resChar); // sets nextGame
11440             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11441             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11442         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11443
11444         if (nextGame <= appData.matchGames && !abortMatch) {
11445             gameMode = nextGameMode;
11446             matchGame = nextGame; // this will be overruled in tourney mode!
11447             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11448             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11449             endingGame = 0; /* [HGM] crash */
11450             return;
11451         } else {
11452             gameMode = nextGameMode;
11453             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11454                      first.tidy, second.tidy,
11455                      first.matchWins, second.matchWins,
11456                      appData.matchGames - (first.matchWins + second.matchWins));
11457             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11458             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11459             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11460             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11461                 first.twoMachinesColor = "black\n";
11462                 second.twoMachinesColor = "white\n";
11463             } else {
11464                 first.twoMachinesColor = "white\n";
11465                 second.twoMachinesColor = "black\n";
11466             }
11467         }
11468     }
11469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11470         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11471       ExitAnalyzeMode();
11472     gameMode = nextGameMode;
11473     ModeHighlight();
11474     endingGame = 0;  /* [HGM] crash */
11475     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11476         if(matchMode == TRUE) { // match through command line: exit with or without popup
11477             if(ranking) {
11478                 ToNrEvent(forwardMostMove);
11479                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11480                 else ExitEvent(0);
11481             } else DisplayFatalError(buf, 0, 0);
11482         } else { // match through menu; just stop, with or without popup
11483             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11484             ModeHighlight();
11485             if(ranking){
11486                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11487             } else DisplayNote(buf);
11488       }
11489       if(ranking) free(ranking);
11490     }
11491 }
11492
11493 /* Assumes program was just initialized (initString sent).
11494    Leaves program in force mode. */
11495 void
11496 FeedMovesToProgram (ChessProgramState *cps, int upto)
11497 {
11498     int i;
11499
11500     if (appData.debugMode)
11501       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11502               startedFromSetupPosition ? "position and " : "",
11503               backwardMostMove, upto, cps->which);
11504     if(currentlyInitializedVariant != gameInfo.variant) {
11505       char buf[MSG_SIZ];
11506         // [HGM] variantswitch: make engine aware of new variant
11507         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11508                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11509                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11510         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11511         SendToProgram(buf, cps);
11512         currentlyInitializedVariant = gameInfo.variant;
11513     }
11514     SendToProgram("force\n", cps);
11515     if (startedFromSetupPosition) {
11516         SendBoard(cps, backwardMostMove);
11517     if (appData.debugMode) {
11518         fprintf(debugFP, "feedMoves\n");
11519     }
11520     }
11521     for (i = backwardMostMove; i < upto; i++) {
11522         SendMoveToProgram(i, cps);
11523     }
11524 }
11525
11526
11527 int
11528 ResurrectChessProgram ()
11529 {
11530      /* The chess program may have exited.
11531         If so, restart it and feed it all the moves made so far. */
11532     static int doInit = 0;
11533
11534     if (appData.noChessProgram) return 1;
11535
11536     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11537         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11538         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11539         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11540     } else {
11541         if (first.pr != NoProc) return 1;
11542         StartChessProgram(&first);
11543     }
11544     InitChessProgram(&first, FALSE);
11545     FeedMovesToProgram(&first, currentMove);
11546
11547     if (!first.sendTime) {
11548         /* can't tell gnuchess what its clock should read,
11549            so we bow to its notion. */
11550         ResetClocks();
11551         timeRemaining[0][currentMove] = whiteTimeRemaining;
11552         timeRemaining[1][currentMove] = blackTimeRemaining;
11553     }
11554
11555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11556                 appData.icsEngineAnalyze) && first.analysisSupport) {
11557       SendToProgram("analyze\n", &first);
11558       first.analyzing = TRUE;
11559     }
11560     return 1;
11561 }
11562
11563 /*
11564  * Button procedures
11565  */
11566 void
11567 Reset (int redraw, int init)
11568 {
11569     int i;
11570
11571     if (appData.debugMode) {
11572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11573                 redraw, init, gameMode);
11574     }
11575     CleanupTail(); // [HGM] vari: delete any stored variations
11576     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11577     pausing = pauseExamInvalid = FALSE;
11578     startedFromSetupPosition = blackPlaysFirst = FALSE;
11579     firstMove = TRUE;
11580     whiteFlag = blackFlag = FALSE;
11581     userOfferedDraw = FALSE;
11582     hintRequested = bookRequested = FALSE;
11583     first.maybeThinking = FALSE;
11584     second.maybeThinking = FALSE;
11585     first.bookSuspend = FALSE; // [HGM] book
11586     second.bookSuspend = FALSE;
11587     thinkOutput[0] = NULLCHAR;
11588     lastHint[0] = NULLCHAR;
11589     ClearGameInfo(&gameInfo);
11590     gameInfo.variant = StringToVariant(appData.variant);
11591     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11592     ics_user_moved = ics_clock_paused = FALSE;
11593     ics_getting_history = H_FALSE;
11594     ics_gamenum = -1;
11595     white_holding[0] = black_holding[0] = NULLCHAR;
11596     ClearProgramStats();
11597     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11598
11599     ResetFrontEnd();
11600     ClearHighlights();
11601     flipView = appData.flipView;
11602     ClearPremoveHighlights();
11603     gotPremove = FALSE;
11604     alarmSounded = FALSE;
11605     killX = killY = -1; // [HGM] lion
11606
11607     GameEnds(EndOfFile, NULL, GE_PLAYER);
11608     if(appData.serverMovesName != NULL) {
11609         /* [HGM] prepare to make moves file for broadcasting */
11610         clock_t t = clock();
11611         if(serverMoves != NULL) fclose(serverMoves);
11612         serverMoves = fopen(appData.serverMovesName, "r");
11613         if(serverMoves != NULL) {
11614             fclose(serverMoves);
11615             /* delay 15 sec before overwriting, so all clients can see end */
11616             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11617         }
11618         serverMoves = fopen(appData.serverMovesName, "w");
11619     }
11620
11621     ExitAnalyzeMode();
11622     gameMode = BeginningOfGame;
11623     ModeHighlight();
11624     if(appData.icsActive) gameInfo.variant = VariantNormal;
11625     currentMove = forwardMostMove = backwardMostMove = 0;
11626     MarkTargetSquares(1);
11627     InitPosition(redraw);
11628     for (i = 0; i < MAX_MOVES; i++) {
11629         if (commentList[i] != NULL) {
11630             free(commentList[i]);
11631             commentList[i] = NULL;
11632         }
11633     }
11634     ResetClocks();
11635     timeRemaining[0][0] = whiteTimeRemaining;
11636     timeRemaining[1][0] = blackTimeRemaining;
11637
11638     if (first.pr == NoProc) {
11639         StartChessProgram(&first);
11640     }
11641     if (init) {
11642             InitChessProgram(&first, startedFromSetupPosition);
11643     }
11644     DisplayTitle("");
11645     DisplayMessage("", "");
11646     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11647     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11648     ClearMap();        // [HGM] exclude: invalidate map
11649 }
11650
11651 void
11652 AutoPlayGameLoop ()
11653 {
11654     for (;;) {
11655         if (!AutoPlayOneMove())
11656           return;
11657         if (matchMode || appData.timeDelay == 0)
11658           continue;
11659         if (appData.timeDelay < 0)
11660           return;
11661         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11662         break;
11663     }
11664 }
11665
11666 void
11667 AnalyzeNextGame()
11668 {
11669     ReloadGame(1); // next game
11670 }
11671
11672 int
11673 AutoPlayOneMove ()
11674 {
11675     int fromX, fromY, toX, toY;
11676
11677     if (appData.debugMode) {
11678       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11679     }
11680
11681     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11682       return FALSE;
11683
11684     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11685       pvInfoList[currentMove].depth = programStats.depth;
11686       pvInfoList[currentMove].score = programStats.score;
11687       pvInfoList[currentMove].time  = 0;
11688       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11689       else { // append analysis of final position as comment
11690         char buf[MSG_SIZ];
11691         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11692         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11693       }
11694       programStats.depth = 0;
11695     }
11696
11697     if (currentMove >= forwardMostMove) {
11698       if(gameMode == AnalyzeFile) {
11699           if(appData.loadGameIndex == -1) {
11700             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11701           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11702           } else {
11703           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11704         }
11705       }
11706 //      gameMode = EndOfGame;
11707 //      ModeHighlight();
11708
11709       /* [AS] Clear current move marker at the end of a game */
11710       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11711
11712       return FALSE;
11713     }
11714
11715     toX = moveList[currentMove][2] - AAA;
11716     toY = moveList[currentMove][3] - ONE;
11717
11718     if (moveList[currentMove][1] == '@') {
11719         if (appData.highlightLastMove) {
11720             SetHighlights(-1, -1, toX, toY);
11721         }
11722     } else {
11723         fromX = moveList[currentMove][0] - AAA;
11724         fromY = moveList[currentMove][1] - ONE;
11725
11726         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11727
11728         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11729
11730         if (appData.highlightLastMove) {
11731             SetHighlights(fromX, fromY, toX, toY);
11732         }
11733     }
11734     DisplayMove(currentMove);
11735     SendMoveToProgram(currentMove++, &first);
11736     DisplayBothClocks();
11737     DrawPosition(FALSE, boards[currentMove]);
11738     // [HGM] PV info: always display, routine tests if empty
11739     DisplayComment(currentMove - 1, commentList[currentMove]);
11740     return TRUE;
11741 }
11742
11743
11744 int
11745 LoadGameOneMove (ChessMove readAhead)
11746 {
11747     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11748     char promoChar = NULLCHAR;
11749     ChessMove moveType;
11750     char move[MSG_SIZ];
11751     char *p, *q;
11752
11753     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11754         gameMode != AnalyzeMode && gameMode != Training) {
11755         gameFileFP = NULL;
11756         return FALSE;
11757     }
11758
11759     yyboardindex = forwardMostMove;
11760     if (readAhead != EndOfFile) {
11761       moveType = readAhead;
11762     } else {
11763       if (gameFileFP == NULL)
11764           return FALSE;
11765       moveType = (ChessMove) Myylex();
11766     }
11767
11768     done = FALSE;
11769     switch (moveType) {
11770       case Comment:
11771         if (appData.debugMode)
11772           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11773         p = yy_text;
11774
11775         /* append the comment but don't display it */
11776         AppendComment(currentMove, p, FALSE);
11777         return TRUE;
11778
11779       case WhiteCapturesEnPassant:
11780       case BlackCapturesEnPassant:
11781       case WhitePromotion:
11782       case BlackPromotion:
11783       case WhiteNonPromotion:
11784       case BlackNonPromotion:
11785       case NormalMove:
11786       case FirstLeg:
11787       case WhiteKingSideCastle:
11788       case WhiteQueenSideCastle:
11789       case BlackKingSideCastle:
11790       case BlackQueenSideCastle:
11791       case WhiteKingSideCastleWild:
11792       case WhiteQueenSideCastleWild:
11793       case BlackKingSideCastleWild:
11794       case BlackQueenSideCastleWild:
11795       /* PUSH Fabien */
11796       case WhiteHSideCastleFR:
11797       case WhiteASideCastleFR:
11798       case BlackHSideCastleFR:
11799       case BlackASideCastleFR:
11800       /* POP Fabien */
11801         if (appData.debugMode)
11802           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11803         fromX = currentMoveString[0] - AAA;
11804         fromY = currentMoveString[1] - ONE;
11805         toX = currentMoveString[2] - AAA;
11806         toY = currentMoveString[3] - ONE;
11807         promoChar = currentMoveString[4];
11808         if(promoChar == ';') promoChar = NULLCHAR;
11809         break;
11810
11811       case WhiteDrop:
11812       case BlackDrop:
11813         if (appData.debugMode)
11814           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11815         fromX = moveType == WhiteDrop ?
11816           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11817         (int) CharToPiece(ToLower(currentMoveString[0]));
11818         fromY = DROP_RANK;
11819         toX = currentMoveString[2] - AAA;
11820         toY = currentMoveString[3] - ONE;
11821         break;
11822
11823       case WhiteWins:
11824       case BlackWins:
11825       case GameIsDrawn:
11826       case GameUnfinished:
11827         if (appData.debugMode)
11828           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11829         p = strchr(yy_text, '{');
11830         if (p == NULL) p = strchr(yy_text, '(');
11831         if (p == NULL) {
11832             p = yy_text;
11833             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11834         } else {
11835             q = strchr(p, *p == '{' ? '}' : ')');
11836             if (q != NULL) *q = NULLCHAR;
11837             p++;
11838         }
11839         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11840         GameEnds(moveType, p, GE_FILE);
11841         done = TRUE;
11842         if (cmailMsgLoaded) {
11843             ClearHighlights();
11844             flipView = WhiteOnMove(currentMove);
11845             if (moveType == GameUnfinished) flipView = !flipView;
11846             if (appData.debugMode)
11847               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11848         }
11849         break;
11850
11851       case EndOfFile:
11852         if (appData.debugMode)
11853           fprintf(debugFP, "Parser hit end of file\n");
11854         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11855           case MT_NONE:
11856           case MT_CHECK:
11857             break;
11858           case MT_CHECKMATE:
11859           case MT_STAINMATE:
11860             if (WhiteOnMove(currentMove)) {
11861                 GameEnds(BlackWins, "Black mates", GE_FILE);
11862             } else {
11863                 GameEnds(WhiteWins, "White mates", GE_FILE);
11864             }
11865             break;
11866           case MT_STALEMATE:
11867             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11868             break;
11869         }
11870         done = TRUE;
11871         break;
11872
11873       case MoveNumberOne:
11874         if (lastLoadGameStart == GNUChessGame) {
11875             /* GNUChessGames have numbers, but they aren't move numbers */
11876             if (appData.debugMode)
11877               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11878                       yy_text, (int) moveType);
11879             return LoadGameOneMove(EndOfFile); /* tail recursion */
11880         }
11881         /* else fall thru */
11882
11883       case XBoardGame:
11884       case GNUChessGame:
11885       case PGNTag:
11886         /* Reached start of next game in file */
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11889         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11890           case MT_NONE:
11891           case MT_CHECK:
11892             break;
11893           case MT_CHECKMATE:
11894           case MT_STAINMATE:
11895             if (WhiteOnMove(currentMove)) {
11896                 GameEnds(BlackWins, "Black mates", GE_FILE);
11897             } else {
11898                 GameEnds(WhiteWins, "White mates", GE_FILE);
11899             }
11900             break;
11901           case MT_STALEMATE:
11902             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11903             break;
11904         }
11905         done = TRUE;
11906         break;
11907
11908       case PositionDiagram:     /* should not happen; ignore */
11909       case ElapsedTime:         /* ignore */
11910       case NAG:                 /* ignore */
11911         if (appData.debugMode)
11912           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11913                   yy_text, (int) moveType);
11914         return LoadGameOneMove(EndOfFile); /* tail recursion */
11915
11916       case IllegalMove:
11917         if (appData.testLegality) {
11918             if (appData.debugMode)
11919               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11920             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11921                     (forwardMostMove / 2) + 1,
11922                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11923             DisplayError(move, 0);
11924             done = TRUE;
11925         } else {
11926             if (appData.debugMode)
11927               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11928                       yy_text, currentMoveString);
11929             fromX = currentMoveString[0] - AAA;
11930             fromY = currentMoveString[1] - ONE;
11931             toX = currentMoveString[2] - AAA;
11932             toY = currentMoveString[3] - ONE;
11933             promoChar = currentMoveString[4];
11934         }
11935         break;
11936
11937       case AmbiguousMove:
11938         if (appData.debugMode)
11939           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11940         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11941                 (forwardMostMove / 2) + 1,
11942                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11943         DisplayError(move, 0);
11944         done = TRUE;
11945         break;
11946
11947       default:
11948       case ImpossibleMove:
11949         if (appData.debugMode)
11950           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11951         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11952                 (forwardMostMove / 2) + 1,
11953                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11954         DisplayError(move, 0);
11955         done = TRUE;
11956         break;
11957     }
11958
11959     if (done) {
11960         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11961             DrawPosition(FALSE, boards[currentMove]);
11962             DisplayBothClocks();
11963             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11964               DisplayComment(currentMove - 1, commentList[currentMove]);
11965         }
11966         (void) StopLoadGameTimer();
11967         gameFileFP = NULL;
11968         cmailOldMove = forwardMostMove;
11969         return FALSE;
11970     } else {
11971         /* currentMoveString is set as a side-effect of yylex */
11972
11973         thinkOutput[0] = NULLCHAR;
11974         MakeMove(fromX, fromY, toX, toY, promoChar);
11975         killX = killY = -1; // [HGM] lion: used up
11976         currentMove = forwardMostMove;
11977         return TRUE;
11978     }
11979 }
11980
11981 /* Load the nth game from the given file */
11982 int
11983 LoadGameFromFile (char *filename, int n, char *title, int useList)
11984 {
11985     FILE *f;
11986     char buf[MSG_SIZ];
11987
11988     if (strcmp(filename, "-") == 0) {
11989         f = stdin;
11990         title = "stdin";
11991     } else {
11992         f = fopen(filename, "rb");
11993         if (f == NULL) {
11994           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11995             DisplayError(buf, errno);
11996             return FALSE;
11997         }
11998     }
11999     if (fseek(f, 0, 0) == -1) {
12000         /* f is not seekable; probably a pipe */
12001         useList = FALSE;
12002     }
12003     if (useList && n == 0) {
12004         int error = GameListBuild(f);
12005         if (error) {
12006             DisplayError(_("Cannot build game list"), error);
12007         } else if (!ListEmpty(&gameList) &&
12008                    ((ListGame *) gameList.tailPred)->number > 1) {
12009             GameListPopUp(f, title);
12010             return TRUE;
12011         }
12012         GameListDestroy();
12013         n = 1;
12014     }
12015     if (n == 0) n = 1;
12016     return LoadGame(f, n, title, FALSE);
12017 }
12018
12019
12020 void
12021 MakeRegisteredMove ()
12022 {
12023     int fromX, fromY, toX, toY;
12024     char promoChar;
12025     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12026         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12027           case CMAIL_MOVE:
12028           case CMAIL_DRAW:
12029             if (appData.debugMode)
12030               fprintf(debugFP, "Restoring %s for game %d\n",
12031                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12032
12033             thinkOutput[0] = NULLCHAR;
12034             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12035             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12036             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12037             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12038             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12039             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12040             MakeMove(fromX, fromY, toX, toY, promoChar);
12041             ShowMove(fromX, fromY, toX, toY);
12042
12043             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12044               case MT_NONE:
12045               case MT_CHECK:
12046                 break;
12047
12048               case MT_CHECKMATE:
12049               case MT_STAINMATE:
12050                 if (WhiteOnMove(currentMove)) {
12051                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12052                 } else {
12053                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12054                 }
12055                 break;
12056
12057               case MT_STALEMATE:
12058                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12059                 break;
12060             }
12061
12062             break;
12063
12064           case CMAIL_RESIGN:
12065             if (WhiteOnMove(currentMove)) {
12066                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12067             } else {
12068                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12069             }
12070             break;
12071
12072           case CMAIL_ACCEPT:
12073             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12074             break;
12075
12076           default:
12077             break;
12078         }
12079     }
12080
12081     return;
12082 }
12083
12084 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12085 int
12086 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12087 {
12088     int retVal;
12089
12090     if (gameNumber > nCmailGames) {
12091         DisplayError(_("No more games in this message"), 0);
12092         return FALSE;
12093     }
12094     if (f == lastLoadGameFP) {
12095         int offset = gameNumber - lastLoadGameNumber;
12096         if (offset == 0) {
12097             cmailMsg[0] = NULLCHAR;
12098             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12099                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12100                 nCmailMovesRegistered--;
12101             }
12102             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12103             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12104                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12105             }
12106         } else {
12107             if (! RegisterMove()) return FALSE;
12108         }
12109     }
12110
12111     retVal = LoadGame(f, gameNumber, title, useList);
12112
12113     /* Make move registered during previous look at this game, if any */
12114     MakeRegisteredMove();
12115
12116     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12117         commentList[currentMove]
12118           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12119         DisplayComment(currentMove - 1, commentList[currentMove]);
12120     }
12121
12122     return retVal;
12123 }
12124
12125 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12126 int
12127 ReloadGame (int offset)
12128 {
12129     int gameNumber = lastLoadGameNumber + offset;
12130     if (lastLoadGameFP == NULL) {
12131         DisplayError(_("No game has been loaded yet"), 0);
12132         return FALSE;
12133     }
12134     if (gameNumber <= 0) {
12135         DisplayError(_("Can't back up any further"), 0);
12136         return FALSE;
12137     }
12138     if (cmailMsgLoaded) {
12139         return CmailLoadGame(lastLoadGameFP, gameNumber,
12140                              lastLoadGameTitle, lastLoadGameUseList);
12141     } else {
12142         return LoadGame(lastLoadGameFP, gameNumber,
12143                         lastLoadGameTitle, lastLoadGameUseList);
12144     }
12145 }
12146
12147 int keys[EmptySquare+1];
12148
12149 int
12150 PositionMatches (Board b1, Board b2)
12151 {
12152     int r, f, sum=0;
12153     switch(appData.searchMode) {
12154         case 1: return CompareWithRights(b1, b2);
12155         case 2:
12156             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12157                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12158             }
12159             return TRUE;
12160         case 3:
12161             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12163                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12164             }
12165             return sum==0;
12166         case 4:
12167             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12168                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12169             }
12170             return sum==0;
12171     }
12172     return TRUE;
12173 }
12174
12175 #define Q_PROMO  4
12176 #define Q_EP     3
12177 #define Q_BCASTL 2
12178 #define Q_WCASTL 1
12179
12180 int pieceList[256], quickBoard[256];
12181 ChessSquare pieceType[256] = { EmptySquare };
12182 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12183 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12184 int soughtTotal, turn;
12185 Boolean epOK, flipSearch;
12186
12187 typedef struct {
12188     unsigned char piece, to;
12189 } Move;
12190
12191 #define DSIZE (250000)
12192
12193 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12194 Move *moveDatabase = initialSpace;
12195 unsigned int movePtr, dataSize = DSIZE;
12196
12197 int
12198 MakePieceList (Board board, int *counts)
12199 {
12200     int r, f, n=Q_PROMO, total=0;
12201     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12202     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12203         int sq = f + (r<<4);
12204         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12205             quickBoard[sq] = ++n;
12206             pieceList[n] = sq;
12207             pieceType[n] = board[r][f];
12208             counts[board[r][f]]++;
12209             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12210             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12211             total++;
12212         }
12213     }
12214     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12215     return total;
12216 }
12217
12218 void
12219 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12220 {
12221     int sq = fromX + (fromY<<4);
12222     int piece = quickBoard[sq], rook;
12223     quickBoard[sq] = 0;
12224     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12225     if(piece == pieceList[1] && fromY == toY) {
12226       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12227         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12228         moveDatabase[movePtr++].piece = Q_WCASTL;
12229         quickBoard[sq] = piece;
12230         piece = quickBoard[from]; quickBoard[from] = 0;
12231         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12232       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12233         quickBoard[sq] = 0; // remove Rook
12234         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12235         moveDatabase[movePtr++].piece = Q_WCASTL;
12236         quickBoard[sq] = pieceList[1]; // put King
12237         piece = rook;
12238         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12239       }
12240     } else
12241     if(piece == pieceList[2] && fromY == toY) {
12242       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12243         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12244         moveDatabase[movePtr++].piece = Q_BCASTL;
12245         quickBoard[sq] = piece;
12246         piece = quickBoard[from]; quickBoard[from] = 0;
12247         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12248       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12249         quickBoard[sq] = 0; // remove Rook
12250         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12251         moveDatabase[movePtr++].piece = Q_BCASTL;
12252         quickBoard[sq] = pieceList[2]; // put King
12253         piece = rook;
12254         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12255       }
12256     } else
12257     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12258         quickBoard[(fromY<<4)+toX] = 0;
12259         moveDatabase[movePtr].piece = Q_EP;
12260         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12261         moveDatabase[movePtr].to = sq;
12262     } else
12263     if(promoPiece != pieceType[piece]) {
12264         moveDatabase[movePtr++].piece = Q_PROMO;
12265         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12266     }
12267     moveDatabase[movePtr].piece = piece;
12268     quickBoard[sq] = piece;
12269     movePtr++;
12270 }
12271
12272 int
12273 PackGame (Board board)
12274 {
12275     Move *newSpace = NULL;
12276     moveDatabase[movePtr].piece = 0; // terminate previous game
12277     if(movePtr > dataSize) {
12278         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12279         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12280         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12281         if(newSpace) {
12282             int i;
12283             Move *p = moveDatabase, *q = newSpace;
12284             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12285             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12286             moveDatabase = newSpace;
12287         } else { // calloc failed, we must be out of memory. Too bad...
12288             dataSize = 0; // prevent calloc events for all subsequent games
12289             return 0;     // and signal this one isn't cached
12290         }
12291     }
12292     movePtr++;
12293     MakePieceList(board, counts);
12294     return movePtr;
12295 }
12296
12297 int
12298 QuickCompare (Board board, int *minCounts, int *maxCounts)
12299 {   // compare according to search mode
12300     int r, f;
12301     switch(appData.searchMode)
12302     {
12303       case 1: // exact position match
12304         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12305         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12306             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12307         }
12308         break;
12309       case 2: // can have extra material on empty squares
12310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311             if(board[r][f] == EmptySquare) continue;
12312             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12313         }
12314         break;
12315       case 3: // material with exact Pawn structure
12316         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12317             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12318             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12319         } // fall through to material comparison
12320       case 4: // exact material
12321         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12322         break;
12323       case 6: // material range with given imbalance
12324         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12325         // fall through to range comparison
12326       case 5: // material range
12327         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12328     }
12329     return TRUE;
12330 }
12331
12332 int
12333 QuickScan (Board board, Move *move)
12334 {   // reconstruct game,and compare all positions in it
12335     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12336     do {
12337         int piece = move->piece;
12338         int to = move->to, from = pieceList[piece];
12339         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12340           if(!piece) return -1;
12341           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12342             piece = (++move)->piece;
12343             from = pieceList[piece];
12344             counts[pieceType[piece]]--;
12345             pieceType[piece] = (ChessSquare) move->to;
12346             counts[move->to]++;
12347           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12348             counts[pieceType[quickBoard[to]]]--;
12349             quickBoard[to] = 0; total--;
12350             move++;
12351             continue;
12352           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12353             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12354             from  = pieceList[piece]; // so this must be King
12355             quickBoard[from] = 0;
12356             pieceList[piece] = to;
12357             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12358             quickBoard[from] = 0; // rook
12359             quickBoard[to] = piece;
12360             to = move->to; piece = move->piece;
12361             goto aftercastle;
12362           }
12363         }
12364         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12365         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12366         quickBoard[from] = 0;
12367       aftercastle:
12368         quickBoard[to] = piece;
12369         pieceList[piece] = to;
12370         cnt++; turn ^= 3;
12371         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12372            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12373            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12374                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12375           ) {
12376             static int lastCounts[EmptySquare+1];
12377             int i;
12378             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12379             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12380         } else stretch = 0;
12381         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12382         move++;
12383     } while(1);
12384 }
12385
12386 void
12387 InitSearch ()
12388 {
12389     int r, f;
12390     flipSearch = FALSE;
12391     CopyBoard(soughtBoard, boards[currentMove]);
12392     soughtTotal = MakePieceList(soughtBoard, maxSought);
12393     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12394     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12395     CopyBoard(reverseBoard, boards[currentMove]);
12396     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12397         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12398         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12399         reverseBoard[r][f] = piece;
12400     }
12401     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12402     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12403     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12404                  || (boards[currentMove][CASTLING][2] == NoRights ||
12405                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12406                  && (boards[currentMove][CASTLING][5] == NoRights ||
12407                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12408       ) {
12409         flipSearch = TRUE;
12410         CopyBoard(flipBoard, soughtBoard);
12411         CopyBoard(rotateBoard, reverseBoard);
12412         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12413             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12414             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12415         }
12416     }
12417     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12418     if(appData.searchMode >= 5) {
12419         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12420         MakePieceList(soughtBoard, minSought);
12421         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12422     }
12423     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12424         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12425 }
12426
12427 GameInfo dummyInfo;
12428 static int creatingBook;
12429
12430 int
12431 GameContainsPosition (FILE *f, ListGame *lg)
12432 {
12433     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12434     int fromX, fromY, toX, toY;
12435     char promoChar;
12436     static int initDone=FALSE;
12437
12438     // weed out games based on numerical tag comparison
12439     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12440     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12441     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12442     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12443     if(!initDone) {
12444         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12445         initDone = TRUE;
12446     }
12447     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12448     else CopyBoard(boards[scratch], initialPosition); // default start position
12449     if(lg->moves) {
12450         turn = btm + 1;
12451         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12452         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12453     }
12454     if(btm) plyNr++;
12455     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12456     fseek(f, lg->offset, 0);
12457     yynewfile(f);
12458     while(1) {
12459         yyboardindex = scratch;
12460         quickFlag = plyNr+1;
12461         next = Myylex();
12462         quickFlag = 0;
12463         switch(next) {
12464             case PGNTag:
12465                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12466             default:
12467                 continue;
12468
12469             case XBoardGame:
12470             case GNUChessGame:
12471                 if(plyNr) return -1; // after we have seen moves, this is for new game
12472               continue;
12473
12474             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12475             case ImpossibleMove:
12476             case WhiteWins: // game ends here with these four
12477             case BlackWins:
12478             case GameIsDrawn:
12479             case GameUnfinished:
12480                 return -1;
12481
12482             case IllegalMove:
12483                 if(appData.testLegality) return -1;
12484             case WhiteCapturesEnPassant:
12485             case BlackCapturesEnPassant:
12486             case WhitePromotion:
12487             case BlackPromotion:
12488             case WhiteNonPromotion:
12489             case BlackNonPromotion:
12490             case NormalMove:
12491             case FirstLeg:
12492             case WhiteKingSideCastle:
12493             case WhiteQueenSideCastle:
12494             case BlackKingSideCastle:
12495             case BlackQueenSideCastle:
12496             case WhiteKingSideCastleWild:
12497             case WhiteQueenSideCastleWild:
12498             case BlackKingSideCastleWild:
12499             case BlackQueenSideCastleWild:
12500             case WhiteHSideCastleFR:
12501             case WhiteASideCastleFR:
12502             case BlackHSideCastleFR:
12503             case BlackASideCastleFR:
12504                 fromX = currentMoveString[0] - AAA;
12505                 fromY = currentMoveString[1] - ONE;
12506                 toX = currentMoveString[2] - AAA;
12507                 toY = currentMoveString[3] - ONE;
12508                 promoChar = currentMoveString[4];
12509                 break;
12510             case WhiteDrop:
12511             case BlackDrop:
12512                 fromX = next == WhiteDrop ?
12513                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12514                   (int) CharToPiece(ToLower(currentMoveString[0]));
12515                 fromY = DROP_RANK;
12516                 toX = currentMoveString[2] - AAA;
12517                 toY = currentMoveString[3] - ONE;
12518                 promoChar = 0;
12519                 break;
12520         }
12521         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12522         plyNr++;
12523         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12524         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12525         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12526         if(appData.findMirror) {
12527             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12528             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12529         }
12530     }
12531 }
12532
12533 /* Load the nth game from open file f */
12534 int
12535 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12536 {
12537     ChessMove cm;
12538     char buf[MSG_SIZ];
12539     int gn = gameNumber;
12540     ListGame *lg = NULL;
12541     int numPGNTags = 0;
12542     int err, pos = -1;
12543     GameMode oldGameMode;
12544     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12545
12546     if (appData.debugMode)
12547         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12548
12549     if (gameMode == Training )
12550         SetTrainingModeOff();
12551
12552     oldGameMode = gameMode;
12553     if (gameMode != BeginningOfGame) {
12554       Reset(FALSE, TRUE);
12555     }
12556     killX = killY = -1; // [HGM] lion: in case we did not Reset
12557
12558     gameFileFP = f;
12559     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12560         fclose(lastLoadGameFP);
12561     }
12562
12563     if (useList) {
12564         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12565
12566         if (lg) {
12567             fseek(f, lg->offset, 0);
12568             GameListHighlight(gameNumber);
12569             pos = lg->position;
12570             gn = 1;
12571         }
12572         else {
12573             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12574               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12575             else
12576             DisplayError(_("Game number out of range"), 0);
12577             return FALSE;
12578         }
12579     } else {
12580         GameListDestroy();
12581         if (fseek(f, 0, 0) == -1) {
12582             if (f == lastLoadGameFP ?
12583                 gameNumber == lastLoadGameNumber + 1 :
12584                 gameNumber == 1) {
12585                 gn = 1;
12586             } else {
12587                 DisplayError(_("Can't seek on game file"), 0);
12588                 return FALSE;
12589             }
12590         }
12591     }
12592     lastLoadGameFP = f;
12593     lastLoadGameNumber = gameNumber;
12594     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12595     lastLoadGameUseList = useList;
12596
12597     yynewfile(f);
12598
12599     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12600       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12601                 lg->gameInfo.black);
12602             DisplayTitle(buf);
12603     } else if (*title != NULLCHAR) {
12604         if (gameNumber > 1) {
12605           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12606             DisplayTitle(buf);
12607         } else {
12608             DisplayTitle(title);
12609         }
12610     }
12611
12612     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12613         gameMode = PlayFromGameFile;
12614         ModeHighlight();
12615     }
12616
12617     currentMove = forwardMostMove = backwardMostMove = 0;
12618     CopyBoard(boards[0], initialPosition);
12619     StopClocks();
12620
12621     /*
12622      * Skip the first gn-1 games in the file.
12623      * Also skip over anything that precedes an identifiable
12624      * start of game marker, to avoid being confused by
12625      * garbage at the start of the file.  Currently
12626      * recognized start of game markers are the move number "1",
12627      * the pattern "gnuchess .* game", the pattern
12628      * "^[#;%] [^ ]* game file", and a PGN tag block.
12629      * A game that starts with one of the latter two patterns
12630      * will also have a move number 1, possibly
12631      * following a position diagram.
12632      * 5-4-02: Let's try being more lenient and allowing a game to
12633      * start with an unnumbered move.  Does that break anything?
12634      */
12635     cm = lastLoadGameStart = EndOfFile;
12636     while (gn > 0) {
12637         yyboardindex = forwardMostMove;
12638         cm = (ChessMove) Myylex();
12639         switch (cm) {
12640           case EndOfFile:
12641             if (cmailMsgLoaded) {
12642                 nCmailGames = CMAIL_MAX_GAMES - gn;
12643             } else {
12644                 Reset(TRUE, TRUE);
12645                 DisplayError(_("Game not found in file"), 0);
12646             }
12647             return FALSE;
12648
12649           case GNUChessGame:
12650           case XBoardGame:
12651             gn--;
12652             lastLoadGameStart = cm;
12653             break;
12654
12655           case MoveNumberOne:
12656             switch (lastLoadGameStart) {
12657               case GNUChessGame:
12658               case XBoardGame:
12659               case PGNTag:
12660                 break;
12661               case MoveNumberOne:
12662               case EndOfFile:
12663                 gn--;           /* count this game */
12664                 lastLoadGameStart = cm;
12665                 break;
12666               default:
12667                 /* impossible */
12668                 break;
12669             }
12670             break;
12671
12672           case PGNTag:
12673             switch (lastLoadGameStart) {
12674               case GNUChessGame:
12675               case PGNTag:
12676               case MoveNumberOne:
12677               case EndOfFile:
12678                 gn--;           /* count this game */
12679                 lastLoadGameStart = cm;
12680                 break;
12681               case XBoardGame:
12682                 lastLoadGameStart = cm; /* game counted already */
12683                 break;
12684               default:
12685                 /* impossible */
12686                 break;
12687             }
12688             if (gn > 0) {
12689                 do {
12690                     yyboardindex = forwardMostMove;
12691                     cm = (ChessMove) Myylex();
12692                 } while (cm == PGNTag || cm == Comment);
12693             }
12694             break;
12695
12696           case WhiteWins:
12697           case BlackWins:
12698           case GameIsDrawn:
12699             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12700                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12701                     != CMAIL_OLD_RESULT) {
12702                     nCmailResults ++ ;
12703                     cmailResult[  CMAIL_MAX_GAMES
12704                                 - gn - 1] = CMAIL_OLD_RESULT;
12705                 }
12706             }
12707             break;
12708
12709           case NormalMove:
12710           case FirstLeg:
12711             /* Only a NormalMove can be at the start of a game
12712              * without a position diagram. */
12713             if (lastLoadGameStart == EndOfFile ) {
12714               gn--;
12715               lastLoadGameStart = MoveNumberOne;
12716             }
12717             break;
12718
12719           default:
12720             break;
12721         }
12722     }
12723
12724     if (appData.debugMode)
12725       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12726
12727     if (cm == XBoardGame) {
12728         /* Skip any header junk before position diagram and/or move 1 */
12729         for (;;) {
12730             yyboardindex = forwardMostMove;
12731             cm = (ChessMove) Myylex();
12732
12733             if (cm == EndOfFile ||
12734                 cm == GNUChessGame || cm == XBoardGame) {
12735                 /* Empty game; pretend end-of-file and handle later */
12736                 cm = EndOfFile;
12737                 break;
12738             }
12739
12740             if (cm == MoveNumberOne || cm == PositionDiagram ||
12741                 cm == PGNTag || cm == Comment)
12742               break;
12743         }
12744     } else if (cm == GNUChessGame) {
12745         if (gameInfo.event != NULL) {
12746             free(gameInfo.event);
12747         }
12748         gameInfo.event = StrSave(yy_text);
12749     }
12750
12751     startedFromSetupPosition = FALSE;
12752     while (cm == PGNTag) {
12753         if (appData.debugMode)
12754           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12755         err = ParsePGNTag(yy_text, &gameInfo);
12756         if (!err) numPGNTags++;
12757
12758         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12759         if(gameInfo.variant != oldVariant) {
12760             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12761             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12762             InitPosition(TRUE);
12763             oldVariant = gameInfo.variant;
12764             if (appData.debugMode)
12765               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12766         }
12767
12768
12769         if (gameInfo.fen != NULL) {
12770           Board initial_position;
12771           startedFromSetupPosition = TRUE;
12772           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12773             Reset(TRUE, TRUE);
12774             DisplayError(_("Bad FEN position in file"), 0);
12775             return FALSE;
12776           }
12777           CopyBoard(boards[0], initial_position);
12778           if (blackPlaysFirst) {
12779             currentMove = forwardMostMove = backwardMostMove = 1;
12780             CopyBoard(boards[1], initial_position);
12781             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12782             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12783             timeRemaining[0][1] = whiteTimeRemaining;
12784             timeRemaining[1][1] = blackTimeRemaining;
12785             if (commentList[0] != NULL) {
12786               commentList[1] = commentList[0];
12787               commentList[0] = NULL;
12788             }
12789           } else {
12790             currentMove = forwardMostMove = backwardMostMove = 0;
12791           }
12792           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12793           {   int i;
12794               initialRulePlies = FENrulePlies;
12795               for( i=0; i< nrCastlingRights; i++ )
12796                   initialRights[i] = initial_position[CASTLING][i];
12797           }
12798           yyboardindex = forwardMostMove;
12799           free(gameInfo.fen);
12800           gameInfo.fen = NULL;
12801         }
12802
12803         yyboardindex = forwardMostMove;
12804         cm = (ChessMove) Myylex();
12805
12806         /* Handle comments interspersed among the tags */
12807         while (cm == Comment) {
12808             char *p;
12809             if (appData.debugMode)
12810               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12811             p = yy_text;
12812             AppendComment(currentMove, p, FALSE);
12813             yyboardindex = forwardMostMove;
12814             cm = (ChessMove) Myylex();
12815         }
12816     }
12817
12818     /* don't rely on existence of Event tag since if game was
12819      * pasted from clipboard the Event tag may not exist
12820      */
12821     if (numPGNTags > 0){
12822         char *tags;
12823         if (gameInfo.variant == VariantNormal) {
12824           VariantClass v = StringToVariant(gameInfo.event);
12825           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12826           if(v < VariantShogi) gameInfo.variant = v;
12827         }
12828         if (!matchMode) {
12829           if( appData.autoDisplayTags ) {
12830             tags = PGNTags(&gameInfo);
12831             TagsPopUp(tags, CmailMsg());
12832             free(tags);
12833           }
12834         }
12835     } else {
12836         /* Make something up, but don't display it now */
12837         SetGameInfo();
12838         TagsPopDown();
12839     }
12840
12841     if (cm == PositionDiagram) {
12842         int i, j;
12843         char *p;
12844         Board initial_position;
12845
12846         if (appData.debugMode)
12847           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12848
12849         if (!startedFromSetupPosition) {
12850             p = yy_text;
12851             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12852               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12853                 switch (*p) {
12854                   case '{':
12855                   case '[':
12856                   case '-':
12857                   case ' ':
12858                   case '\t':
12859                   case '\n':
12860                   case '\r':
12861                     break;
12862                   default:
12863                     initial_position[i][j++] = CharToPiece(*p);
12864                     break;
12865                 }
12866             while (*p == ' ' || *p == '\t' ||
12867                    *p == '\n' || *p == '\r') p++;
12868
12869             if (strncmp(p, "black", strlen("black"))==0)
12870               blackPlaysFirst = TRUE;
12871             else
12872               blackPlaysFirst = FALSE;
12873             startedFromSetupPosition = TRUE;
12874
12875             CopyBoard(boards[0], initial_position);
12876             if (blackPlaysFirst) {
12877                 currentMove = forwardMostMove = backwardMostMove = 1;
12878                 CopyBoard(boards[1], initial_position);
12879                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12880                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12881                 timeRemaining[0][1] = whiteTimeRemaining;
12882                 timeRemaining[1][1] = blackTimeRemaining;
12883                 if (commentList[0] != NULL) {
12884                     commentList[1] = commentList[0];
12885                     commentList[0] = NULL;
12886                 }
12887             } else {
12888                 currentMove = forwardMostMove = backwardMostMove = 0;
12889             }
12890         }
12891         yyboardindex = forwardMostMove;
12892         cm = (ChessMove) Myylex();
12893     }
12894
12895   if(!creatingBook) {
12896     if (first.pr == NoProc) {
12897         StartChessProgram(&first);
12898     }
12899     InitChessProgram(&first, FALSE);
12900     SendToProgram("force\n", &first);
12901     if (startedFromSetupPosition) {
12902         SendBoard(&first, forwardMostMove);
12903     if (appData.debugMode) {
12904         fprintf(debugFP, "Load Game\n");
12905     }
12906         DisplayBothClocks();
12907     }
12908   }
12909
12910     /* [HGM] server: flag to write setup moves in broadcast file as one */
12911     loadFlag = appData.suppressLoadMoves;
12912
12913     while (cm == Comment) {
12914         char *p;
12915         if (appData.debugMode)
12916           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12917         p = yy_text;
12918         AppendComment(currentMove, p, FALSE);
12919         yyboardindex = forwardMostMove;
12920         cm = (ChessMove) Myylex();
12921     }
12922
12923     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12924         cm == WhiteWins || cm == BlackWins ||
12925         cm == GameIsDrawn || cm == GameUnfinished) {
12926         DisplayMessage("", _("No moves in game"));
12927         if (cmailMsgLoaded) {
12928             if (appData.debugMode)
12929               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12930             ClearHighlights();
12931             flipView = FALSE;
12932         }
12933         DrawPosition(FALSE, boards[currentMove]);
12934         DisplayBothClocks();
12935         gameMode = EditGame;
12936         ModeHighlight();
12937         gameFileFP = NULL;
12938         cmailOldMove = 0;
12939         return TRUE;
12940     }
12941
12942     // [HGM] PV info: routine tests if comment empty
12943     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12944         DisplayComment(currentMove - 1, commentList[currentMove]);
12945     }
12946     if (!matchMode && appData.timeDelay != 0)
12947       DrawPosition(FALSE, boards[currentMove]);
12948
12949     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12950       programStats.ok_to_send = 1;
12951     }
12952
12953     /* if the first token after the PGN tags is a move
12954      * and not move number 1, retrieve it from the parser
12955      */
12956     if (cm != MoveNumberOne)
12957         LoadGameOneMove(cm);
12958
12959     /* load the remaining moves from the file */
12960     while (LoadGameOneMove(EndOfFile)) {
12961       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12962       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12963     }
12964
12965     /* rewind to the start of the game */
12966     currentMove = backwardMostMove;
12967
12968     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12969
12970     if (oldGameMode == AnalyzeFile) {
12971       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12972       AnalyzeFileEvent();
12973     } else
12974     if (oldGameMode == AnalyzeMode) {
12975       AnalyzeFileEvent();
12976     }
12977
12978     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12979         long int w, b; // [HGM] adjourn: restore saved clock times
12980         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12981         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12982             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12983             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12984         }
12985     }
12986
12987     if(creatingBook) return TRUE;
12988     if (!matchMode && pos > 0) {
12989         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12990     } else
12991     if (matchMode || appData.timeDelay == 0) {
12992       ToEndEvent();
12993     } else if (appData.timeDelay > 0) {
12994       AutoPlayGameLoop();
12995     }
12996
12997     if (appData.debugMode)
12998         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12999
13000     loadFlag = 0; /* [HGM] true game starts */
13001     return TRUE;
13002 }
13003
13004 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13005 int
13006 ReloadPosition (int offset)
13007 {
13008     int positionNumber = lastLoadPositionNumber + offset;
13009     if (lastLoadPositionFP == NULL) {
13010         DisplayError(_("No position has been loaded yet"), 0);
13011         return FALSE;
13012     }
13013     if (positionNumber <= 0) {
13014         DisplayError(_("Can't back up any further"), 0);
13015         return FALSE;
13016     }
13017     return LoadPosition(lastLoadPositionFP, positionNumber,
13018                         lastLoadPositionTitle);
13019 }
13020
13021 /* Load the nth position from the given file */
13022 int
13023 LoadPositionFromFile (char *filename, int n, char *title)
13024 {
13025     FILE *f;
13026     char buf[MSG_SIZ];
13027
13028     if (strcmp(filename, "-") == 0) {
13029         return LoadPosition(stdin, n, "stdin");
13030     } else {
13031         f = fopen(filename, "rb");
13032         if (f == NULL) {
13033             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13034             DisplayError(buf, errno);
13035             return FALSE;
13036         } else {
13037             return LoadPosition(f, n, title);
13038         }
13039     }
13040 }
13041
13042 /* Load the nth position from the given open file, and close it */
13043 int
13044 LoadPosition (FILE *f, int positionNumber, char *title)
13045 {
13046     char *p, line[MSG_SIZ];
13047     Board initial_position;
13048     int i, j, fenMode, pn;
13049
13050     if (gameMode == Training )
13051         SetTrainingModeOff();
13052
13053     if (gameMode != BeginningOfGame) {
13054         Reset(FALSE, TRUE);
13055     }
13056     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13057         fclose(lastLoadPositionFP);
13058     }
13059     if (positionNumber == 0) positionNumber = 1;
13060     lastLoadPositionFP = f;
13061     lastLoadPositionNumber = positionNumber;
13062     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13063     if (first.pr == NoProc && !appData.noChessProgram) {
13064       StartChessProgram(&first);
13065       InitChessProgram(&first, FALSE);
13066     }
13067     pn = positionNumber;
13068     if (positionNumber < 0) {
13069         /* Negative position number means to seek to that byte offset */
13070         if (fseek(f, -positionNumber, 0) == -1) {
13071             DisplayError(_("Can't seek on position file"), 0);
13072             return FALSE;
13073         };
13074         pn = 1;
13075     } else {
13076         if (fseek(f, 0, 0) == -1) {
13077             if (f == lastLoadPositionFP ?
13078                 positionNumber == lastLoadPositionNumber + 1 :
13079                 positionNumber == 1) {
13080                 pn = 1;
13081             } else {
13082                 DisplayError(_("Can't seek on position file"), 0);
13083                 return FALSE;
13084             }
13085         }
13086     }
13087     /* See if this file is FEN or old-style xboard */
13088     if (fgets(line, MSG_SIZ, f) == NULL) {
13089         DisplayError(_("Position not found in file"), 0);
13090         return FALSE;
13091     }
13092     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13093     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13094
13095     if (pn >= 2) {
13096         if (fenMode || line[0] == '#') pn--;
13097         while (pn > 0) {
13098             /* skip positions before number pn */
13099             if (fgets(line, MSG_SIZ, f) == NULL) {
13100                 Reset(TRUE, TRUE);
13101                 DisplayError(_("Position not found in file"), 0);
13102                 return FALSE;
13103             }
13104             if (fenMode || line[0] == '#') pn--;
13105         }
13106     }
13107
13108     if (fenMode) {
13109         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13110             DisplayError(_("Bad FEN position in file"), 0);
13111             return FALSE;
13112         }
13113     } else {
13114         (void) fgets(line, MSG_SIZ, f);
13115         (void) fgets(line, MSG_SIZ, f);
13116
13117         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13118             (void) fgets(line, MSG_SIZ, f);
13119             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13120                 if (*p == ' ')
13121                   continue;
13122                 initial_position[i][j++] = CharToPiece(*p);
13123             }
13124         }
13125
13126         blackPlaysFirst = FALSE;
13127         if (!feof(f)) {
13128             (void) fgets(line, MSG_SIZ, f);
13129             if (strncmp(line, "black", strlen("black"))==0)
13130               blackPlaysFirst = TRUE;
13131         }
13132     }
13133     startedFromSetupPosition = TRUE;
13134
13135     CopyBoard(boards[0], initial_position);
13136     if (blackPlaysFirst) {
13137         currentMove = forwardMostMove = backwardMostMove = 1;
13138         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13139         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13140         CopyBoard(boards[1], initial_position);
13141         DisplayMessage("", _("Black to play"));
13142     } else {
13143         currentMove = forwardMostMove = backwardMostMove = 0;
13144         DisplayMessage("", _("White to play"));
13145     }
13146     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13147     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13148         SendToProgram("force\n", &first);
13149         SendBoard(&first, forwardMostMove);
13150     }
13151     if (appData.debugMode) {
13152 int i, j;
13153   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13154   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13155         fprintf(debugFP, "Load Position\n");
13156     }
13157
13158     if (positionNumber > 1) {
13159       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13160         DisplayTitle(line);
13161     } else {
13162         DisplayTitle(title);
13163     }
13164     gameMode = EditGame;
13165     ModeHighlight();
13166     ResetClocks();
13167     timeRemaining[0][1] = whiteTimeRemaining;
13168     timeRemaining[1][1] = blackTimeRemaining;
13169     DrawPosition(FALSE, boards[currentMove]);
13170
13171     return TRUE;
13172 }
13173
13174
13175 void
13176 CopyPlayerNameIntoFileName (char **dest, char *src)
13177 {
13178     while (*src != NULLCHAR && *src != ',') {
13179         if (*src == ' ') {
13180             *(*dest)++ = '_';
13181             src++;
13182         } else {
13183             *(*dest)++ = *src++;
13184         }
13185     }
13186 }
13187
13188 char *
13189 DefaultFileName (char *ext)
13190 {
13191     static char def[MSG_SIZ];
13192     char *p;
13193
13194     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13195         p = def;
13196         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13197         *p++ = '-';
13198         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13199         *p++ = '.';
13200         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13201     } else {
13202         def[0] = NULLCHAR;
13203     }
13204     return def;
13205 }
13206
13207 /* Save the current game to the given file */
13208 int
13209 SaveGameToFile (char *filename, int append)
13210 {
13211     FILE *f;
13212     char buf[MSG_SIZ];
13213     int result, i, t,tot=0;
13214
13215     if (strcmp(filename, "-") == 0) {
13216         return SaveGame(stdout, 0, NULL);
13217     } else {
13218         for(i=0; i<10; i++) { // upto 10 tries
13219              f = fopen(filename, append ? "a" : "w");
13220              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13221              if(f || errno != 13) break;
13222              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13223              tot += t;
13224         }
13225         if (f == NULL) {
13226             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13227             DisplayError(buf, errno);
13228             return FALSE;
13229         } else {
13230             safeStrCpy(buf, lastMsg, MSG_SIZ);
13231             DisplayMessage(_("Waiting for access to save file"), "");
13232             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13233             DisplayMessage(_("Saving game"), "");
13234             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13235             result = SaveGame(f, 0, NULL);
13236             DisplayMessage(buf, "");
13237             return result;
13238         }
13239     }
13240 }
13241
13242 char *
13243 SavePart (char *str)
13244 {
13245     static char buf[MSG_SIZ];
13246     char *p;
13247
13248     p = strchr(str, ' ');
13249     if (p == NULL) return str;
13250     strncpy(buf, str, p - str);
13251     buf[p - str] = NULLCHAR;
13252     return buf;
13253 }
13254
13255 #define PGN_MAX_LINE 75
13256
13257 #define PGN_SIDE_WHITE  0
13258 #define PGN_SIDE_BLACK  1
13259
13260 static int
13261 FindFirstMoveOutOfBook (int side)
13262 {
13263     int result = -1;
13264
13265     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13266         int index = backwardMostMove;
13267         int has_book_hit = 0;
13268
13269         if( (index % 2) != side ) {
13270             index++;
13271         }
13272
13273         while( index < forwardMostMove ) {
13274             /* Check to see if engine is in book */
13275             int depth = pvInfoList[index].depth;
13276             int score = pvInfoList[index].score;
13277             int in_book = 0;
13278
13279             if( depth <= 2 ) {
13280                 in_book = 1;
13281             }
13282             else if( score == 0 && depth == 63 ) {
13283                 in_book = 1; /* Zappa */
13284             }
13285             else if( score == 2 && depth == 99 ) {
13286                 in_book = 1; /* Abrok */
13287             }
13288
13289             has_book_hit += in_book;
13290
13291             if( ! in_book ) {
13292                 result = index;
13293
13294                 break;
13295             }
13296
13297             index += 2;
13298         }
13299     }
13300
13301     return result;
13302 }
13303
13304 void
13305 GetOutOfBookInfo (char * buf)
13306 {
13307     int oob[2];
13308     int i;
13309     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13310
13311     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13312     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13313
13314     *buf = '\0';
13315
13316     if( oob[0] >= 0 || oob[1] >= 0 ) {
13317         for( i=0; i<2; i++ ) {
13318             int idx = oob[i];
13319
13320             if( idx >= 0 ) {
13321                 if( i > 0 && oob[0] >= 0 ) {
13322                     strcat( buf, "   " );
13323                 }
13324
13325                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13326                 sprintf( buf+strlen(buf), "%s%.2f",
13327                     pvInfoList[idx].score >= 0 ? "+" : "",
13328                     pvInfoList[idx].score / 100.0 );
13329             }
13330         }
13331     }
13332 }
13333
13334 /* Save game in PGN style and close the file */
13335 int
13336 SaveGamePGN (FILE *f)
13337 {
13338     int i, offset, linelen, newblock;
13339 //    char *movetext;
13340     char numtext[32];
13341     int movelen, numlen, blank;
13342     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13343
13344     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13345
13346     PrintPGNTags(f, &gameInfo);
13347
13348     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13349
13350     if (backwardMostMove > 0 || startedFromSetupPosition) {
13351         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13352         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13353         fprintf(f, "\n{--------------\n");
13354         PrintPosition(f, backwardMostMove);
13355         fprintf(f, "--------------}\n");
13356         free(fen);
13357     }
13358     else {
13359         /* [AS] Out of book annotation */
13360         if( appData.saveOutOfBookInfo ) {
13361             char buf[64];
13362
13363             GetOutOfBookInfo( buf );
13364
13365             if( buf[0] != '\0' ) {
13366                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13367             }
13368         }
13369
13370         fprintf(f, "\n");
13371     }
13372
13373     i = backwardMostMove;
13374     linelen = 0;
13375     newblock = TRUE;
13376
13377     while (i < forwardMostMove) {
13378         /* Print comments preceding this move */
13379         if (commentList[i] != NULL) {
13380             if (linelen > 0) fprintf(f, "\n");
13381             fprintf(f, "%s", commentList[i]);
13382             linelen = 0;
13383             newblock = TRUE;
13384         }
13385
13386         /* Format move number */
13387         if ((i % 2) == 0)
13388           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13389         else
13390           if (newblock)
13391             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13392           else
13393             numtext[0] = NULLCHAR;
13394
13395         numlen = strlen(numtext);
13396         newblock = FALSE;
13397
13398         /* Print move number */
13399         blank = linelen > 0 && numlen > 0;
13400         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13401             fprintf(f, "\n");
13402             linelen = 0;
13403             blank = 0;
13404         }
13405         if (blank) {
13406             fprintf(f, " ");
13407             linelen++;
13408         }
13409         fprintf(f, "%s", numtext);
13410         linelen += numlen;
13411
13412         /* Get move */
13413         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13414         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13415
13416         /* Print move */
13417         blank = linelen > 0 && movelen > 0;
13418         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13419             fprintf(f, "\n");
13420             linelen = 0;
13421             blank = 0;
13422         }
13423         if (blank) {
13424             fprintf(f, " ");
13425             linelen++;
13426         }
13427         fprintf(f, "%s", move_buffer);
13428         linelen += movelen;
13429
13430         /* [AS] Add PV info if present */
13431         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13432             /* [HGM] add time */
13433             char buf[MSG_SIZ]; int seconds;
13434
13435             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13436
13437             if( seconds <= 0)
13438               buf[0] = 0;
13439             else
13440               if( seconds < 30 )
13441                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13442               else
13443                 {
13444                   seconds = (seconds + 4)/10; // round to full seconds
13445                   if( seconds < 60 )
13446                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13447                   else
13448                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13449                 }
13450
13451             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13452                       pvInfoList[i].score >= 0 ? "+" : "",
13453                       pvInfoList[i].score / 100.0,
13454                       pvInfoList[i].depth,
13455                       buf );
13456
13457             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13458
13459             /* Print score/depth */
13460             blank = linelen > 0 && movelen > 0;
13461             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13462                 fprintf(f, "\n");
13463                 linelen = 0;
13464                 blank = 0;
13465             }
13466             if (blank) {
13467                 fprintf(f, " ");
13468                 linelen++;
13469             }
13470             fprintf(f, "%s", move_buffer);
13471             linelen += movelen;
13472         }
13473
13474         i++;
13475     }
13476
13477     /* Start a new line */
13478     if (linelen > 0) fprintf(f, "\n");
13479
13480     /* Print comments after last move */
13481     if (commentList[i] != NULL) {
13482         fprintf(f, "%s\n", commentList[i]);
13483     }
13484
13485     /* Print result */
13486     if (gameInfo.resultDetails != NULL &&
13487         gameInfo.resultDetails[0] != NULLCHAR) {
13488         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13489         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13490            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13491             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13492         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13493     } else {
13494         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13495     }
13496
13497     fclose(f);
13498     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13499     return TRUE;
13500 }
13501
13502 /* Save game in old style and close the file */
13503 int
13504 SaveGameOldStyle (FILE *f)
13505 {
13506     int i, offset;
13507     time_t tm;
13508
13509     tm = time((time_t *) NULL);
13510
13511     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13512     PrintOpponents(f);
13513
13514     if (backwardMostMove > 0 || startedFromSetupPosition) {
13515         fprintf(f, "\n[--------------\n");
13516         PrintPosition(f, backwardMostMove);
13517         fprintf(f, "--------------]\n");
13518     } else {
13519         fprintf(f, "\n");
13520     }
13521
13522     i = backwardMostMove;
13523     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13524
13525     while (i < forwardMostMove) {
13526         if (commentList[i] != NULL) {
13527             fprintf(f, "[%s]\n", commentList[i]);
13528         }
13529
13530         if ((i % 2) == 1) {
13531             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13532             i++;
13533         } else {
13534             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13535             i++;
13536             if (commentList[i] != NULL) {
13537                 fprintf(f, "\n");
13538                 continue;
13539             }
13540             if (i >= forwardMostMove) {
13541                 fprintf(f, "\n");
13542                 break;
13543             }
13544             fprintf(f, "%s\n", parseList[i]);
13545             i++;
13546         }
13547     }
13548
13549     if (commentList[i] != NULL) {
13550         fprintf(f, "[%s]\n", commentList[i]);
13551     }
13552
13553     /* This isn't really the old style, but it's close enough */
13554     if (gameInfo.resultDetails != NULL &&
13555         gameInfo.resultDetails[0] != NULLCHAR) {
13556         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13557                 gameInfo.resultDetails);
13558     } else {
13559         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13560     }
13561
13562     fclose(f);
13563     return TRUE;
13564 }
13565
13566 /* Save the current game to open file f and close the file */
13567 int
13568 SaveGame (FILE *f, int dummy, char *dummy2)
13569 {
13570     if (gameMode == EditPosition) EditPositionDone(TRUE);
13571     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13572     if (appData.oldSaveStyle)
13573       return SaveGameOldStyle(f);
13574     else
13575       return SaveGamePGN(f);
13576 }
13577
13578 /* Save the current position to the given file */
13579 int
13580 SavePositionToFile (char *filename)
13581 {
13582     FILE *f;
13583     char buf[MSG_SIZ];
13584
13585     if (strcmp(filename, "-") == 0) {
13586         return SavePosition(stdout, 0, NULL);
13587     } else {
13588         f = fopen(filename, "a");
13589         if (f == NULL) {
13590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13591             DisplayError(buf, errno);
13592             return FALSE;
13593         } else {
13594             safeStrCpy(buf, lastMsg, MSG_SIZ);
13595             DisplayMessage(_("Waiting for access to save file"), "");
13596             flock(fileno(f), LOCK_EX); // [HGM] lock
13597             DisplayMessage(_("Saving position"), "");
13598             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13599             SavePosition(f, 0, NULL);
13600             DisplayMessage(buf, "");
13601             return TRUE;
13602         }
13603     }
13604 }
13605
13606 /* Save the current position to the given open file and close the file */
13607 int
13608 SavePosition (FILE *f, int dummy, char *dummy2)
13609 {
13610     time_t tm;
13611     char *fen;
13612
13613     if (gameMode == EditPosition) EditPositionDone(TRUE);
13614     if (appData.oldSaveStyle) {
13615         tm = time((time_t *) NULL);
13616
13617         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13618         PrintOpponents(f);
13619         fprintf(f, "[--------------\n");
13620         PrintPosition(f, currentMove);
13621         fprintf(f, "--------------]\n");
13622     } else {
13623         fen = PositionToFEN(currentMove, NULL, 1);
13624         fprintf(f, "%s\n", fen);
13625         free(fen);
13626     }
13627     fclose(f);
13628     return TRUE;
13629 }
13630
13631 void
13632 ReloadCmailMsgEvent (int unregister)
13633 {
13634 #if !WIN32
13635     static char *inFilename = NULL;
13636     static char *outFilename;
13637     int i;
13638     struct stat inbuf, outbuf;
13639     int status;
13640
13641     /* Any registered moves are unregistered if unregister is set, */
13642     /* i.e. invoked by the signal handler */
13643     if (unregister) {
13644         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13645             cmailMoveRegistered[i] = FALSE;
13646             if (cmailCommentList[i] != NULL) {
13647                 free(cmailCommentList[i]);
13648                 cmailCommentList[i] = NULL;
13649             }
13650         }
13651         nCmailMovesRegistered = 0;
13652     }
13653
13654     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13655         cmailResult[i] = CMAIL_NOT_RESULT;
13656     }
13657     nCmailResults = 0;
13658
13659     if (inFilename == NULL) {
13660         /* Because the filenames are static they only get malloced once  */
13661         /* and they never get freed                                      */
13662         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13663         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13664
13665         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13666         sprintf(outFilename, "%s.out", appData.cmailGameName);
13667     }
13668
13669     status = stat(outFilename, &outbuf);
13670     if (status < 0) {
13671         cmailMailedMove = FALSE;
13672     } else {
13673         status = stat(inFilename, &inbuf);
13674         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13675     }
13676
13677     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13678        counts the games, notes how each one terminated, etc.
13679
13680        It would be nice to remove this kludge and instead gather all
13681        the information while building the game list.  (And to keep it
13682        in the game list nodes instead of having a bunch of fixed-size
13683        parallel arrays.)  Note this will require getting each game's
13684        termination from the PGN tags, as the game list builder does
13685        not process the game moves.  --mann
13686        */
13687     cmailMsgLoaded = TRUE;
13688     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13689
13690     /* Load first game in the file or popup game menu */
13691     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13692
13693 #endif /* !WIN32 */
13694     return;
13695 }
13696
13697 int
13698 RegisterMove ()
13699 {
13700     FILE *f;
13701     char string[MSG_SIZ];
13702
13703     if (   cmailMailedMove
13704         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13705         return TRUE;            /* Allow free viewing  */
13706     }
13707
13708     /* Unregister move to ensure that we don't leave RegisterMove        */
13709     /* with the move registered when the conditions for registering no   */
13710     /* longer hold                                                       */
13711     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13712         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13713         nCmailMovesRegistered --;
13714
13715         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13716           {
13717               free(cmailCommentList[lastLoadGameNumber - 1]);
13718               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13719           }
13720     }
13721
13722     if (cmailOldMove == -1) {
13723         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13724         return FALSE;
13725     }
13726
13727     if (currentMove > cmailOldMove + 1) {
13728         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13729         return FALSE;
13730     }
13731
13732     if (currentMove < cmailOldMove) {
13733         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13734         return FALSE;
13735     }
13736
13737     if (forwardMostMove > currentMove) {
13738         /* Silently truncate extra moves */
13739         TruncateGame();
13740     }
13741
13742     if (   (currentMove == cmailOldMove + 1)
13743         || (   (currentMove == cmailOldMove)
13744             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13745                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13746         if (gameInfo.result != GameUnfinished) {
13747             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13748         }
13749
13750         if (commentList[currentMove] != NULL) {
13751             cmailCommentList[lastLoadGameNumber - 1]
13752               = StrSave(commentList[currentMove]);
13753         }
13754         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13755
13756         if (appData.debugMode)
13757           fprintf(debugFP, "Saving %s for game %d\n",
13758                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13759
13760         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13761
13762         f = fopen(string, "w");
13763         if (appData.oldSaveStyle) {
13764             SaveGameOldStyle(f); /* also closes the file */
13765
13766             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13767             f = fopen(string, "w");
13768             SavePosition(f, 0, NULL); /* also closes the file */
13769         } else {
13770             fprintf(f, "{--------------\n");
13771             PrintPosition(f, currentMove);
13772             fprintf(f, "--------------}\n\n");
13773
13774             SaveGame(f, 0, NULL); /* also closes the file*/
13775         }
13776
13777         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13778         nCmailMovesRegistered ++;
13779     } else if (nCmailGames == 1) {
13780         DisplayError(_("You have not made a move yet"), 0);
13781         return FALSE;
13782     }
13783
13784     return TRUE;
13785 }
13786
13787 void
13788 MailMoveEvent ()
13789 {
13790 #if !WIN32
13791     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13792     FILE *commandOutput;
13793     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13794     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13795     int nBuffers;
13796     int i;
13797     int archived;
13798     char *arcDir;
13799
13800     if (! cmailMsgLoaded) {
13801         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13802         return;
13803     }
13804
13805     if (nCmailGames == nCmailResults) {
13806         DisplayError(_("No unfinished games"), 0);
13807         return;
13808     }
13809
13810 #if CMAIL_PROHIBIT_REMAIL
13811     if (cmailMailedMove) {
13812       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);
13813         DisplayError(msg, 0);
13814         return;
13815     }
13816 #endif
13817
13818     if (! (cmailMailedMove || RegisterMove())) return;
13819
13820     if (   cmailMailedMove
13821         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13822       snprintf(string, MSG_SIZ, partCommandString,
13823                appData.debugMode ? " -v" : "", appData.cmailGameName);
13824         commandOutput = popen(string, "r");
13825
13826         if (commandOutput == NULL) {
13827             DisplayError(_("Failed to invoke cmail"), 0);
13828         } else {
13829             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13830                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13831             }
13832             if (nBuffers > 1) {
13833                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13834                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13835                 nBytes = MSG_SIZ - 1;
13836             } else {
13837                 (void) memcpy(msg, buffer, nBytes);
13838             }
13839             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13840
13841             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13842                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13843
13844                 archived = TRUE;
13845                 for (i = 0; i < nCmailGames; i ++) {
13846                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13847                         archived = FALSE;
13848                     }
13849                 }
13850                 if (   archived
13851                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13852                         != NULL)) {
13853                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13854                            arcDir,
13855                            appData.cmailGameName,
13856                            gameInfo.date);
13857                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13858                     cmailMsgLoaded = FALSE;
13859                 }
13860             }
13861
13862             DisplayInformation(msg);
13863             pclose(commandOutput);
13864         }
13865     } else {
13866         if ((*cmailMsg) != '\0') {
13867             DisplayInformation(cmailMsg);
13868         }
13869     }
13870
13871     return;
13872 #endif /* !WIN32 */
13873 }
13874
13875 char *
13876 CmailMsg ()
13877 {
13878 #if WIN32
13879     return NULL;
13880 #else
13881     int  prependComma = 0;
13882     char number[5];
13883     char string[MSG_SIZ];       /* Space for game-list */
13884     int  i;
13885
13886     if (!cmailMsgLoaded) return "";
13887
13888     if (cmailMailedMove) {
13889       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13890     } else {
13891         /* Create a list of games left */
13892       snprintf(string, MSG_SIZ, "[");
13893         for (i = 0; i < nCmailGames; i ++) {
13894             if (! (   cmailMoveRegistered[i]
13895                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13896                 if (prependComma) {
13897                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13898                 } else {
13899                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13900                     prependComma = 1;
13901                 }
13902
13903                 strcat(string, number);
13904             }
13905         }
13906         strcat(string, "]");
13907
13908         if (nCmailMovesRegistered + nCmailResults == 0) {
13909             switch (nCmailGames) {
13910               case 1:
13911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13912                 break;
13913
13914               case 2:
13915                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13916                 break;
13917
13918               default:
13919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13920                          nCmailGames);
13921                 break;
13922             }
13923         } else {
13924             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13925               case 1:
13926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13927                          string);
13928                 break;
13929
13930               case 0:
13931                 if (nCmailResults == nCmailGames) {
13932                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13933                 } else {
13934                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13935                 }
13936                 break;
13937
13938               default:
13939                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13940                          string);
13941             }
13942         }
13943     }
13944     return cmailMsg;
13945 #endif /* WIN32 */
13946 }
13947
13948 void
13949 ResetGameEvent ()
13950 {
13951     if (gameMode == Training)
13952       SetTrainingModeOff();
13953
13954     Reset(TRUE, TRUE);
13955     cmailMsgLoaded = FALSE;
13956     if (appData.icsActive) {
13957       SendToICS(ics_prefix);
13958       SendToICS("refresh\n");
13959     }
13960 }
13961
13962 void
13963 ExitEvent (int status)
13964 {
13965     exiting++;
13966     if (exiting > 2) {
13967       /* Give up on clean exit */
13968       exit(status);
13969     }
13970     if (exiting > 1) {
13971       /* Keep trying for clean exit */
13972       return;
13973     }
13974
13975     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13976
13977     if (telnetISR != NULL) {
13978       RemoveInputSource(telnetISR);
13979     }
13980     if (icsPR != NoProc) {
13981       DestroyChildProcess(icsPR, TRUE);
13982     }
13983
13984     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13985     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13986
13987     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13988     /* make sure this other one finishes before killing it!                  */
13989     if(endingGame) { int count = 0;
13990         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13991         while(endingGame && count++ < 10) DoSleep(1);
13992         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13993     }
13994
13995     /* Kill off chess programs */
13996     if (first.pr != NoProc) {
13997         ExitAnalyzeMode();
13998
13999         DoSleep( appData.delayBeforeQuit );
14000         SendToProgram("quit\n", &first);
14001         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14002     }
14003     if (second.pr != NoProc) {
14004         DoSleep( appData.delayBeforeQuit );
14005         SendToProgram("quit\n", &second);
14006         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14007     }
14008     if (first.isr != NULL) {
14009         RemoveInputSource(first.isr);
14010     }
14011     if (second.isr != NULL) {
14012         RemoveInputSource(second.isr);
14013     }
14014
14015     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14016     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14017
14018     ShutDownFrontEnd();
14019     exit(status);
14020 }
14021
14022 void
14023 PauseEngine (ChessProgramState *cps)
14024 {
14025     SendToProgram("pause\n", cps);
14026     cps->pause = 2;
14027 }
14028
14029 void
14030 UnPauseEngine (ChessProgramState *cps)
14031 {
14032     SendToProgram("resume\n", cps);
14033     cps->pause = 1;
14034 }
14035
14036 void
14037 PauseEvent ()
14038 {
14039     if (appData.debugMode)
14040         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14041     if (pausing) {
14042         pausing = FALSE;
14043         ModeHighlight();
14044         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14045             StartClocks();
14046             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14047                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14048                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14049             }
14050             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14051             HandleMachineMove(stashedInputMove, stalledEngine);
14052             stalledEngine = NULL;
14053             return;
14054         }
14055         if (gameMode == MachinePlaysWhite ||
14056             gameMode == TwoMachinesPlay   ||
14057             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14058             if(first.pause)  UnPauseEngine(&first);
14059             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14060             if(second.pause) UnPauseEngine(&second);
14061             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14062             StartClocks();
14063         } else {
14064             DisplayBothClocks();
14065         }
14066         if (gameMode == PlayFromGameFile) {
14067             if (appData.timeDelay >= 0)
14068                 AutoPlayGameLoop();
14069         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14070             Reset(FALSE, TRUE);
14071             SendToICS(ics_prefix);
14072             SendToICS("refresh\n");
14073         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14074             ForwardInner(forwardMostMove);
14075         }
14076         pauseExamInvalid = FALSE;
14077     } else {
14078         switch (gameMode) {
14079           default:
14080             return;
14081           case IcsExamining:
14082             pauseExamForwardMostMove = forwardMostMove;
14083             pauseExamInvalid = FALSE;
14084             /* fall through */
14085           case IcsObserving:
14086           case IcsPlayingWhite:
14087           case IcsPlayingBlack:
14088             pausing = TRUE;
14089             ModeHighlight();
14090             return;
14091           case PlayFromGameFile:
14092             (void) StopLoadGameTimer();
14093             pausing = TRUE;
14094             ModeHighlight();
14095             break;
14096           case BeginningOfGame:
14097             if (appData.icsActive) return;
14098             /* else fall through */
14099           case MachinePlaysWhite:
14100           case MachinePlaysBlack:
14101           case TwoMachinesPlay:
14102             if (forwardMostMove == 0)
14103               return;           /* don't pause if no one has moved */
14104             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14105                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14106                 if(onMove->pause) {           // thinking engine can be paused
14107                     PauseEngine(onMove);      // do it
14108                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14109                         PauseEngine(onMove->other);
14110                     else
14111                         SendToProgram("easy\n", onMove->other);
14112                     StopClocks();
14113                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14114             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14115                 if(first.pause) {
14116                     PauseEngine(&first);
14117                     StopClocks();
14118                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14119             } else { // human on move, pause pondering by either method
14120                 if(first.pause)
14121                     PauseEngine(&first);
14122                 else if(appData.ponderNextMove)
14123                     SendToProgram("easy\n", &first);
14124                 StopClocks();
14125             }
14126             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14127           case AnalyzeMode:
14128             pausing = TRUE;
14129             ModeHighlight();
14130             break;
14131         }
14132     }
14133 }
14134
14135 void
14136 EditCommentEvent ()
14137 {
14138     char title[MSG_SIZ];
14139
14140     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14141       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14142     } else {
14143       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14144                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14145                parseList[currentMove - 1]);
14146     }
14147
14148     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14149 }
14150
14151
14152 void
14153 EditTagsEvent ()
14154 {
14155     char *tags = PGNTags(&gameInfo);
14156     bookUp = FALSE;
14157     EditTagsPopUp(tags, NULL);
14158     free(tags);
14159 }
14160
14161 void
14162 ToggleSecond ()
14163 {
14164   if(second.analyzing) {
14165     SendToProgram("exit\n", &second);
14166     second.analyzing = FALSE;
14167   } else {
14168     if (second.pr == NoProc) StartChessProgram(&second);
14169     InitChessProgram(&second, FALSE);
14170     FeedMovesToProgram(&second, currentMove);
14171
14172     SendToProgram("analyze\n", &second);
14173     second.analyzing = TRUE;
14174   }
14175 }
14176
14177 /* Toggle ShowThinking */
14178 void
14179 ToggleShowThinking()
14180 {
14181   appData.showThinking = !appData.showThinking;
14182   ShowThinkingEvent();
14183 }
14184
14185 int
14186 AnalyzeModeEvent ()
14187 {
14188     char buf[MSG_SIZ];
14189
14190     if (!first.analysisSupport) {
14191       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14192       DisplayError(buf, 0);
14193       return 0;
14194     }
14195     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14196     if (appData.icsActive) {
14197         if (gameMode != IcsObserving) {
14198           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14199             DisplayError(buf, 0);
14200             /* secure check */
14201             if (appData.icsEngineAnalyze) {
14202                 if (appData.debugMode)
14203                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14204                 ExitAnalyzeMode();
14205                 ModeHighlight();
14206             }
14207             return 0;
14208         }
14209         /* if enable, user wants to disable icsEngineAnalyze */
14210         if (appData.icsEngineAnalyze) {
14211                 ExitAnalyzeMode();
14212                 ModeHighlight();
14213                 return 0;
14214         }
14215         appData.icsEngineAnalyze = TRUE;
14216         if (appData.debugMode)
14217             fprintf(debugFP, "ICS engine analyze starting... \n");
14218     }
14219
14220     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14221     if (appData.noChessProgram || gameMode == AnalyzeMode)
14222       return 0;
14223
14224     if (gameMode != AnalyzeFile) {
14225         if (!appData.icsEngineAnalyze) {
14226                EditGameEvent();
14227                if (gameMode != EditGame) return 0;
14228         }
14229         if (!appData.showThinking) ToggleShowThinking();
14230         ResurrectChessProgram();
14231         SendToProgram("analyze\n", &first);
14232         first.analyzing = TRUE;
14233         /*first.maybeThinking = TRUE;*/
14234         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14235         EngineOutputPopUp();
14236     }
14237     if (!appData.icsEngineAnalyze) {
14238         gameMode = AnalyzeMode;
14239         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14240     }
14241     pausing = FALSE;
14242     ModeHighlight();
14243     SetGameInfo();
14244
14245     StartAnalysisClock();
14246     GetTimeMark(&lastNodeCountTime);
14247     lastNodeCount = 0;
14248     return 1;
14249 }
14250
14251 void
14252 AnalyzeFileEvent ()
14253 {
14254     if (appData.noChessProgram || gameMode == AnalyzeFile)
14255       return;
14256
14257     if (!first.analysisSupport) {
14258       char buf[MSG_SIZ];
14259       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14260       DisplayError(buf, 0);
14261       return;
14262     }
14263
14264     if (gameMode != AnalyzeMode) {
14265         keepInfo = 1; // mere annotating should not alter PGN tags
14266         EditGameEvent();
14267         keepInfo = 0;
14268         if (gameMode != EditGame) return;
14269         if (!appData.showThinking) ToggleShowThinking();
14270         ResurrectChessProgram();
14271         SendToProgram("analyze\n", &first);
14272         first.analyzing = TRUE;
14273         /*first.maybeThinking = TRUE;*/
14274         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14275         EngineOutputPopUp();
14276     }
14277     gameMode = AnalyzeFile;
14278     pausing = FALSE;
14279     ModeHighlight();
14280
14281     StartAnalysisClock();
14282     GetTimeMark(&lastNodeCountTime);
14283     lastNodeCount = 0;
14284     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14285     AnalysisPeriodicEvent(1);
14286 }
14287
14288 void
14289 MachineWhiteEvent ()
14290 {
14291     char buf[MSG_SIZ];
14292     char *bookHit = NULL;
14293
14294     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14295       return;
14296
14297
14298     if (gameMode == PlayFromGameFile ||
14299         gameMode == TwoMachinesPlay  ||
14300         gameMode == Training         ||
14301         gameMode == AnalyzeMode      ||
14302         gameMode == EndOfGame)
14303         EditGameEvent();
14304
14305     if (gameMode == EditPosition)
14306         EditPositionDone(TRUE);
14307
14308     if (!WhiteOnMove(currentMove)) {
14309         DisplayError(_("It is not White's turn"), 0);
14310         return;
14311     }
14312
14313     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14314       ExitAnalyzeMode();
14315
14316     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14317         gameMode == AnalyzeFile)
14318         TruncateGame();
14319
14320     ResurrectChessProgram();    /* in case it isn't running */
14321     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14322         gameMode = MachinePlaysWhite;
14323         ResetClocks();
14324     } else
14325     gameMode = MachinePlaysWhite;
14326     pausing = FALSE;
14327     ModeHighlight();
14328     SetGameInfo();
14329     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14330     DisplayTitle(buf);
14331     if (first.sendName) {
14332       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14333       SendToProgram(buf, &first);
14334     }
14335     if (first.sendTime) {
14336       if (first.useColors) {
14337         SendToProgram("black\n", &first); /*gnu kludge*/
14338       }
14339       SendTimeRemaining(&first, TRUE);
14340     }
14341     if (first.useColors) {
14342       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14343     }
14344     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14345     SetMachineThinkingEnables();
14346     first.maybeThinking = TRUE;
14347     StartClocks();
14348     firstMove = FALSE;
14349
14350     if (appData.autoFlipView && !flipView) {
14351       flipView = !flipView;
14352       DrawPosition(FALSE, NULL);
14353       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14354     }
14355
14356     if(bookHit) { // [HGM] book: simulate book reply
14357         static char bookMove[MSG_SIZ]; // a bit generous?
14358
14359         programStats.nodes = programStats.depth = programStats.time =
14360         programStats.score = programStats.got_only_move = 0;
14361         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14362
14363         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14364         strcat(bookMove, bookHit);
14365         HandleMachineMove(bookMove, &first);
14366     }
14367 }
14368
14369 void
14370 MachineBlackEvent ()
14371 {
14372   char buf[MSG_SIZ];
14373   char *bookHit = NULL;
14374
14375     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14376         return;
14377
14378
14379     if (gameMode == PlayFromGameFile ||
14380         gameMode == TwoMachinesPlay  ||
14381         gameMode == Training         ||
14382         gameMode == AnalyzeMode      ||
14383         gameMode == EndOfGame)
14384         EditGameEvent();
14385
14386     if (gameMode == EditPosition)
14387         EditPositionDone(TRUE);
14388
14389     if (WhiteOnMove(currentMove)) {
14390         DisplayError(_("It is not Black's turn"), 0);
14391         return;
14392     }
14393
14394     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14395       ExitAnalyzeMode();
14396
14397     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14398         gameMode == AnalyzeFile)
14399         TruncateGame();
14400
14401     ResurrectChessProgram();    /* in case it isn't running */
14402     gameMode = MachinePlaysBlack;
14403     pausing = FALSE;
14404     ModeHighlight();
14405     SetGameInfo();
14406     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14407     DisplayTitle(buf);
14408     if (first.sendName) {
14409       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14410       SendToProgram(buf, &first);
14411     }
14412     if (first.sendTime) {
14413       if (first.useColors) {
14414         SendToProgram("white\n", &first); /*gnu kludge*/
14415       }
14416       SendTimeRemaining(&first, FALSE);
14417     }
14418     if (first.useColors) {
14419       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14420     }
14421     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14422     SetMachineThinkingEnables();
14423     first.maybeThinking = TRUE;
14424     StartClocks();
14425
14426     if (appData.autoFlipView && flipView) {
14427       flipView = !flipView;
14428       DrawPosition(FALSE, NULL);
14429       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14430     }
14431     if(bookHit) { // [HGM] book: simulate book reply
14432         static char bookMove[MSG_SIZ]; // a bit generous?
14433
14434         programStats.nodes = programStats.depth = programStats.time =
14435         programStats.score = programStats.got_only_move = 0;
14436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14437
14438         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14439         strcat(bookMove, bookHit);
14440         HandleMachineMove(bookMove, &first);
14441     }
14442 }
14443
14444
14445 void
14446 DisplayTwoMachinesTitle ()
14447 {
14448     char buf[MSG_SIZ];
14449     if (appData.matchGames > 0) {
14450         if(appData.tourneyFile[0]) {
14451           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14452                    gameInfo.white, _("vs."), gameInfo.black,
14453                    nextGame+1, appData.matchGames+1,
14454                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14455         } else
14456         if (first.twoMachinesColor[0] == 'w') {
14457           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14458                    gameInfo.white, _("vs."),  gameInfo.black,
14459                    first.matchWins, second.matchWins,
14460                    matchGame - 1 - (first.matchWins + second.matchWins));
14461         } else {
14462           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14463                    gameInfo.white, _("vs."), gameInfo.black,
14464                    second.matchWins, first.matchWins,
14465                    matchGame - 1 - (first.matchWins + second.matchWins));
14466         }
14467     } else {
14468       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14469     }
14470     DisplayTitle(buf);
14471 }
14472
14473 void
14474 SettingsMenuIfReady ()
14475 {
14476   if (second.lastPing != second.lastPong) {
14477     DisplayMessage("", _("Waiting for second chess program"));
14478     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14479     return;
14480   }
14481   ThawUI();
14482   DisplayMessage("", "");
14483   SettingsPopUp(&second);
14484 }
14485
14486 int
14487 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14488 {
14489     char buf[MSG_SIZ];
14490     if (cps->pr == NoProc) {
14491         StartChessProgram(cps);
14492         if (cps->protocolVersion == 1) {
14493           retry();
14494           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14495         } else {
14496           /* kludge: allow timeout for initial "feature" command */
14497           if(retry != TwoMachinesEventIfReady) FreezeUI();
14498           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14499           DisplayMessage("", buf);
14500           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14501         }
14502         return 1;
14503     }
14504     return 0;
14505 }
14506
14507 void
14508 TwoMachinesEvent P((void))
14509 {
14510     int i;
14511     char buf[MSG_SIZ];
14512     ChessProgramState *onmove;
14513     char *bookHit = NULL;
14514     static int stalling = 0;
14515     TimeMark now;
14516     long wait;
14517
14518     if (appData.noChessProgram) return;
14519
14520     switch (gameMode) {
14521       case TwoMachinesPlay:
14522         return;
14523       case MachinePlaysWhite:
14524       case MachinePlaysBlack:
14525         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14526             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14527             return;
14528         }
14529         /* fall through */
14530       case BeginningOfGame:
14531       case PlayFromGameFile:
14532       case EndOfGame:
14533         EditGameEvent();
14534         if (gameMode != EditGame) return;
14535         break;
14536       case EditPosition:
14537         EditPositionDone(TRUE);
14538         break;
14539       case AnalyzeMode:
14540       case AnalyzeFile:
14541         ExitAnalyzeMode();
14542         break;
14543       case EditGame:
14544       default:
14545         break;
14546     }
14547
14548 //    forwardMostMove = currentMove;
14549     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14550     startingEngine = TRUE;
14551
14552     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14553
14554     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14555     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14556       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14557       return;
14558     }
14559     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14560
14561     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14562                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14563         startingEngine = FALSE;
14564         DisplayError("second engine does not play this", 0);
14565         return;
14566     }
14567
14568     if(!stalling) {
14569       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14570       SendToProgram("force\n", &second);
14571       stalling = 1;
14572       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14573       return;
14574     }
14575     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14576     if(appData.matchPause>10000 || appData.matchPause<10)
14577                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14578     wait = SubtractTimeMarks(&now, &pauseStart);
14579     if(wait < appData.matchPause) {
14580         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14581         return;
14582     }
14583     // we are now committed to starting the game
14584     stalling = 0;
14585     DisplayMessage("", "");
14586     if (startedFromSetupPosition) {
14587         SendBoard(&second, backwardMostMove);
14588     if (appData.debugMode) {
14589         fprintf(debugFP, "Two Machines\n");
14590     }
14591     }
14592     for (i = backwardMostMove; i < forwardMostMove; i++) {
14593         SendMoveToProgram(i, &second);
14594     }
14595
14596     gameMode = TwoMachinesPlay;
14597     pausing = startingEngine = FALSE;
14598     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14599     SetGameInfo();
14600     DisplayTwoMachinesTitle();
14601     firstMove = TRUE;
14602     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14603         onmove = &first;
14604     } else {
14605         onmove = &second;
14606     }
14607     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14608     SendToProgram(first.computerString, &first);
14609     if (first.sendName) {
14610       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14611       SendToProgram(buf, &first);
14612     }
14613     SendToProgram(second.computerString, &second);
14614     if (second.sendName) {
14615       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14616       SendToProgram(buf, &second);
14617     }
14618
14619     ResetClocks();
14620     if (!first.sendTime || !second.sendTime) {
14621         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14622         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14623     }
14624     if (onmove->sendTime) {
14625       if (onmove->useColors) {
14626         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14627       }
14628       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14629     }
14630     if (onmove->useColors) {
14631       SendToProgram(onmove->twoMachinesColor, onmove);
14632     }
14633     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14634 //    SendToProgram("go\n", onmove);
14635     onmove->maybeThinking = TRUE;
14636     SetMachineThinkingEnables();
14637
14638     StartClocks();
14639
14640     if(bookHit) { // [HGM] book: simulate book reply
14641         static char bookMove[MSG_SIZ]; // a bit generous?
14642
14643         programStats.nodes = programStats.depth = programStats.time =
14644         programStats.score = programStats.got_only_move = 0;
14645         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14646
14647         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14648         strcat(bookMove, bookHit);
14649         savedMessage = bookMove; // args for deferred call
14650         savedState = onmove;
14651         ScheduleDelayedEvent(DeferredBookMove, 1);
14652     }
14653 }
14654
14655 void
14656 TrainingEvent ()
14657 {
14658     if (gameMode == Training) {
14659       SetTrainingModeOff();
14660       gameMode = PlayFromGameFile;
14661       DisplayMessage("", _("Training mode off"));
14662     } else {
14663       gameMode = Training;
14664       animateTraining = appData.animate;
14665
14666       /* make sure we are not already at the end of the game */
14667       if (currentMove < forwardMostMove) {
14668         SetTrainingModeOn();
14669         DisplayMessage("", _("Training mode on"));
14670       } else {
14671         gameMode = PlayFromGameFile;
14672         DisplayError(_("Already at end of game"), 0);
14673       }
14674     }
14675     ModeHighlight();
14676 }
14677
14678 void
14679 IcsClientEvent ()
14680 {
14681     if (!appData.icsActive) return;
14682     switch (gameMode) {
14683       case IcsPlayingWhite:
14684       case IcsPlayingBlack:
14685       case IcsObserving:
14686       case IcsIdle:
14687       case BeginningOfGame:
14688       case IcsExamining:
14689         return;
14690
14691       case EditGame:
14692         break;
14693
14694       case EditPosition:
14695         EditPositionDone(TRUE);
14696         break;
14697
14698       case AnalyzeMode:
14699       case AnalyzeFile:
14700         ExitAnalyzeMode();
14701         break;
14702
14703       default:
14704         EditGameEvent();
14705         break;
14706     }
14707
14708     gameMode = IcsIdle;
14709     ModeHighlight();
14710     return;
14711 }
14712
14713 void
14714 EditGameEvent ()
14715 {
14716     int i;
14717
14718     switch (gameMode) {
14719       case Training:
14720         SetTrainingModeOff();
14721         break;
14722       case MachinePlaysWhite:
14723       case MachinePlaysBlack:
14724       case BeginningOfGame:
14725         SendToProgram("force\n", &first);
14726         SetUserThinkingEnables();
14727         break;
14728       case PlayFromGameFile:
14729         (void) StopLoadGameTimer();
14730         if (gameFileFP != NULL) {
14731             gameFileFP = NULL;
14732         }
14733         break;
14734       case EditPosition:
14735         EditPositionDone(TRUE);
14736         break;
14737       case AnalyzeMode:
14738       case AnalyzeFile:
14739         ExitAnalyzeMode();
14740         SendToProgram("force\n", &first);
14741         break;
14742       case TwoMachinesPlay:
14743         GameEnds(EndOfFile, NULL, GE_PLAYER);
14744         ResurrectChessProgram();
14745         SetUserThinkingEnables();
14746         break;
14747       case EndOfGame:
14748         ResurrectChessProgram();
14749         break;
14750       case IcsPlayingBlack:
14751       case IcsPlayingWhite:
14752         DisplayError(_("Warning: You are still playing a game"), 0);
14753         break;
14754       case IcsObserving:
14755         DisplayError(_("Warning: You are still observing a game"), 0);
14756         break;
14757       case IcsExamining:
14758         DisplayError(_("Warning: You are still examining a game"), 0);
14759         break;
14760       case IcsIdle:
14761         break;
14762       case EditGame:
14763       default:
14764         return;
14765     }
14766
14767     pausing = FALSE;
14768     StopClocks();
14769     first.offeredDraw = second.offeredDraw = 0;
14770
14771     if (gameMode == PlayFromGameFile) {
14772         whiteTimeRemaining = timeRemaining[0][currentMove];
14773         blackTimeRemaining = timeRemaining[1][currentMove];
14774         DisplayTitle("");
14775     }
14776
14777     if (gameMode == MachinePlaysWhite ||
14778         gameMode == MachinePlaysBlack ||
14779         gameMode == TwoMachinesPlay ||
14780         gameMode == EndOfGame) {
14781         i = forwardMostMove;
14782         while (i > currentMove) {
14783             SendToProgram("undo\n", &first);
14784             i--;
14785         }
14786         if(!adjustedClock) {
14787         whiteTimeRemaining = timeRemaining[0][currentMove];
14788         blackTimeRemaining = timeRemaining[1][currentMove];
14789         DisplayBothClocks();
14790         }
14791         if (whiteFlag || blackFlag) {
14792             whiteFlag = blackFlag = 0;
14793         }
14794         DisplayTitle("");
14795     }
14796
14797     gameMode = EditGame;
14798     ModeHighlight();
14799     SetGameInfo();
14800 }
14801
14802
14803 void
14804 EditPositionEvent ()
14805 {
14806     if (gameMode == EditPosition) {
14807         EditGameEvent();
14808         return;
14809     }
14810
14811     EditGameEvent();
14812     if (gameMode != EditGame) return;
14813
14814     gameMode = EditPosition;
14815     ModeHighlight();
14816     SetGameInfo();
14817     if (currentMove > 0)
14818       CopyBoard(boards[0], boards[currentMove]);
14819
14820     blackPlaysFirst = !WhiteOnMove(currentMove);
14821     ResetClocks();
14822     currentMove = forwardMostMove = backwardMostMove = 0;
14823     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14824     DisplayMove(-1);
14825     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14826 }
14827
14828 void
14829 ExitAnalyzeMode ()
14830 {
14831     /* [DM] icsEngineAnalyze - possible call from other functions */
14832     if (appData.icsEngineAnalyze) {
14833         appData.icsEngineAnalyze = FALSE;
14834
14835         DisplayMessage("",_("Close ICS engine analyze..."));
14836     }
14837     if (first.analysisSupport && first.analyzing) {
14838       SendToBoth("exit\n");
14839       first.analyzing = second.analyzing = FALSE;
14840     }
14841     thinkOutput[0] = NULLCHAR;
14842 }
14843
14844 void
14845 EditPositionDone (Boolean fakeRights)
14846 {
14847     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14848
14849     startedFromSetupPosition = TRUE;
14850     InitChessProgram(&first, FALSE);
14851     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14852       boards[0][EP_STATUS] = EP_NONE;
14853       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14854       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14855         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14856         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14857       } else boards[0][CASTLING][2] = NoRights;
14858       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14859         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14860         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14861       } else boards[0][CASTLING][5] = NoRights;
14862       if(gameInfo.variant == VariantSChess) {
14863         int i;
14864         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14865           boards[0][VIRGIN][i] = 0;
14866           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14867           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14868         }
14869       }
14870     }
14871     SendToProgram("force\n", &first);
14872     if (blackPlaysFirst) {
14873         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14874         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14875         currentMove = forwardMostMove = backwardMostMove = 1;
14876         CopyBoard(boards[1], boards[0]);
14877     } else {
14878         currentMove = forwardMostMove = backwardMostMove = 0;
14879     }
14880     SendBoard(&first, forwardMostMove);
14881     if (appData.debugMode) {
14882         fprintf(debugFP, "EditPosDone\n");
14883     }
14884     DisplayTitle("");
14885     DisplayMessage("", "");
14886     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14887     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14888     gameMode = EditGame;
14889     ModeHighlight();
14890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14891     ClearHighlights(); /* [AS] */
14892 }
14893
14894 /* Pause for `ms' milliseconds */
14895 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14896 void
14897 TimeDelay (long ms)
14898 {
14899     TimeMark m1, m2;
14900
14901     GetTimeMark(&m1);
14902     do {
14903         GetTimeMark(&m2);
14904     } while (SubtractTimeMarks(&m2, &m1) < ms);
14905 }
14906
14907 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14908 void
14909 SendMultiLineToICS (char *buf)
14910 {
14911     char temp[MSG_SIZ+1], *p;
14912     int len;
14913
14914     len = strlen(buf);
14915     if (len > MSG_SIZ)
14916       len = MSG_SIZ;
14917
14918     strncpy(temp, buf, len);
14919     temp[len] = 0;
14920
14921     p = temp;
14922     while (*p) {
14923         if (*p == '\n' || *p == '\r')
14924           *p = ' ';
14925         ++p;
14926     }
14927
14928     strcat(temp, "\n");
14929     SendToICS(temp);
14930     SendToPlayer(temp, strlen(temp));
14931 }
14932
14933 void
14934 SetWhiteToPlayEvent ()
14935 {
14936     if (gameMode == EditPosition) {
14937         blackPlaysFirst = FALSE;
14938         DisplayBothClocks();    /* works because currentMove is 0 */
14939     } else if (gameMode == IcsExamining) {
14940         SendToICS(ics_prefix);
14941         SendToICS("tomove white\n");
14942     }
14943 }
14944
14945 void
14946 SetBlackToPlayEvent ()
14947 {
14948     if (gameMode == EditPosition) {
14949         blackPlaysFirst = TRUE;
14950         currentMove = 1;        /* kludge */
14951         DisplayBothClocks();
14952         currentMove = 0;
14953     } else if (gameMode == IcsExamining) {
14954         SendToICS(ics_prefix);
14955         SendToICS("tomove black\n");
14956     }
14957 }
14958
14959 void
14960 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14961 {
14962     char buf[MSG_SIZ];
14963     ChessSquare piece = boards[0][y][x];
14964     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14965     static int lastVariant;
14966
14967     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14968
14969     switch (selection) {
14970       case ClearBoard:
14971         CopyBoard(currentBoard, boards[0]);
14972         CopyBoard(menuBoard, initialPosition);
14973         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14974             SendToICS(ics_prefix);
14975             SendToICS("bsetup clear\n");
14976         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14977             SendToICS(ics_prefix);
14978             SendToICS("clearboard\n");
14979         } else {
14980             int nonEmpty = 0;
14981             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14982                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14983                 for (y = 0; y < BOARD_HEIGHT; y++) {
14984                     if (gameMode == IcsExamining) {
14985                         if (boards[currentMove][y][x] != EmptySquare) {
14986                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14987                                     AAA + x, ONE + y);
14988                             SendToICS(buf);
14989                         }
14990                     } else {
14991                         if(boards[0][y][x] != p) nonEmpty++;
14992                         boards[0][y][x] = p;
14993                     }
14994                 }
14995                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14996             }
14997             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14998                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14999                     ChessSquare p = menuBoard[0][x];
15000                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15001                     p = menuBoard[BOARD_HEIGHT-1][x];
15002                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15003                 }
15004                 DisplayMessage("Clicking clock again restores position", "");
15005                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15006                 if(!nonEmpty) { // asked to clear an empty board
15007                     CopyBoard(boards[0], menuBoard);
15008                 } else
15009                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15010                     CopyBoard(boards[0], initialPosition);
15011                 } else
15012                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15013                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15014                     CopyBoard(boards[0], erasedBoard);
15015                 } else
15016                     CopyBoard(erasedBoard, currentBoard);
15017
15018             }
15019         }
15020         if (gameMode == EditPosition) {
15021             DrawPosition(FALSE, boards[0]);
15022         }
15023         break;
15024
15025       case WhitePlay:
15026         SetWhiteToPlayEvent();
15027         break;
15028
15029       case BlackPlay:
15030         SetBlackToPlayEvent();
15031         break;
15032
15033       case EmptySquare:
15034         if (gameMode == IcsExamining) {
15035             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15036             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15037             SendToICS(buf);
15038         } else {
15039             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15040                 if(x == BOARD_LEFT-2) {
15041                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15042                     boards[0][y][1] = 0;
15043                 } else
15044                 if(x == BOARD_RGHT+1) {
15045                     if(y >= gameInfo.holdingsSize) break;
15046                     boards[0][y][BOARD_WIDTH-2] = 0;
15047                 } else break;
15048             }
15049             boards[0][y][x] = EmptySquare;
15050             DrawPosition(FALSE, boards[0]);
15051         }
15052         break;
15053
15054       case PromotePiece:
15055         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15056            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15057             selection = (ChessSquare) (PROMOTED piece);
15058         } else if(piece == EmptySquare) selection = WhiteSilver;
15059         else selection = (ChessSquare)((int)piece - 1);
15060         goto defaultlabel;
15061
15062       case DemotePiece:
15063         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15064            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15065             selection = (ChessSquare) (DEMOTED piece);
15066         } else if(piece == EmptySquare) selection = BlackSilver;
15067         else selection = (ChessSquare)((int)piece + 1);
15068         goto defaultlabel;
15069
15070       case WhiteQueen:
15071       case BlackQueen:
15072         if(gameInfo.variant == VariantShatranj ||
15073            gameInfo.variant == VariantXiangqi  ||
15074            gameInfo.variant == VariantCourier  ||
15075            gameInfo.variant == VariantASEAN    ||
15076            gameInfo.variant == VariantMakruk     )
15077             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15078         goto defaultlabel;
15079
15080       case WhiteKing:
15081       case BlackKing:
15082         if(gameInfo.variant == VariantXiangqi)
15083             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15084         if(gameInfo.variant == VariantKnightmate)
15085             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15086       default:
15087         defaultlabel:
15088         if (gameMode == IcsExamining) {
15089             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15090             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15091                      PieceToChar(selection), AAA + x, ONE + y);
15092             SendToICS(buf);
15093         } else {
15094             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15095                 int n;
15096                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15097                     n = PieceToNumber(selection - BlackPawn);
15098                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15099                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15100                     boards[0][BOARD_HEIGHT-1-n][1]++;
15101                 } else
15102                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15103                     n = PieceToNumber(selection);
15104                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15105                     boards[0][n][BOARD_WIDTH-1] = selection;
15106                     boards[0][n][BOARD_WIDTH-2]++;
15107                 }
15108             } else
15109             boards[0][y][x] = selection;
15110             DrawPosition(TRUE, boards[0]);
15111             ClearHighlights();
15112             fromX = fromY = -1;
15113         }
15114         break;
15115     }
15116 }
15117
15118
15119 void
15120 DropMenuEvent (ChessSquare selection, int x, int y)
15121 {
15122     ChessMove moveType;
15123
15124     switch (gameMode) {
15125       case IcsPlayingWhite:
15126       case MachinePlaysBlack:
15127         if (!WhiteOnMove(currentMove)) {
15128             DisplayMoveError(_("It is Black's turn"));
15129             return;
15130         }
15131         moveType = WhiteDrop;
15132         break;
15133       case IcsPlayingBlack:
15134       case MachinePlaysWhite:
15135         if (WhiteOnMove(currentMove)) {
15136             DisplayMoveError(_("It is White's turn"));
15137             return;
15138         }
15139         moveType = BlackDrop;
15140         break;
15141       case EditGame:
15142         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15143         break;
15144       default:
15145         return;
15146     }
15147
15148     if (moveType == BlackDrop && selection < BlackPawn) {
15149       selection = (ChessSquare) ((int) selection
15150                                  + (int) BlackPawn - (int) WhitePawn);
15151     }
15152     if (boards[currentMove][y][x] != EmptySquare) {
15153         DisplayMoveError(_("That square is occupied"));
15154         return;
15155     }
15156
15157     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15158 }
15159
15160 void
15161 AcceptEvent ()
15162 {
15163     /* Accept a pending offer of any kind from opponent */
15164
15165     if (appData.icsActive) {
15166         SendToICS(ics_prefix);
15167         SendToICS("accept\n");
15168     } else if (cmailMsgLoaded) {
15169         if (currentMove == cmailOldMove &&
15170             commentList[cmailOldMove] != NULL &&
15171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15172                    "Black offers a draw" : "White offers a draw")) {
15173             TruncateGame();
15174             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15175             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15176         } else {
15177             DisplayError(_("There is no pending offer on this move"), 0);
15178             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15179         }
15180     } else {
15181         /* Not used for offers from chess program */
15182     }
15183 }
15184
15185 void
15186 DeclineEvent ()
15187 {
15188     /* Decline a pending offer of any kind from opponent */
15189
15190     if (appData.icsActive) {
15191         SendToICS(ics_prefix);
15192         SendToICS("decline\n");
15193     } else if (cmailMsgLoaded) {
15194         if (currentMove == cmailOldMove &&
15195             commentList[cmailOldMove] != NULL &&
15196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15197                    "Black offers a draw" : "White offers a draw")) {
15198 #ifdef NOTDEF
15199             AppendComment(cmailOldMove, "Draw declined", TRUE);
15200             DisplayComment(cmailOldMove - 1, "Draw declined");
15201 #endif /*NOTDEF*/
15202         } else {
15203             DisplayError(_("There is no pending offer on this move"), 0);
15204         }
15205     } else {
15206         /* Not used for offers from chess program */
15207     }
15208 }
15209
15210 void
15211 RematchEvent ()
15212 {
15213     /* Issue ICS rematch command */
15214     if (appData.icsActive) {
15215         SendToICS(ics_prefix);
15216         SendToICS("rematch\n");
15217     }
15218 }
15219
15220 void
15221 CallFlagEvent ()
15222 {
15223     /* Call your opponent's flag (claim a win on time) */
15224     if (appData.icsActive) {
15225         SendToICS(ics_prefix);
15226         SendToICS("flag\n");
15227     } else {
15228         switch (gameMode) {
15229           default:
15230             return;
15231           case MachinePlaysWhite:
15232             if (whiteFlag) {
15233                 if (blackFlag)
15234                   GameEnds(GameIsDrawn, "Both players ran out of time",
15235                            GE_PLAYER);
15236                 else
15237                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15238             } else {
15239                 DisplayError(_("Your opponent is not out of time"), 0);
15240             }
15241             break;
15242           case MachinePlaysBlack:
15243             if (blackFlag) {
15244                 if (whiteFlag)
15245                   GameEnds(GameIsDrawn, "Both players ran out of time",
15246                            GE_PLAYER);
15247                 else
15248                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15249             } else {
15250                 DisplayError(_("Your opponent is not out of time"), 0);
15251             }
15252             break;
15253         }
15254     }
15255 }
15256
15257 void
15258 ClockClick (int which)
15259 {       // [HGM] code moved to back-end from winboard.c
15260         if(which) { // black clock
15261           if (gameMode == EditPosition || gameMode == IcsExamining) {
15262             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15263             SetBlackToPlayEvent();
15264           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15265           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15266           } else if (shiftKey) {
15267             AdjustClock(which, -1);
15268           } else if (gameMode == IcsPlayingWhite ||
15269                      gameMode == MachinePlaysBlack) {
15270             CallFlagEvent();
15271           }
15272         } else { // white clock
15273           if (gameMode == EditPosition || gameMode == IcsExamining) {
15274             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15275             SetWhiteToPlayEvent();
15276           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15277           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15278           } else if (shiftKey) {
15279             AdjustClock(which, -1);
15280           } else if (gameMode == IcsPlayingBlack ||
15281                    gameMode == MachinePlaysWhite) {
15282             CallFlagEvent();
15283           }
15284         }
15285 }
15286
15287 void
15288 DrawEvent ()
15289 {
15290     /* Offer draw or accept pending draw offer from opponent */
15291
15292     if (appData.icsActive) {
15293         /* Note: tournament rules require draw offers to be
15294            made after you make your move but before you punch
15295            your clock.  Currently ICS doesn't let you do that;
15296            instead, you immediately punch your clock after making
15297            a move, but you can offer a draw at any time. */
15298
15299         SendToICS(ics_prefix);
15300         SendToICS("draw\n");
15301         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15302     } else if (cmailMsgLoaded) {
15303         if (currentMove == cmailOldMove &&
15304             commentList[cmailOldMove] != NULL &&
15305             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15306                    "Black offers a draw" : "White offers a draw")) {
15307             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15308             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15309         } else if (currentMove == cmailOldMove + 1) {
15310             char *offer = WhiteOnMove(cmailOldMove) ?
15311               "White offers a draw" : "Black offers a draw";
15312             AppendComment(currentMove, offer, TRUE);
15313             DisplayComment(currentMove - 1, offer);
15314             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15315         } else {
15316             DisplayError(_("You must make your move before offering a draw"), 0);
15317             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15318         }
15319     } else if (first.offeredDraw) {
15320         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15321     } else {
15322         if (first.sendDrawOffers) {
15323             SendToProgram("draw\n", &first);
15324             userOfferedDraw = TRUE;
15325         }
15326     }
15327 }
15328
15329 void
15330 AdjournEvent ()
15331 {
15332     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15333
15334     if (appData.icsActive) {
15335         SendToICS(ics_prefix);
15336         SendToICS("adjourn\n");
15337     } else {
15338         /* Currently GNU Chess doesn't offer or accept Adjourns */
15339     }
15340 }
15341
15342
15343 void
15344 AbortEvent ()
15345 {
15346     /* Offer Abort or accept pending Abort offer from opponent */
15347
15348     if (appData.icsActive) {
15349         SendToICS(ics_prefix);
15350         SendToICS("abort\n");
15351     } else {
15352         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15353     }
15354 }
15355
15356 void
15357 ResignEvent ()
15358 {
15359     /* Resign.  You can do this even if it's not your turn. */
15360
15361     if (appData.icsActive) {
15362         SendToICS(ics_prefix);
15363         SendToICS("resign\n");
15364     } else {
15365         switch (gameMode) {
15366           case MachinePlaysWhite:
15367             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15368             break;
15369           case MachinePlaysBlack:
15370             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15371             break;
15372           case EditGame:
15373             if (cmailMsgLoaded) {
15374                 TruncateGame();
15375                 if (WhiteOnMove(cmailOldMove)) {
15376                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15377                 } else {
15378                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15379                 }
15380                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15381             }
15382             break;
15383           default:
15384             break;
15385         }
15386     }
15387 }
15388
15389
15390 void
15391 StopObservingEvent ()
15392 {
15393     /* Stop observing current games */
15394     SendToICS(ics_prefix);
15395     SendToICS("unobserve\n");
15396 }
15397
15398 void
15399 StopExaminingEvent ()
15400 {
15401     /* Stop observing current game */
15402     SendToICS(ics_prefix);
15403     SendToICS("unexamine\n");
15404 }
15405
15406 void
15407 ForwardInner (int target)
15408 {
15409     int limit; int oldSeekGraphUp = seekGraphUp;
15410
15411     if (appData.debugMode)
15412         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15413                 target, currentMove, forwardMostMove);
15414
15415     if (gameMode == EditPosition)
15416       return;
15417
15418     seekGraphUp = FALSE;
15419     MarkTargetSquares(1);
15420
15421     if (gameMode == PlayFromGameFile && !pausing)
15422       PauseEvent();
15423
15424     if (gameMode == IcsExamining && pausing)
15425       limit = pauseExamForwardMostMove;
15426     else
15427       limit = forwardMostMove;
15428
15429     if (target > limit) target = limit;
15430
15431     if (target > 0 && moveList[target - 1][0]) {
15432         int fromX, fromY, toX, toY;
15433         toX = moveList[target - 1][2] - AAA;
15434         toY = moveList[target - 1][3] - ONE;
15435         if (moveList[target - 1][1] == '@') {
15436             if (appData.highlightLastMove) {
15437                 SetHighlights(-1, -1, toX, toY);
15438             }
15439         } else {
15440             fromX = moveList[target - 1][0] - AAA;
15441             fromY = moveList[target - 1][1] - ONE;
15442             if (target == currentMove + 1) {
15443                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15444             }
15445             if (appData.highlightLastMove) {
15446                 SetHighlights(fromX, fromY, toX, toY);
15447             }
15448         }
15449     }
15450     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15451         gameMode == Training || gameMode == PlayFromGameFile ||
15452         gameMode == AnalyzeFile) {
15453         while (currentMove < target) {
15454             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15455             SendMoveToProgram(currentMove++, &first);
15456         }
15457     } else {
15458         currentMove = target;
15459     }
15460
15461     if (gameMode == EditGame || gameMode == EndOfGame) {
15462         whiteTimeRemaining = timeRemaining[0][currentMove];
15463         blackTimeRemaining = timeRemaining[1][currentMove];
15464     }
15465     DisplayBothClocks();
15466     DisplayMove(currentMove - 1);
15467     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15468     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15469     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15470         DisplayComment(currentMove - 1, commentList[currentMove]);
15471     }
15472     ClearMap(); // [HGM] exclude: invalidate map
15473 }
15474
15475
15476 void
15477 ForwardEvent ()
15478 {
15479     if (gameMode == IcsExamining && !pausing) {
15480         SendToICS(ics_prefix);
15481         SendToICS("forward\n");
15482     } else {
15483         ForwardInner(currentMove + 1);
15484     }
15485 }
15486
15487 void
15488 ToEndEvent ()
15489 {
15490     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15491         /* to optimze, we temporarily turn off analysis mode while we feed
15492          * the remaining moves to the engine. Otherwise we get analysis output
15493          * after each move.
15494          */
15495         if (first.analysisSupport) {
15496           SendToProgram("exit\nforce\n", &first);
15497           first.analyzing = FALSE;
15498         }
15499     }
15500
15501     if (gameMode == IcsExamining && !pausing) {
15502         SendToICS(ics_prefix);
15503         SendToICS("forward 999999\n");
15504     } else {
15505         ForwardInner(forwardMostMove);
15506     }
15507
15508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15509         /* we have fed all the moves, so reactivate analysis mode */
15510         SendToProgram("analyze\n", &first);
15511         first.analyzing = TRUE;
15512         /*first.maybeThinking = TRUE;*/
15513         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15514     }
15515 }
15516
15517 void
15518 BackwardInner (int target)
15519 {
15520     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15521
15522     if (appData.debugMode)
15523         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15524                 target, currentMove, forwardMostMove);
15525
15526     if (gameMode == EditPosition) return;
15527     seekGraphUp = FALSE;
15528     MarkTargetSquares(1);
15529     if (currentMove <= backwardMostMove) {
15530         ClearHighlights();
15531         DrawPosition(full_redraw, boards[currentMove]);
15532         return;
15533     }
15534     if (gameMode == PlayFromGameFile && !pausing)
15535       PauseEvent();
15536
15537     if (moveList[target][0]) {
15538         int fromX, fromY, toX, toY;
15539         toX = moveList[target][2] - AAA;
15540         toY = moveList[target][3] - ONE;
15541         if (moveList[target][1] == '@') {
15542             if (appData.highlightLastMove) {
15543                 SetHighlights(-1, -1, toX, toY);
15544             }
15545         } else {
15546             fromX = moveList[target][0] - AAA;
15547             fromY = moveList[target][1] - ONE;
15548             if (target == currentMove - 1) {
15549                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15550             }
15551             if (appData.highlightLastMove) {
15552                 SetHighlights(fromX, fromY, toX, toY);
15553             }
15554         }
15555     }
15556     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15557         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15558         while (currentMove > target) {
15559             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15560                 // null move cannot be undone. Reload program with move history before it.
15561                 int i;
15562                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15563                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15564                 }
15565                 SendBoard(&first, i);
15566               if(second.analyzing) SendBoard(&second, i);
15567                 for(currentMove=i; currentMove<target; currentMove++) {
15568                     SendMoveToProgram(currentMove, &first);
15569                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15570                 }
15571                 break;
15572             }
15573             SendToBoth("undo\n");
15574             currentMove--;
15575         }
15576     } else {
15577         currentMove = target;
15578     }
15579
15580     if (gameMode == EditGame || gameMode == EndOfGame) {
15581         whiteTimeRemaining = timeRemaining[0][currentMove];
15582         blackTimeRemaining = timeRemaining[1][currentMove];
15583     }
15584     DisplayBothClocks();
15585     DisplayMove(currentMove - 1);
15586     DrawPosition(full_redraw, boards[currentMove]);
15587     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15588     // [HGM] PV info: routine tests if comment empty
15589     DisplayComment(currentMove - 1, commentList[currentMove]);
15590     ClearMap(); // [HGM] exclude: invalidate map
15591 }
15592
15593 void
15594 BackwardEvent ()
15595 {
15596     if (gameMode == IcsExamining && !pausing) {
15597         SendToICS(ics_prefix);
15598         SendToICS("backward\n");
15599     } else {
15600         BackwardInner(currentMove - 1);
15601     }
15602 }
15603
15604 void
15605 ToStartEvent ()
15606 {
15607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15608         /* to optimize, we temporarily turn off analysis mode while we undo
15609          * all the moves. Otherwise we get analysis output after each undo.
15610          */
15611         if (first.analysisSupport) {
15612           SendToProgram("exit\nforce\n", &first);
15613           first.analyzing = FALSE;
15614         }
15615     }
15616
15617     if (gameMode == IcsExamining && !pausing) {
15618         SendToICS(ics_prefix);
15619         SendToICS("backward 999999\n");
15620     } else {
15621         BackwardInner(backwardMostMove);
15622     }
15623
15624     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15625         /* we have fed all the moves, so reactivate analysis mode */
15626         SendToProgram("analyze\n", &first);
15627         first.analyzing = TRUE;
15628         /*first.maybeThinking = TRUE;*/
15629         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15630     }
15631 }
15632
15633 void
15634 ToNrEvent (int to)
15635 {
15636   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15637   if (to >= forwardMostMove) to = forwardMostMove;
15638   if (to <= backwardMostMove) to = backwardMostMove;
15639   if (to < currentMove) {
15640     BackwardInner(to);
15641   } else {
15642     ForwardInner(to);
15643   }
15644 }
15645
15646 void
15647 RevertEvent (Boolean annotate)
15648 {
15649     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15650         return;
15651     }
15652     if (gameMode != IcsExamining) {
15653         DisplayError(_("You are not examining a game"), 0);
15654         return;
15655     }
15656     if (pausing) {
15657         DisplayError(_("You can't revert while pausing"), 0);
15658         return;
15659     }
15660     SendToICS(ics_prefix);
15661     SendToICS("revert\n");
15662 }
15663
15664 void
15665 RetractMoveEvent ()
15666 {
15667     switch (gameMode) {
15668       case MachinePlaysWhite:
15669       case MachinePlaysBlack:
15670         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15671             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15672             return;
15673         }
15674         if (forwardMostMove < 2) return;
15675         currentMove = forwardMostMove = forwardMostMove - 2;
15676         whiteTimeRemaining = timeRemaining[0][currentMove];
15677         blackTimeRemaining = timeRemaining[1][currentMove];
15678         DisplayBothClocks();
15679         DisplayMove(currentMove - 1);
15680         ClearHighlights();/*!! could figure this out*/
15681         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15682         SendToProgram("remove\n", &first);
15683         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15684         break;
15685
15686       case BeginningOfGame:
15687       default:
15688         break;
15689
15690       case IcsPlayingWhite:
15691       case IcsPlayingBlack:
15692         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15693             SendToICS(ics_prefix);
15694             SendToICS("takeback 2\n");
15695         } else {
15696             SendToICS(ics_prefix);
15697             SendToICS("takeback 1\n");
15698         }
15699         break;
15700     }
15701 }
15702
15703 void
15704 MoveNowEvent ()
15705 {
15706     ChessProgramState *cps;
15707
15708     switch (gameMode) {
15709       case MachinePlaysWhite:
15710         if (!WhiteOnMove(forwardMostMove)) {
15711             DisplayError(_("It is your turn"), 0);
15712             return;
15713         }
15714         cps = &first;
15715         break;
15716       case MachinePlaysBlack:
15717         if (WhiteOnMove(forwardMostMove)) {
15718             DisplayError(_("It is your turn"), 0);
15719             return;
15720         }
15721         cps = &first;
15722         break;
15723       case TwoMachinesPlay:
15724         if (WhiteOnMove(forwardMostMove) ==
15725             (first.twoMachinesColor[0] == 'w')) {
15726             cps = &first;
15727         } else {
15728             cps = &second;
15729         }
15730         break;
15731       case BeginningOfGame:
15732       default:
15733         return;
15734     }
15735     SendToProgram("?\n", cps);
15736 }
15737
15738 void
15739 TruncateGameEvent ()
15740 {
15741     EditGameEvent();
15742     if (gameMode != EditGame) return;
15743     TruncateGame();
15744 }
15745
15746 void
15747 TruncateGame ()
15748 {
15749     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15750     if (forwardMostMove > currentMove) {
15751         if (gameInfo.resultDetails != NULL) {
15752             free(gameInfo.resultDetails);
15753             gameInfo.resultDetails = NULL;
15754             gameInfo.result = GameUnfinished;
15755         }
15756         forwardMostMove = currentMove;
15757         HistorySet(parseList, backwardMostMove, forwardMostMove,
15758                    currentMove-1);
15759     }
15760 }
15761
15762 void
15763 HintEvent ()
15764 {
15765     if (appData.noChessProgram) return;
15766     switch (gameMode) {
15767       case MachinePlaysWhite:
15768         if (WhiteOnMove(forwardMostMove)) {
15769             DisplayError(_("Wait until your turn."), 0);
15770             return;
15771         }
15772         break;
15773       case BeginningOfGame:
15774       case MachinePlaysBlack:
15775         if (!WhiteOnMove(forwardMostMove)) {
15776             DisplayError(_("Wait until your turn."), 0);
15777             return;
15778         }
15779         break;
15780       default:
15781         DisplayError(_("No hint available"), 0);
15782         return;
15783     }
15784     SendToProgram("hint\n", &first);
15785     hintRequested = TRUE;
15786 }
15787
15788 void
15789 CreateBookEvent ()
15790 {
15791     ListGame * lg = (ListGame *) gameList.head;
15792     FILE *f, *g;
15793     int nItem;
15794     static int secondTime = FALSE;
15795
15796     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15797         DisplayError(_("Game list not loaded or empty"), 0);
15798         return;
15799     }
15800
15801     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15802         fclose(g);
15803         secondTime++;
15804         DisplayNote(_("Book file exists! Try again for overwrite."));
15805         return;
15806     }
15807
15808     creatingBook = TRUE;
15809     secondTime = FALSE;
15810
15811     /* Get list size */
15812     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15813         LoadGame(f, nItem, "", TRUE);
15814         AddGameToBook(TRUE);
15815         lg = (ListGame *) lg->node.succ;
15816     }
15817
15818     creatingBook = FALSE;
15819     FlushBook();
15820 }
15821
15822 void
15823 BookEvent ()
15824 {
15825     if (appData.noChessProgram) return;
15826     switch (gameMode) {
15827       case MachinePlaysWhite:
15828         if (WhiteOnMove(forwardMostMove)) {
15829             DisplayError(_("Wait until your turn."), 0);
15830             return;
15831         }
15832         break;
15833       case BeginningOfGame:
15834       case MachinePlaysBlack:
15835         if (!WhiteOnMove(forwardMostMove)) {
15836             DisplayError(_("Wait until your turn."), 0);
15837             return;
15838         }
15839         break;
15840       case EditPosition:
15841         EditPositionDone(TRUE);
15842         break;
15843       case TwoMachinesPlay:
15844         return;
15845       default:
15846         break;
15847     }
15848     SendToProgram("bk\n", &first);
15849     bookOutput[0] = NULLCHAR;
15850     bookRequested = TRUE;
15851 }
15852
15853 void
15854 AboutGameEvent ()
15855 {
15856     char *tags = PGNTags(&gameInfo);
15857     TagsPopUp(tags, CmailMsg());
15858     free(tags);
15859 }
15860
15861 /* end button procedures */
15862
15863 void
15864 PrintPosition (FILE *fp, int move)
15865 {
15866     int i, j;
15867
15868     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15870             char c = PieceToChar(boards[move][i][j]);
15871             fputc(c == 'x' ? '.' : c, fp);
15872             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15873         }
15874     }
15875     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15876       fprintf(fp, "white to play\n");
15877     else
15878       fprintf(fp, "black to play\n");
15879 }
15880
15881 void
15882 PrintOpponents (FILE *fp)
15883 {
15884     if (gameInfo.white != NULL) {
15885         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15886     } else {
15887         fprintf(fp, "\n");
15888     }
15889 }
15890
15891 /* Find last component of program's own name, using some heuristics */
15892 void
15893 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15894 {
15895     char *p, *q, c;
15896     int local = (strcmp(host, "localhost") == 0);
15897     while (!local && (p = strchr(prog, ';')) != NULL) {
15898         p++;
15899         while (*p == ' ') p++;
15900         prog = p;
15901     }
15902     if (*prog == '"' || *prog == '\'') {
15903         q = strchr(prog + 1, *prog);
15904     } else {
15905         q = strchr(prog, ' ');
15906     }
15907     if (q == NULL) q = prog + strlen(prog);
15908     p = q;
15909     while (p >= prog && *p != '/' && *p != '\\') p--;
15910     p++;
15911     if(p == prog && *p == '"') p++;
15912     c = *q; *q = 0;
15913     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15914     memcpy(buf, p, q - p);
15915     buf[q - p] = NULLCHAR;
15916     if (!local) {
15917         strcat(buf, "@");
15918         strcat(buf, host);
15919     }
15920 }
15921
15922 char *
15923 TimeControlTagValue ()
15924 {
15925     char buf[MSG_SIZ];
15926     if (!appData.clockMode) {
15927       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15928     } else if (movesPerSession > 0) {
15929       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15930     } else if (timeIncrement == 0) {
15931       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15932     } else {
15933       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15934     }
15935     return StrSave(buf);
15936 }
15937
15938 void
15939 SetGameInfo ()
15940 {
15941     /* This routine is used only for certain modes */
15942     VariantClass v = gameInfo.variant;
15943     ChessMove r = GameUnfinished;
15944     char *p = NULL;
15945
15946     if(keepInfo) return;
15947
15948     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15949         r = gameInfo.result;
15950         p = gameInfo.resultDetails;
15951         gameInfo.resultDetails = NULL;
15952     }
15953     ClearGameInfo(&gameInfo);
15954     gameInfo.variant = v;
15955
15956     switch (gameMode) {
15957       case MachinePlaysWhite:
15958         gameInfo.event = StrSave( appData.pgnEventHeader );
15959         gameInfo.site = StrSave(HostName());
15960         gameInfo.date = PGNDate();
15961         gameInfo.round = StrSave("-");
15962         gameInfo.white = StrSave(first.tidy);
15963         gameInfo.black = StrSave(UserName());
15964         gameInfo.timeControl = TimeControlTagValue();
15965         break;
15966
15967       case MachinePlaysBlack:
15968         gameInfo.event = StrSave( appData.pgnEventHeader );
15969         gameInfo.site = StrSave(HostName());
15970         gameInfo.date = PGNDate();
15971         gameInfo.round = StrSave("-");
15972         gameInfo.white = StrSave(UserName());
15973         gameInfo.black = StrSave(first.tidy);
15974         gameInfo.timeControl = TimeControlTagValue();
15975         break;
15976
15977       case TwoMachinesPlay:
15978         gameInfo.event = StrSave( appData.pgnEventHeader );
15979         gameInfo.site = StrSave(HostName());
15980         gameInfo.date = PGNDate();
15981         if (roundNr > 0) {
15982             char buf[MSG_SIZ];
15983             snprintf(buf, MSG_SIZ, "%d", roundNr);
15984             gameInfo.round = StrSave(buf);
15985         } else {
15986             gameInfo.round = StrSave("-");
15987         }
15988         if (first.twoMachinesColor[0] == 'w') {
15989             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15990             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15991         } else {
15992             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15993             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15994         }
15995         gameInfo.timeControl = TimeControlTagValue();
15996         break;
15997
15998       case EditGame:
15999         gameInfo.event = StrSave("Edited game");
16000         gameInfo.site = StrSave(HostName());
16001         gameInfo.date = PGNDate();
16002         gameInfo.round = StrSave("-");
16003         gameInfo.white = StrSave("-");
16004         gameInfo.black = StrSave("-");
16005         gameInfo.result = r;
16006         gameInfo.resultDetails = p;
16007         break;
16008
16009       case EditPosition:
16010         gameInfo.event = StrSave("Edited position");
16011         gameInfo.site = StrSave(HostName());
16012         gameInfo.date = PGNDate();
16013         gameInfo.round = StrSave("-");
16014         gameInfo.white = StrSave("-");
16015         gameInfo.black = StrSave("-");
16016         break;
16017
16018       case IcsPlayingWhite:
16019       case IcsPlayingBlack:
16020       case IcsObserving:
16021       case IcsExamining:
16022         break;
16023
16024       case PlayFromGameFile:
16025         gameInfo.event = StrSave("Game from non-PGN file");
16026         gameInfo.site = StrSave(HostName());
16027         gameInfo.date = PGNDate();
16028         gameInfo.round = StrSave("-");
16029         gameInfo.white = StrSave("?");
16030         gameInfo.black = StrSave("?");
16031         break;
16032
16033       default:
16034         break;
16035     }
16036 }
16037
16038 void
16039 ReplaceComment (int index, char *text)
16040 {
16041     int len;
16042     char *p;
16043     float score;
16044
16045     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16046        pvInfoList[index-1].depth == len &&
16047        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16048        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16049     while (*text == '\n') text++;
16050     len = strlen(text);
16051     while (len > 0 && text[len - 1] == '\n') len--;
16052
16053     if (commentList[index] != NULL)
16054       free(commentList[index]);
16055
16056     if (len == 0) {
16057         commentList[index] = NULL;
16058         return;
16059     }
16060   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16061       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16062       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16063     commentList[index] = (char *) malloc(len + 2);
16064     strncpy(commentList[index], text, len);
16065     commentList[index][len] = '\n';
16066     commentList[index][len + 1] = NULLCHAR;
16067   } else {
16068     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16069     char *p;
16070     commentList[index] = (char *) malloc(len + 7);
16071     safeStrCpy(commentList[index], "{\n", 3);
16072     safeStrCpy(commentList[index]+2, text, len+1);
16073     commentList[index][len+2] = NULLCHAR;
16074     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16075     strcat(commentList[index], "\n}\n");
16076   }
16077 }
16078
16079 void
16080 CrushCRs (char *text)
16081 {
16082   char *p = text;
16083   char *q = text;
16084   char ch;
16085
16086   do {
16087     ch = *p++;
16088     if (ch == '\r') continue;
16089     *q++ = ch;
16090   } while (ch != '\0');
16091 }
16092
16093 void
16094 AppendComment (int index, char *text, Boolean addBraces)
16095 /* addBraces  tells if we should add {} */
16096 {
16097     int oldlen, len;
16098     char *old;
16099
16100 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16101     if(addBraces == 3) addBraces = 0; else // force appending literally
16102     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16103
16104     CrushCRs(text);
16105     while (*text == '\n') text++;
16106     len = strlen(text);
16107     while (len > 0 && text[len - 1] == '\n') len--;
16108     text[len] = NULLCHAR;
16109
16110     if (len == 0) return;
16111
16112     if (commentList[index] != NULL) {
16113       Boolean addClosingBrace = addBraces;
16114         old = commentList[index];
16115         oldlen = strlen(old);
16116         while(commentList[index][oldlen-1] ==  '\n')
16117           commentList[index][--oldlen] = NULLCHAR;
16118         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16119         safeStrCpy(commentList[index], old, oldlen + len + 6);
16120         free(old);
16121         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16122         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16123           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16124           while (*text == '\n') { text++; len--; }
16125           commentList[index][--oldlen] = NULLCHAR;
16126       }
16127         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16128         else          strcat(commentList[index], "\n");
16129         strcat(commentList[index], text);
16130         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16131         else          strcat(commentList[index], "\n");
16132     } else {
16133         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16134         if(addBraces)
16135           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16136         else commentList[index][0] = NULLCHAR;
16137         strcat(commentList[index], text);
16138         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16139         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16140     }
16141 }
16142
16143 static char *
16144 FindStr (char * text, char * sub_text)
16145 {
16146     char * result = strstr( text, sub_text );
16147
16148     if( result != NULL ) {
16149         result += strlen( sub_text );
16150     }
16151
16152     return result;
16153 }
16154
16155 /* [AS] Try to extract PV info from PGN comment */
16156 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16157 char *
16158 GetInfoFromComment (int index, char * text)
16159 {
16160     char * sep = text, *p;
16161
16162     if( text != NULL && index > 0 ) {
16163         int score = 0;
16164         int depth = 0;
16165         int time = -1, sec = 0, deci;
16166         char * s_eval = FindStr( text, "[%eval " );
16167         char * s_emt = FindStr( text, "[%emt " );
16168 #if 0
16169         if( s_eval != NULL || s_emt != NULL ) {
16170 #else
16171         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16172 #endif
16173             /* New style */
16174             char delim;
16175
16176             if( s_eval != NULL ) {
16177                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16178                     return text;
16179                 }
16180
16181                 if( delim != ']' ) {
16182                     return text;
16183                 }
16184             }
16185
16186             if( s_emt != NULL ) {
16187             }
16188                 return text;
16189         }
16190         else {
16191             /* We expect something like: [+|-]nnn.nn/dd */
16192             int score_lo = 0;
16193
16194             if(*text != '{') return text; // [HGM] braces: must be normal comment
16195
16196             sep = strchr( text, '/' );
16197             if( sep == NULL || sep < (text+4) ) {
16198                 return text;
16199             }
16200
16201             p = text;
16202             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16203             if(p[1] == '(') { // comment starts with PV
16204                p = strchr(p, ')'); // locate end of PV
16205                if(p == NULL || sep < p+5) return text;
16206                // at this point we have something like "{(.*) +0.23/6 ..."
16207                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16208                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16209                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16210             }
16211             time = -1; sec = -1; deci = -1;
16212             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16213                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16214                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16215                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16216                 return text;
16217             }
16218
16219             if( score_lo < 0 || score_lo >= 100 ) {
16220                 return text;
16221             }
16222
16223             if(sec >= 0) time = 600*time + 10*sec; else
16224             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16225
16226             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16227
16228             /* [HGM] PV time: now locate end of PV info */
16229             while( *++sep >= '0' && *sep <= '9'); // strip depth
16230             if(time >= 0)
16231             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16232             if(sec >= 0)
16233             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16234             if(deci >= 0)
16235             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16236             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16237         }
16238
16239         if( depth <= 0 ) {
16240             return text;
16241         }
16242
16243         if( time < 0 ) {
16244             time = -1;
16245         }
16246
16247         pvInfoList[index-1].depth = depth;
16248         pvInfoList[index-1].score = score;
16249         pvInfoList[index-1].time  = 10*time; // centi-sec
16250         if(*sep == '}') *sep = 0; else *--sep = '{';
16251         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16252     }
16253     return sep;
16254 }
16255
16256 void
16257 SendToProgram (char *message, ChessProgramState *cps)
16258 {
16259     int count, outCount, error;
16260     char buf[MSG_SIZ];
16261
16262     if (cps->pr == NoProc) return;
16263     Attention(cps);
16264
16265     if (appData.debugMode) {
16266         TimeMark now;
16267         GetTimeMark(&now);
16268         fprintf(debugFP, "%ld >%-6s: %s",
16269                 SubtractTimeMarks(&now, &programStartTime),
16270                 cps->which, message);
16271         if(serverFP)
16272             fprintf(serverFP, "%ld >%-6s: %s",
16273                 SubtractTimeMarks(&now, &programStartTime),
16274                 cps->which, message), fflush(serverFP);
16275     }
16276
16277     count = strlen(message);
16278     outCount = OutputToProcess(cps->pr, message, count, &error);
16279     if (outCount < count && !exiting
16280                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16281       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16282       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16283         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16284             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16285                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16286                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16287                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16288             } else {
16289                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16290                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16291                 gameInfo.result = res;
16292             }
16293             gameInfo.resultDetails = StrSave(buf);
16294         }
16295         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16296         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16297     }
16298 }
16299
16300 void
16301 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16302 {
16303     char *end_str;
16304     char buf[MSG_SIZ];
16305     ChessProgramState *cps = (ChessProgramState *)closure;
16306
16307     if (isr != cps->isr) return; /* Killed intentionally */
16308     if (count <= 0) {
16309         if (count == 0) {
16310             RemoveInputSource(cps->isr);
16311             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16312                     _(cps->which), cps->program);
16313             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16314             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16315                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16316                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16317                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16318                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16319                 } else {
16320                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16321                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16322                     gameInfo.result = res;
16323                 }
16324                 gameInfo.resultDetails = StrSave(buf);
16325             }
16326             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16327             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16328         } else {
16329             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16330                     _(cps->which), cps->program);
16331             RemoveInputSource(cps->isr);
16332
16333             /* [AS] Program is misbehaving badly... kill it */
16334             if( count == -2 ) {
16335                 DestroyChildProcess( cps->pr, 9 );
16336                 cps->pr = NoProc;
16337             }
16338
16339             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16340         }
16341         return;
16342     }
16343
16344     if ((end_str = strchr(message, '\r')) != NULL)
16345       *end_str = NULLCHAR;
16346     if ((end_str = strchr(message, '\n')) != NULL)
16347       *end_str = NULLCHAR;
16348
16349     if (appData.debugMode) {
16350         TimeMark now; int print = 1;
16351         char *quote = ""; char c; int i;
16352
16353         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16354                 char start = message[0];
16355                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16356                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16357                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16358                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16359                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16360                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16361                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16362                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16363                    sscanf(message, "hint: %c", &c)!=1 &&
16364                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16365                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16366                     print = (appData.engineComments >= 2);
16367                 }
16368                 message[0] = start; // restore original message
16369         }
16370         if(print) {
16371                 GetTimeMark(&now);
16372                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16373                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16374                         quote,
16375                         message);
16376                 if(serverFP)
16377                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16378                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16379                         quote,
16380                         message), fflush(serverFP);
16381         }
16382     }
16383
16384     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16385     if (appData.icsEngineAnalyze) {
16386         if (strstr(message, "whisper") != NULL ||
16387              strstr(message, "kibitz") != NULL ||
16388             strstr(message, "tellics") != NULL) return;
16389     }
16390
16391     HandleMachineMove(message, cps);
16392 }
16393
16394
16395 void
16396 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16397 {
16398     char buf[MSG_SIZ];
16399     int seconds;
16400
16401     if( timeControl_2 > 0 ) {
16402         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16403             tc = timeControl_2;
16404         }
16405     }
16406     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16407     inc /= cps->timeOdds;
16408     st  /= cps->timeOdds;
16409
16410     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16411
16412     if (st > 0) {
16413       /* Set exact time per move, normally using st command */
16414       if (cps->stKludge) {
16415         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16416         seconds = st % 60;
16417         if (seconds == 0) {
16418           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16419         } else {
16420           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16421         }
16422       } else {
16423         snprintf(buf, MSG_SIZ, "st %d\n", st);
16424       }
16425     } else {
16426       /* Set conventional or incremental time control, using level command */
16427       if (seconds == 0) {
16428         /* Note old gnuchess bug -- minutes:seconds used to not work.
16429            Fixed in later versions, but still avoid :seconds
16430            when seconds is 0. */
16431         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16432       } else {
16433         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16434                  seconds, inc/1000.);
16435       }
16436     }
16437     SendToProgram(buf, cps);
16438
16439     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16440     /* Orthogonally, limit search to given depth */
16441     if (sd > 0) {
16442       if (cps->sdKludge) {
16443         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16444       } else {
16445         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16446       }
16447       SendToProgram(buf, cps);
16448     }
16449
16450     if(cps->nps >= 0) { /* [HGM] nps */
16451         if(cps->supportsNPS == FALSE)
16452           cps->nps = -1; // don't use if engine explicitly says not supported!
16453         else {
16454           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16455           SendToProgram(buf, cps);
16456         }
16457     }
16458 }
16459
16460 ChessProgramState *
16461 WhitePlayer ()
16462 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16463 {
16464     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16465        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16466         return &second;
16467     return &first;
16468 }
16469
16470 void
16471 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16472 {
16473     char message[MSG_SIZ];
16474     long time, otime;
16475
16476     /* Note: this routine must be called when the clocks are stopped
16477        or when they have *just* been set or switched; otherwise
16478        it will be off by the time since the current tick started.
16479     */
16480     if (machineWhite) {
16481         time = whiteTimeRemaining / 10;
16482         otime = blackTimeRemaining / 10;
16483     } else {
16484         time = blackTimeRemaining / 10;
16485         otime = whiteTimeRemaining / 10;
16486     }
16487     /* [HGM] translate opponent's time by time-odds factor */
16488     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16489
16490     if (time <= 0) time = 1;
16491     if (otime <= 0) otime = 1;
16492
16493     snprintf(message, MSG_SIZ, "time %ld\n", time);
16494     SendToProgram(message, cps);
16495
16496     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16497     SendToProgram(message, cps);
16498 }
16499
16500 char *
16501 EngineDefinedVariant (ChessProgramState *cps, int n)
16502 {   // return name of n-th unknown variant that engine supports
16503     static char buf[MSG_SIZ];
16504     char *p, *s = cps->variants;
16505     if(!s) return NULL;
16506     do { // parse string from variants feature
16507       VariantClass v;
16508         p = strchr(s, ',');
16509         if(p) *p = NULLCHAR;
16510       v = StringToVariant(s);
16511       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16512         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16513             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16514         }
16515         if(p) *p++ = ',';
16516         if(n < 0) return buf;
16517     } while(s = p);
16518     return NULL;
16519 }
16520
16521 int
16522 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16523 {
16524   char buf[MSG_SIZ];
16525   int len = strlen(name);
16526   int val;
16527
16528   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16529     (*p) += len + 1;
16530     sscanf(*p, "%d", &val);
16531     *loc = (val != 0);
16532     while (**p && **p != ' ')
16533       (*p)++;
16534     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16535     SendToProgram(buf, cps);
16536     return TRUE;
16537   }
16538   return FALSE;
16539 }
16540
16541 int
16542 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16543 {
16544   char buf[MSG_SIZ];
16545   int len = strlen(name);
16546   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16547     (*p) += len + 1;
16548     sscanf(*p, "%d", loc);
16549     while (**p && **p != ' ') (*p)++;
16550     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16551     SendToProgram(buf, cps);
16552     return TRUE;
16553   }
16554   return FALSE;
16555 }
16556
16557 int
16558 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16559 {
16560   char buf[MSG_SIZ];
16561   int len = strlen(name);
16562   if (strncmp((*p), name, len) == 0
16563       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16564     (*p) += len + 2;
16565     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16566     sscanf(*p, "%[^\"]", *loc);
16567     while (**p && **p != '\"') (*p)++;
16568     if (**p == '\"') (*p)++;
16569     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16570     SendToProgram(buf, cps);
16571     return TRUE;
16572   }
16573   return FALSE;
16574 }
16575
16576 int
16577 ParseOption (Option *opt, ChessProgramState *cps)
16578 // [HGM] options: process the string that defines an engine option, and determine
16579 // name, type, default value, and allowed value range
16580 {
16581         char *p, *q, buf[MSG_SIZ];
16582         int n, min = (-1)<<31, max = 1<<31, def;
16583
16584         if(p = strstr(opt->name, " -spin ")) {
16585             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16586             if(max < min) max = min; // enforce consistency
16587             if(def < min) def = min;
16588             if(def > max) def = max;
16589             opt->value = def;
16590             opt->min = min;
16591             opt->max = max;
16592             opt->type = Spin;
16593         } else if((p = strstr(opt->name, " -slider "))) {
16594             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16595             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16596             if(max < min) max = min; // enforce consistency
16597             if(def < min) def = min;
16598             if(def > max) def = max;
16599             opt->value = def;
16600             opt->min = min;
16601             opt->max = max;
16602             opt->type = Spin; // Slider;
16603         } else if((p = strstr(opt->name, " -string "))) {
16604             opt->textValue = p+9;
16605             opt->type = TextBox;
16606         } else if((p = strstr(opt->name, " -file "))) {
16607             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16608             opt->textValue = p+7;
16609             opt->type = FileName; // FileName;
16610         } else if((p = strstr(opt->name, " -path "))) {
16611             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16612             opt->textValue = p+7;
16613             opt->type = PathName; // PathName;
16614         } else if(p = strstr(opt->name, " -check ")) {
16615             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16616             opt->value = (def != 0);
16617             opt->type = CheckBox;
16618         } else if(p = strstr(opt->name, " -combo ")) {
16619             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16620             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16621             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16622             opt->value = n = 0;
16623             while(q = StrStr(q, " /// ")) {
16624                 n++; *q = 0;    // count choices, and null-terminate each of them
16625                 q += 5;
16626                 if(*q == '*') { // remember default, which is marked with * prefix
16627                     q++;
16628                     opt->value = n;
16629                 }
16630                 cps->comboList[cps->comboCnt++] = q;
16631             }
16632             cps->comboList[cps->comboCnt++] = NULL;
16633             opt->max = n + 1;
16634             opt->type = ComboBox;
16635         } else if(p = strstr(opt->name, " -button")) {
16636             opt->type = Button;
16637         } else if(p = strstr(opt->name, " -save")) {
16638             opt->type = SaveButton;
16639         } else return FALSE;
16640         *p = 0; // terminate option name
16641         // now look if the command-line options define a setting for this engine option.
16642         if(cps->optionSettings && cps->optionSettings[0])
16643             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16644         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16645           snprintf(buf, MSG_SIZ, "option %s", p);
16646                 if(p = strstr(buf, ",")) *p = 0;
16647                 if(q = strchr(buf, '=')) switch(opt->type) {
16648                     case ComboBox:
16649                         for(n=0; n<opt->max; n++)
16650                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16651                         break;
16652                     case TextBox:
16653                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16654                         break;
16655                     case Spin:
16656                     case CheckBox:
16657                         opt->value = atoi(q+1);
16658                     default:
16659                         break;
16660                 }
16661                 strcat(buf, "\n");
16662                 SendToProgram(buf, cps);
16663         }
16664         return TRUE;
16665 }
16666
16667 void
16668 FeatureDone (ChessProgramState *cps, int val)
16669 {
16670   DelayedEventCallback cb = GetDelayedEvent();
16671   if ((cb == InitBackEnd3 && cps == &first) ||
16672       (cb == SettingsMenuIfReady && cps == &second) ||
16673       (cb == LoadEngine) ||
16674       (cb == TwoMachinesEventIfReady)) {
16675     CancelDelayedEvent();
16676     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16677   }
16678   cps->initDone = val;
16679   if(val) cps->reload = FALSE;
16680 }
16681
16682 /* Parse feature command from engine */
16683 void
16684 ParseFeatures (char *args, ChessProgramState *cps)
16685 {
16686   char *p = args;
16687   char *q = NULL;
16688   int val;
16689   char buf[MSG_SIZ];
16690
16691   for (;;) {
16692     while (*p == ' ') p++;
16693     if (*p == NULLCHAR) return;
16694
16695     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16696     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16697     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16698     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16699     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16700     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16701     if (BoolFeature(&p, "reuse", &val, cps)) {
16702       /* Engine can disable reuse, but can't enable it if user said no */
16703       if (!val) cps->reuse = FALSE;
16704       continue;
16705     }
16706     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16707     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16708       if (gameMode == TwoMachinesPlay) {
16709         DisplayTwoMachinesTitle();
16710       } else {
16711         DisplayTitle("");
16712       }
16713       continue;
16714     }
16715     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16716     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16717     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16718     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16719     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16720     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16721     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16722     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16723     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16724     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16725     if (IntFeature(&p, "done", &val, cps)) {
16726       FeatureDone(cps, val);
16727       continue;
16728     }
16729     /* Added by Tord: */
16730     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16731     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16732     /* End of additions by Tord */
16733
16734     /* [HGM] added features: */
16735     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16736     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16737     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16738     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16739     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16740     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16741     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16742     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16743         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16744         FREE(cps->option[cps->nrOptions].name);
16745         cps->option[cps->nrOptions].name = q; q = NULL;
16746         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16747           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16748             SendToProgram(buf, cps);
16749             continue;
16750         }
16751         if(cps->nrOptions >= MAX_OPTIONS) {
16752             cps->nrOptions--;
16753             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16754             DisplayError(buf, 0);
16755         }
16756         continue;
16757     }
16758     /* End of additions by HGM */
16759
16760     /* unknown feature: complain and skip */
16761     q = p;
16762     while (*q && *q != '=') q++;
16763     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16764     SendToProgram(buf, cps);
16765     p = q;
16766     if (*p == '=') {
16767       p++;
16768       if (*p == '\"') {
16769         p++;
16770         while (*p && *p != '\"') p++;
16771         if (*p == '\"') p++;
16772       } else {
16773         while (*p && *p != ' ') p++;
16774       }
16775     }
16776   }
16777
16778 }
16779
16780 void
16781 PeriodicUpdatesEvent (int newState)
16782 {
16783     if (newState == appData.periodicUpdates)
16784       return;
16785
16786     appData.periodicUpdates=newState;
16787
16788     /* Display type changes, so update it now */
16789 //    DisplayAnalysis();
16790
16791     /* Get the ball rolling again... */
16792     if (newState) {
16793         AnalysisPeriodicEvent(1);
16794         StartAnalysisClock();
16795     }
16796 }
16797
16798 void
16799 PonderNextMoveEvent (int newState)
16800 {
16801     if (newState == appData.ponderNextMove) return;
16802     if (gameMode == EditPosition) EditPositionDone(TRUE);
16803     if (newState) {
16804         SendToProgram("hard\n", &first);
16805         if (gameMode == TwoMachinesPlay) {
16806             SendToProgram("hard\n", &second);
16807         }
16808     } else {
16809         SendToProgram("easy\n", &first);
16810         thinkOutput[0] = NULLCHAR;
16811         if (gameMode == TwoMachinesPlay) {
16812             SendToProgram("easy\n", &second);
16813         }
16814     }
16815     appData.ponderNextMove = newState;
16816 }
16817
16818 void
16819 NewSettingEvent (int option, int *feature, char *command, int value)
16820 {
16821     char buf[MSG_SIZ];
16822
16823     if (gameMode == EditPosition) EditPositionDone(TRUE);
16824     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16825     if(feature == NULL || *feature) SendToProgram(buf, &first);
16826     if (gameMode == TwoMachinesPlay) {
16827         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16828     }
16829 }
16830
16831 void
16832 ShowThinkingEvent ()
16833 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16834 {
16835     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16836     int newState = appData.showThinking
16837         // [HGM] thinking: other features now need thinking output as well
16838         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16839
16840     if (oldState == newState) return;
16841     oldState = newState;
16842     if (gameMode == EditPosition) EditPositionDone(TRUE);
16843     if (oldState) {
16844         SendToProgram("post\n", &first);
16845         if (gameMode == TwoMachinesPlay) {
16846             SendToProgram("post\n", &second);
16847         }
16848     } else {
16849         SendToProgram("nopost\n", &first);
16850         thinkOutput[0] = NULLCHAR;
16851         if (gameMode == TwoMachinesPlay) {
16852             SendToProgram("nopost\n", &second);
16853         }
16854     }
16855 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16856 }
16857
16858 void
16859 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16860 {
16861   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16862   if (pr == NoProc) return;
16863   AskQuestion(title, question, replyPrefix, pr);
16864 }
16865
16866 void
16867 TypeInEvent (char firstChar)
16868 {
16869     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16870         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16871         gameMode == AnalyzeMode || gameMode == EditGame ||
16872         gameMode == EditPosition || gameMode == IcsExamining ||
16873         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16874         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16875                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16876                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16877         gameMode == Training) PopUpMoveDialog(firstChar);
16878 }
16879
16880 void
16881 TypeInDoneEvent (char *move)
16882 {
16883         Board board;
16884         int n, fromX, fromY, toX, toY;
16885         char promoChar;
16886         ChessMove moveType;
16887
16888         // [HGM] FENedit
16889         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16890                 EditPositionPasteFEN(move);
16891                 return;
16892         }
16893         // [HGM] movenum: allow move number to be typed in any mode
16894         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16895           ToNrEvent(2*n-1);
16896           return;
16897         }
16898         // undocumented kludge: allow command-line option to be typed in!
16899         // (potentially fatal, and does not implement the effect of the option.)
16900         // should only be used for options that are values on which future decisions will be made,
16901         // and definitely not on options that would be used during initialization.
16902         if(strstr(move, "!!! -") == move) {
16903             ParseArgsFromString(move+4);
16904             return;
16905         }
16906
16907       if (gameMode != EditGame && currentMove != forwardMostMove &&
16908         gameMode != Training) {
16909         DisplayMoveError(_("Displayed move is not current"));
16910       } else {
16911         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16912           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16913         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16914         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16915           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16916           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16917         } else {
16918           DisplayMoveError(_("Could not parse move"));
16919         }
16920       }
16921 }
16922
16923 void
16924 DisplayMove (int moveNumber)
16925 {
16926     char message[MSG_SIZ];
16927     char res[MSG_SIZ];
16928     char cpThinkOutput[MSG_SIZ];
16929
16930     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16931
16932     if (moveNumber == forwardMostMove - 1 ||
16933         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16934
16935         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16936
16937         if (strchr(cpThinkOutput, '\n')) {
16938             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16939         }
16940     } else {
16941         *cpThinkOutput = NULLCHAR;
16942     }
16943
16944     /* [AS] Hide thinking from human user */
16945     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16946         *cpThinkOutput = NULLCHAR;
16947         if( thinkOutput[0] != NULLCHAR ) {
16948             int i;
16949
16950             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16951                 cpThinkOutput[i] = '.';
16952             }
16953             cpThinkOutput[i] = NULLCHAR;
16954             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16955         }
16956     }
16957
16958     if (moveNumber == forwardMostMove - 1 &&
16959         gameInfo.resultDetails != NULL) {
16960         if (gameInfo.resultDetails[0] == NULLCHAR) {
16961           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16962         } else {
16963           snprintf(res, MSG_SIZ, " {%s} %s",
16964                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16965         }
16966     } else {
16967         res[0] = NULLCHAR;
16968     }
16969
16970     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16971         DisplayMessage(res, cpThinkOutput);
16972     } else {
16973       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16974                 WhiteOnMove(moveNumber) ? " " : ".. ",
16975                 parseList[moveNumber], res);
16976         DisplayMessage(message, cpThinkOutput);
16977     }
16978 }
16979
16980 void
16981 DisplayComment (int moveNumber, char *text)
16982 {
16983     char title[MSG_SIZ];
16984
16985     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16986       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16987     } else {
16988       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16989               WhiteOnMove(moveNumber) ? " " : ".. ",
16990               parseList[moveNumber]);
16991     }
16992     if (text != NULL && (appData.autoDisplayComment || commentUp))
16993         CommentPopUp(title, text);
16994 }
16995
16996 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16997  * might be busy thinking or pondering.  It can be omitted if your
16998  * gnuchess is configured to stop thinking immediately on any user
16999  * input.  However, that gnuchess feature depends on the FIONREAD
17000  * ioctl, which does not work properly on some flavors of Unix.
17001  */
17002 void
17003 Attention (ChessProgramState *cps)
17004 {
17005 #if ATTENTION
17006     if (!cps->useSigint) return;
17007     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17008     switch (gameMode) {
17009       case MachinePlaysWhite:
17010       case MachinePlaysBlack:
17011       case TwoMachinesPlay:
17012       case IcsPlayingWhite:
17013       case IcsPlayingBlack:
17014       case AnalyzeMode:
17015       case AnalyzeFile:
17016         /* Skip if we know it isn't thinking */
17017         if (!cps->maybeThinking) return;
17018         if (appData.debugMode)
17019           fprintf(debugFP, "Interrupting %s\n", cps->which);
17020         InterruptChildProcess(cps->pr);
17021         cps->maybeThinking = FALSE;
17022         break;
17023       default:
17024         break;
17025     }
17026 #endif /*ATTENTION*/
17027 }
17028
17029 int
17030 CheckFlags ()
17031 {
17032     if (whiteTimeRemaining <= 0) {
17033         if (!whiteFlag) {
17034             whiteFlag = TRUE;
17035             if (appData.icsActive) {
17036                 if (appData.autoCallFlag &&
17037                     gameMode == IcsPlayingBlack && !blackFlag) {
17038                   SendToICS(ics_prefix);
17039                   SendToICS("flag\n");
17040                 }
17041             } else {
17042                 if (blackFlag) {
17043                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17044                 } else {
17045                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17046                     if (appData.autoCallFlag) {
17047                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17048                         return TRUE;
17049                     }
17050                 }
17051             }
17052         }
17053     }
17054     if (blackTimeRemaining <= 0) {
17055         if (!blackFlag) {
17056             blackFlag = TRUE;
17057             if (appData.icsActive) {
17058                 if (appData.autoCallFlag &&
17059                     gameMode == IcsPlayingWhite && !whiteFlag) {
17060                   SendToICS(ics_prefix);
17061                   SendToICS("flag\n");
17062                 }
17063             } else {
17064                 if (whiteFlag) {
17065                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17066                 } else {
17067                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17068                     if (appData.autoCallFlag) {
17069                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17070                         return TRUE;
17071                     }
17072                 }
17073             }
17074         }
17075     }
17076     return FALSE;
17077 }
17078
17079 void
17080 CheckTimeControl ()
17081 {
17082     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17083         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17084
17085     /*
17086      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17087      */
17088     if ( !WhiteOnMove(forwardMostMove) ) {
17089         /* White made time control */
17090         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17091         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17092         /* [HGM] time odds: correct new time quota for time odds! */
17093                                             / WhitePlayer()->timeOdds;
17094         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17095     } else {
17096         lastBlack -= blackTimeRemaining;
17097         /* Black made time control */
17098         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17099                                             / WhitePlayer()->other->timeOdds;
17100         lastWhite = whiteTimeRemaining;
17101     }
17102 }
17103
17104 void
17105 DisplayBothClocks ()
17106 {
17107     int wom = gameMode == EditPosition ?
17108       !blackPlaysFirst : WhiteOnMove(currentMove);
17109     DisplayWhiteClock(whiteTimeRemaining, wom);
17110     DisplayBlackClock(blackTimeRemaining, !wom);
17111 }
17112
17113
17114 /* Timekeeping seems to be a portability nightmare.  I think everyone
17115    has ftime(), but I'm really not sure, so I'm including some ifdefs
17116    to use other calls if you don't.  Clocks will be less accurate if
17117    you have neither ftime nor gettimeofday.
17118 */
17119
17120 /* VS 2008 requires the #include outside of the function */
17121 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17122 #include <sys/timeb.h>
17123 #endif
17124
17125 /* Get the current time as a TimeMark */
17126 void
17127 GetTimeMark (TimeMark *tm)
17128 {
17129 #if HAVE_GETTIMEOFDAY
17130
17131     struct timeval timeVal;
17132     struct timezone timeZone;
17133
17134     gettimeofday(&timeVal, &timeZone);
17135     tm->sec = (long) timeVal.tv_sec;
17136     tm->ms = (int) (timeVal.tv_usec / 1000L);
17137
17138 #else /*!HAVE_GETTIMEOFDAY*/
17139 #if HAVE_FTIME
17140
17141 // include <sys/timeb.h> / moved to just above start of function
17142     struct timeb timeB;
17143
17144     ftime(&timeB);
17145     tm->sec = (long) timeB.time;
17146     tm->ms = (int) timeB.millitm;
17147
17148 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17149     tm->sec = (long) time(NULL);
17150     tm->ms = 0;
17151 #endif
17152 #endif
17153 }
17154
17155 /* Return the difference in milliseconds between two
17156    time marks.  We assume the difference will fit in a long!
17157 */
17158 long
17159 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17160 {
17161     return 1000L*(tm2->sec - tm1->sec) +
17162            (long) (tm2->ms - tm1->ms);
17163 }
17164
17165
17166 /*
17167  * Code to manage the game clocks.
17168  *
17169  * In tournament play, black starts the clock and then white makes a move.
17170  * We give the human user a slight advantage if he is playing white---the
17171  * clocks don't run until he makes his first move, so it takes zero time.
17172  * Also, we don't account for network lag, so we could get out of sync
17173  * with GNU Chess's clock -- but then, referees are always right.
17174  */
17175
17176 static TimeMark tickStartTM;
17177 static long intendedTickLength;
17178
17179 long
17180 NextTickLength (long timeRemaining)
17181 {
17182     long nominalTickLength, nextTickLength;
17183
17184     if (timeRemaining > 0L && timeRemaining <= 10000L)
17185       nominalTickLength = 100L;
17186     else
17187       nominalTickLength = 1000L;
17188     nextTickLength = timeRemaining % nominalTickLength;
17189     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17190
17191     return nextTickLength;
17192 }
17193
17194 /* Adjust clock one minute up or down */
17195 void
17196 AdjustClock (Boolean which, int dir)
17197 {
17198     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17199     if(which) blackTimeRemaining += 60000*dir;
17200     else      whiteTimeRemaining += 60000*dir;
17201     DisplayBothClocks();
17202     adjustedClock = TRUE;
17203 }
17204
17205 /* Stop clocks and reset to a fresh time control */
17206 void
17207 ResetClocks ()
17208 {
17209     (void) StopClockTimer();
17210     if (appData.icsActive) {
17211         whiteTimeRemaining = blackTimeRemaining = 0;
17212     } else if (searchTime) {
17213         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17214         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17215     } else { /* [HGM] correct new time quote for time odds */
17216         whiteTC = blackTC = fullTimeControlString;
17217         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17218         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17219     }
17220     if (whiteFlag || blackFlag) {
17221         DisplayTitle("");
17222         whiteFlag = blackFlag = FALSE;
17223     }
17224     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17225     DisplayBothClocks();
17226     adjustedClock = FALSE;
17227 }
17228
17229 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17230
17231 /* Decrement running clock by amount of time that has passed */
17232 void
17233 DecrementClocks ()
17234 {
17235     long timeRemaining;
17236     long lastTickLength, fudge;
17237     TimeMark now;
17238
17239     if (!appData.clockMode) return;
17240     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17241
17242     GetTimeMark(&now);
17243
17244     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17245
17246     /* Fudge if we woke up a little too soon */
17247     fudge = intendedTickLength - lastTickLength;
17248     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17249
17250     if (WhiteOnMove(forwardMostMove)) {
17251         if(whiteNPS >= 0) lastTickLength = 0;
17252         timeRemaining = whiteTimeRemaining -= lastTickLength;
17253         if(timeRemaining < 0 && !appData.icsActive) {
17254             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17255             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17256                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17257                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17258             }
17259         }
17260         DisplayWhiteClock(whiteTimeRemaining - fudge,
17261                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17262     } else {
17263         if(blackNPS >= 0) lastTickLength = 0;
17264         timeRemaining = blackTimeRemaining -= lastTickLength;
17265         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17266             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17267             if(suddenDeath) {
17268                 blackStartMove = forwardMostMove;
17269                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17270             }
17271         }
17272         DisplayBlackClock(blackTimeRemaining - fudge,
17273                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17274     }
17275     if (CheckFlags()) return;
17276
17277     if(twoBoards) { // count down secondary board's clocks as well
17278         activePartnerTime -= lastTickLength;
17279         partnerUp = 1;
17280         if(activePartner == 'W')
17281             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17282         else
17283             DisplayBlackClock(activePartnerTime, TRUE);
17284         partnerUp = 0;
17285     }
17286
17287     tickStartTM = now;
17288     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17289     StartClockTimer(intendedTickLength);
17290
17291     /* if the time remaining has fallen below the alarm threshold, sound the
17292      * alarm. if the alarm has sounded and (due to a takeback or time control
17293      * with increment) the time remaining has increased to a level above the
17294      * threshold, reset the alarm so it can sound again.
17295      */
17296
17297     if (appData.icsActive && appData.icsAlarm) {
17298
17299         /* make sure we are dealing with the user's clock */
17300         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17301                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17302            )) return;
17303
17304         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17305             alarmSounded = FALSE;
17306         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17307             PlayAlarmSound();
17308             alarmSounded = TRUE;
17309         }
17310     }
17311 }
17312
17313
17314 /* A player has just moved, so stop the previously running
17315    clock and (if in clock mode) start the other one.
17316    We redisplay both clocks in case we're in ICS mode, because
17317    ICS gives us an update to both clocks after every move.
17318    Note that this routine is called *after* forwardMostMove
17319    is updated, so the last fractional tick must be subtracted
17320    from the color that is *not* on move now.
17321 */
17322 void
17323 SwitchClocks (int newMoveNr)
17324 {
17325     long lastTickLength;
17326     TimeMark now;
17327     int flagged = FALSE;
17328
17329     GetTimeMark(&now);
17330
17331     if (StopClockTimer() && appData.clockMode) {
17332         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17333         if (!WhiteOnMove(forwardMostMove)) {
17334             if(blackNPS >= 0) lastTickLength = 0;
17335             blackTimeRemaining -= lastTickLength;
17336            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17337 //         if(pvInfoList[forwardMostMove].time == -1)
17338                  pvInfoList[forwardMostMove].time =               // use GUI time
17339                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17340         } else {
17341            if(whiteNPS >= 0) lastTickLength = 0;
17342            whiteTimeRemaining -= lastTickLength;
17343            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17344 //         if(pvInfoList[forwardMostMove].time == -1)
17345                  pvInfoList[forwardMostMove].time =
17346                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17347         }
17348         flagged = CheckFlags();
17349     }
17350     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17351     CheckTimeControl();
17352
17353     if (flagged || !appData.clockMode) return;
17354
17355     switch (gameMode) {
17356       case MachinePlaysBlack:
17357       case MachinePlaysWhite:
17358       case BeginningOfGame:
17359         if (pausing) return;
17360         break;
17361
17362       case EditGame:
17363       case PlayFromGameFile:
17364       case IcsExamining:
17365         return;
17366
17367       default:
17368         break;
17369     }
17370
17371     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17372         if(WhiteOnMove(forwardMostMove))
17373              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17374         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17375     }
17376
17377     tickStartTM = now;
17378     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17379       whiteTimeRemaining : blackTimeRemaining);
17380     StartClockTimer(intendedTickLength);
17381 }
17382
17383
17384 /* Stop both clocks */
17385 void
17386 StopClocks ()
17387 {
17388     long lastTickLength;
17389     TimeMark now;
17390
17391     if (!StopClockTimer()) return;
17392     if (!appData.clockMode) return;
17393
17394     GetTimeMark(&now);
17395
17396     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17397     if (WhiteOnMove(forwardMostMove)) {
17398         if(whiteNPS >= 0) lastTickLength = 0;
17399         whiteTimeRemaining -= lastTickLength;
17400         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17401     } else {
17402         if(blackNPS >= 0) lastTickLength = 0;
17403         blackTimeRemaining -= lastTickLength;
17404         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17405     }
17406     CheckFlags();
17407 }
17408
17409 /* Start clock of player on move.  Time may have been reset, so
17410    if clock is already running, stop and restart it. */
17411 void
17412 StartClocks ()
17413 {
17414     (void) StopClockTimer(); /* in case it was running already */
17415     DisplayBothClocks();
17416     if (CheckFlags()) return;
17417
17418     if (!appData.clockMode) return;
17419     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17420
17421     GetTimeMark(&tickStartTM);
17422     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17423       whiteTimeRemaining : blackTimeRemaining);
17424
17425    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17426     whiteNPS = blackNPS = -1;
17427     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17428        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17429         whiteNPS = first.nps;
17430     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17431        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17432         blackNPS = first.nps;
17433     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17434         whiteNPS = second.nps;
17435     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17436         blackNPS = second.nps;
17437     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17438
17439     StartClockTimer(intendedTickLength);
17440 }
17441
17442 char *
17443 TimeString (long ms)
17444 {
17445     long second, minute, hour, day;
17446     char *sign = "";
17447     static char buf[32];
17448
17449     if (ms > 0 && ms <= 9900) {
17450       /* convert milliseconds to tenths, rounding up */
17451       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17452
17453       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17454       return buf;
17455     }
17456
17457     /* convert milliseconds to seconds, rounding up */
17458     /* use floating point to avoid strangeness of integer division
17459        with negative dividends on many machines */
17460     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17461
17462     if (second < 0) {
17463         sign = "-";
17464         second = -second;
17465     }
17466
17467     day = second / (60 * 60 * 24);
17468     second = second % (60 * 60 * 24);
17469     hour = second / (60 * 60);
17470     second = second % (60 * 60);
17471     minute = second / 60;
17472     second = second % 60;
17473
17474     if (day > 0)
17475       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17476               sign, day, hour, minute, second);
17477     else if (hour > 0)
17478       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17479     else
17480       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17481
17482     return buf;
17483 }
17484
17485
17486 /*
17487  * This is necessary because some C libraries aren't ANSI C compliant yet.
17488  */
17489 char *
17490 StrStr (char *string, char *match)
17491 {
17492     int i, length;
17493
17494     length = strlen(match);
17495
17496     for (i = strlen(string) - length; i >= 0; i--, string++)
17497       if (!strncmp(match, string, length))
17498         return string;
17499
17500     return NULL;
17501 }
17502
17503 char *
17504 StrCaseStr (char *string, char *match)
17505 {
17506     int i, j, length;
17507
17508     length = strlen(match);
17509
17510     for (i = strlen(string) - length; i >= 0; i--, string++) {
17511         for (j = 0; j < length; j++) {
17512             if (ToLower(match[j]) != ToLower(string[j]))
17513               break;
17514         }
17515         if (j == length) return string;
17516     }
17517
17518     return NULL;
17519 }
17520
17521 #ifndef _amigados
17522 int
17523 StrCaseCmp (char *s1, char *s2)
17524 {
17525     char c1, c2;
17526
17527     for (;;) {
17528         c1 = ToLower(*s1++);
17529         c2 = ToLower(*s2++);
17530         if (c1 > c2) return 1;
17531         if (c1 < c2) return -1;
17532         if (c1 == NULLCHAR) return 0;
17533     }
17534 }
17535
17536
17537 int
17538 ToLower (int c)
17539 {
17540     return isupper(c) ? tolower(c) : c;
17541 }
17542
17543
17544 int
17545 ToUpper (int c)
17546 {
17547     return islower(c) ? toupper(c) : c;
17548 }
17549 #endif /* !_amigados    */
17550
17551 char *
17552 StrSave (char *s)
17553 {
17554   char *ret;
17555
17556   if ((ret = (char *) malloc(strlen(s) + 1)))
17557     {
17558       safeStrCpy(ret, s, strlen(s)+1);
17559     }
17560   return ret;
17561 }
17562
17563 char *
17564 StrSavePtr (char *s, char **savePtr)
17565 {
17566     if (*savePtr) {
17567         free(*savePtr);
17568     }
17569     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17570       safeStrCpy(*savePtr, s, strlen(s)+1);
17571     }
17572     return(*savePtr);
17573 }
17574
17575 char *
17576 PGNDate ()
17577 {
17578     time_t clock;
17579     struct tm *tm;
17580     char buf[MSG_SIZ];
17581
17582     clock = time((time_t *)NULL);
17583     tm = localtime(&clock);
17584     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17585             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17586     return StrSave(buf);
17587 }
17588
17589
17590 char *
17591 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17592 {
17593     int i, j, fromX, fromY, toX, toY;
17594     int whiteToPlay;
17595     char buf[MSG_SIZ];
17596     char *p, *q;
17597     int emptycount;
17598     ChessSquare piece;
17599
17600     whiteToPlay = (gameMode == EditPosition) ?
17601       !blackPlaysFirst : (move % 2 == 0);
17602     p = buf;
17603
17604     /* Piece placement data */
17605     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17606         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17607         emptycount = 0;
17608         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17609             if (boards[move][i][j] == EmptySquare) {
17610                 emptycount++;
17611             } else { ChessSquare piece = boards[move][i][j];
17612                 if (emptycount > 0) {
17613                     if(emptycount<10) /* [HGM] can be >= 10 */
17614                         *p++ = '0' + emptycount;
17615                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17616                     emptycount = 0;
17617                 }
17618                 if(PieceToChar(piece) == '+') {
17619                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17620                     *p++ = '+';
17621                     piece = (ChessSquare)(DEMOTED piece);
17622                 }
17623                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17624                 if(p[-1] == '~') {
17625                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17626                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17627                     *p++ = '~';
17628                 }
17629             }
17630         }
17631         if (emptycount > 0) {
17632             if(emptycount<10) /* [HGM] can be >= 10 */
17633                 *p++ = '0' + emptycount;
17634             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17635             emptycount = 0;
17636         }
17637         *p++ = '/';
17638     }
17639     *(p - 1) = ' ';
17640
17641     /* [HGM] print Crazyhouse or Shogi holdings */
17642     if( gameInfo.holdingsWidth ) {
17643         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17644         q = p;
17645         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17646             piece = boards[move][i][BOARD_WIDTH-1];
17647             if( piece != EmptySquare )
17648               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17649                   *p++ = PieceToChar(piece);
17650         }
17651         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17652             piece = boards[move][BOARD_HEIGHT-i-1][0];
17653             if( piece != EmptySquare )
17654               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17655                   *p++ = PieceToChar(piece);
17656         }
17657
17658         if( q == p ) *p++ = '-';
17659         *p++ = ']';
17660         *p++ = ' ';
17661     }
17662
17663     /* Active color */
17664     *p++ = whiteToPlay ? 'w' : 'b';
17665     *p++ = ' ';
17666
17667   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17668     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17669   } else {
17670   if(nrCastlingRights) {
17671      q = p;
17672      if(appData.fischerCastling) {
17673        /* [HGM] write directly from rights */
17674            if(boards[move][CASTLING][2] != NoRights &&
17675               boards[move][CASTLING][0] != NoRights   )
17676                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17677            if(boards[move][CASTLING][2] != NoRights &&
17678               boards[move][CASTLING][1] != NoRights   )
17679                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17680            if(boards[move][CASTLING][5] != NoRights &&
17681               boards[move][CASTLING][3] != NoRights   )
17682                 *p++ = boards[move][CASTLING][3] + AAA;
17683            if(boards[move][CASTLING][5] != NoRights &&
17684               boards[move][CASTLING][4] != NoRights   )
17685                 *p++ = boards[move][CASTLING][4] + AAA;
17686      } else {
17687
17688         /* [HGM] write true castling rights */
17689         if( nrCastlingRights == 6 ) {
17690             int q, k=0;
17691             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17692                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17693             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17694                  boards[move][CASTLING][2] != NoRights  );
17695             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17696                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17697                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17698                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17699                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17700             }
17701             if(q) *p++ = 'Q';
17702             k = 0;
17703             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17704                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17705             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17706                  boards[move][CASTLING][5] != NoRights  );
17707             if(gameInfo.variant == VariantSChess) {
17708                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17709                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17710                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17711                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17712             }
17713             if(q) *p++ = 'q';
17714         }
17715      }
17716      if (q == p) *p++ = '-'; /* No castling rights */
17717      *p++ = ' ';
17718   }
17719
17720   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17721      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17722      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17723     /* En passant target square */
17724     if (move > backwardMostMove) {
17725         fromX = moveList[move - 1][0] - AAA;
17726         fromY = moveList[move - 1][1] - ONE;
17727         toX = moveList[move - 1][2] - AAA;
17728         toY = moveList[move - 1][3] - ONE;
17729         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17730             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17731             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17732             fromX == toX) {
17733             /* 2-square pawn move just happened */
17734             *p++ = toX + AAA;
17735             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17736         } else {
17737             *p++ = '-';
17738         }
17739     } else if(move == backwardMostMove) {
17740         // [HGM] perhaps we should always do it like this, and forget the above?
17741         if((signed char)boards[move][EP_STATUS] >= 0) {
17742             *p++ = boards[move][EP_STATUS] + AAA;
17743             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17744         } else {
17745             *p++ = '-';
17746         }
17747     } else {
17748         *p++ = '-';
17749     }
17750     *p++ = ' ';
17751   }
17752   }
17753
17754     if(moveCounts)
17755     {   int i = 0, j=move;
17756
17757         /* [HGM] find reversible plies */
17758         if (appData.debugMode) { int k;
17759             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17760             for(k=backwardMostMove; k<=forwardMostMove; k++)
17761                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17762
17763         }
17764
17765         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17766         if( j == backwardMostMove ) i += initialRulePlies;
17767         sprintf(p, "%d ", i);
17768         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17769
17770         /* Fullmove number */
17771         sprintf(p, "%d", (move / 2) + 1);
17772     } else *--p = NULLCHAR;
17773
17774     return StrSave(buf);
17775 }
17776
17777 Boolean
17778 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17779 {
17780     int i, j, k, w=0, subst=0, shuffle=0;
17781     char *p, c;
17782     int emptycount, virgin[BOARD_FILES];
17783     ChessSquare piece;
17784
17785     p = fen;
17786
17787     /* Piece placement data */
17788     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17789         j = 0;
17790         for (;;) {
17791             if (*p == '/' || *p == ' ' || *p == '[' ) {
17792                 if(j > w) w = j;
17793                 emptycount = gameInfo.boardWidth - j;
17794                 while (emptycount--)
17795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17796                 if (*p == '/') p++;
17797                 else if(autoSize) { // we stumbled unexpectedly into end of board
17798                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17799                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17800                     }
17801                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17802                 }
17803                 break;
17804 #if(BOARD_FILES >= 10)
17805             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17806                 p++; emptycount=10;
17807                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17808                 while (emptycount--)
17809                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17810 #endif
17811             } else if (*p == '*') {
17812                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17813             } else if (isdigit(*p)) {
17814                 emptycount = *p++ - '0';
17815                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17816                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17817                 while (emptycount--)
17818                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17819             } else if (*p == '<') {
17820                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17821                 else if (i != 0 || !shuffle) return FALSE;
17822                 p++;
17823             } else if (shuffle && *p == '>') {
17824                 p++; // for now ignore closing shuffle range, and assume rank-end
17825             } else if (*p == '?') {
17826                 if (j >= gameInfo.boardWidth) return FALSE;
17827                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17828                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17829             } else if (*p == '+' || isalpha(*p)) {
17830                 if (j >= gameInfo.boardWidth) return FALSE;
17831                 if(*p=='+') {
17832                     piece = CharToPiece(*++p);
17833                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17834                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17835                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17836                 } else piece = CharToPiece(*p++);
17837
17838                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17839                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17840                     piece = (ChessSquare) (PROMOTED piece);
17841                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17842                     p++;
17843                 }
17844                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17845             } else {
17846                 return FALSE;
17847             }
17848         }
17849     }
17850     while (*p == '/' || *p == ' ') p++;
17851
17852     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17853
17854     /* [HGM] by default clear Crazyhouse holdings, if present */
17855     if(gameInfo.holdingsWidth) {
17856        for(i=0; i<BOARD_HEIGHT; i++) {
17857            board[i][0]             = EmptySquare; /* black holdings */
17858            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17859            board[i][1]             = (ChessSquare) 0; /* black counts */
17860            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17861        }
17862     }
17863
17864     /* [HGM] look for Crazyhouse holdings here */
17865     while(*p==' ') p++;
17866     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17867         int swap=0, wcnt=0, bcnt=0;
17868         if(*p == '[') p++;
17869         if(*p == '<') swap++, p++;
17870         if(*p == '-' ) p++; /* empty holdings */ else {
17871             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17872             /* if we would allow FEN reading to set board size, we would   */
17873             /* have to add holdings and shift the board read so far here   */
17874             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17875                 p++;
17876                 if((int) piece >= (int) BlackPawn ) {
17877                     i = (int)piece - (int)BlackPawn;
17878                     i = PieceToNumber((ChessSquare)i);
17879                     if( i >= gameInfo.holdingsSize ) return FALSE;
17880                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17881                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17882                     bcnt++;
17883                 } else {
17884                     i = (int)piece - (int)WhitePawn;
17885                     i = PieceToNumber((ChessSquare)i);
17886                     if( i >= gameInfo.holdingsSize ) return FALSE;
17887                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17888                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17889                     wcnt++;
17890                 }
17891             }
17892             if(subst) { // substitute back-rank question marks by holdings pieces
17893                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17894                     int k, m, n = bcnt + 1;
17895                     if(board[0][j] == ClearBoard) {
17896                         if(!wcnt) return FALSE;
17897                         n = rand() % wcnt;
17898                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17899                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17900                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17901                             break;
17902                         }
17903                     }
17904                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17905                         if(!bcnt) return FALSE;
17906                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17907                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17908                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17909                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17910                             break;
17911                         }
17912                     }
17913                 }
17914                 subst = 0;
17915             }
17916         }
17917         if(*p == ']') p++;
17918     }
17919
17920     if(subst) return FALSE; // substitution requested, but no holdings
17921
17922     while(*p == ' ') p++;
17923
17924     /* Active color */
17925     c = *p++;
17926     if(appData.colorNickNames) {
17927       if( c == appData.colorNickNames[0] ) c = 'w'; else
17928       if( c == appData.colorNickNames[1] ) c = 'b';
17929     }
17930     switch (c) {
17931       case 'w':
17932         *blackPlaysFirst = FALSE;
17933         break;
17934       case 'b':
17935         *blackPlaysFirst = TRUE;
17936         break;
17937       default:
17938         return FALSE;
17939     }
17940
17941     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17942     /* return the extra info in global variiables             */
17943
17944     /* set defaults in case FEN is incomplete */
17945     board[EP_STATUS] = EP_UNKNOWN;
17946     for(i=0; i<nrCastlingRights; i++ ) {
17947         board[CASTLING][i] =
17948             appData.fischerCastling ? NoRights : initialRights[i];
17949     }   /* assume possible unless obviously impossible */
17950     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17951     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17952     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17953                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17954     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17955     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17956     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17957                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17958     FENrulePlies = 0;
17959
17960     while(*p==' ') p++;
17961     if(nrCastlingRights) {
17962       int fischer = 0;
17963       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17964       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17965           /* castling indicator present, so default becomes no castlings */
17966           for(i=0; i<nrCastlingRights; i++ ) {
17967                  board[CASTLING][i] = NoRights;
17968           }
17969       }
17970       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17971              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17972              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17973              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17974         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17975
17976         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17977             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17978             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17979         }
17980         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17981             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17982         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17983                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17984         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17985                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17986         switch(c) {
17987           case'K':
17988               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17989               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17990               board[CASTLING][2] = whiteKingFile;
17991               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17992               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17993               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17994               break;
17995           case'Q':
17996               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17997               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17998               board[CASTLING][2] = whiteKingFile;
17999               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18000               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18001               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18002               break;
18003           case'k':
18004               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18005               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18006               board[CASTLING][5] = blackKingFile;
18007               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18008               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18009               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18010               break;
18011           case'q':
18012               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18013               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18014               board[CASTLING][5] = blackKingFile;
18015               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18016               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18017               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18018           case '-':
18019               break;
18020           default: /* FRC castlings */
18021               if(c >= 'a') { /* black rights */
18022                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18023                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18024                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18025                   if(i == BOARD_RGHT) break;
18026                   board[CASTLING][5] = i;
18027                   c -= AAA;
18028                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18029                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18030                   if(c > i)
18031                       board[CASTLING][3] = c;
18032                   else
18033                       board[CASTLING][4] = c;
18034               } else { /* white rights */
18035                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18036                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18037                     if(board[0][i] == WhiteKing) break;
18038                   if(i == BOARD_RGHT) break;
18039                   board[CASTLING][2] = i;
18040                   c -= AAA - 'a' + 'A';
18041                   if(board[0][c] >= WhiteKing) break;
18042                   if(c > i)
18043                       board[CASTLING][0] = c;
18044                   else
18045                       board[CASTLING][1] = c;
18046               }
18047         }
18048       }
18049       for(i=0; i<nrCastlingRights; i++)
18050         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18051       if(gameInfo.variant == VariantSChess)
18052         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18053       if(fischer && shuffle) appData.fischerCastling = TRUE;
18054     if (appData.debugMode) {
18055         fprintf(debugFP, "FEN castling rights:");
18056         for(i=0; i<nrCastlingRights; i++)
18057         fprintf(debugFP, " %d", board[CASTLING][i]);
18058         fprintf(debugFP, "\n");
18059     }
18060
18061       while(*p==' ') p++;
18062     }
18063
18064     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18065
18066     /* read e.p. field in games that know e.p. capture */
18067     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18068        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18069        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18070       if(*p=='-') {
18071         p++; board[EP_STATUS] = EP_NONE;
18072       } else {
18073          char c = *p++ - AAA;
18074
18075          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18076          if(*p >= '0' && *p <='9') p++;
18077          board[EP_STATUS] = c;
18078       }
18079     }
18080
18081
18082     if(sscanf(p, "%d", &i) == 1) {
18083         FENrulePlies = i; /* 50-move ply counter */
18084         /* (The move number is still ignored)    */
18085     }
18086
18087     return TRUE;
18088 }
18089
18090 void
18091 EditPositionPasteFEN (char *fen)
18092 {
18093   if (fen != NULL) {
18094     Board initial_position;
18095
18096     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18097       DisplayError(_("Bad FEN position in clipboard"), 0);
18098       return ;
18099     } else {
18100       int savedBlackPlaysFirst = blackPlaysFirst;
18101       EditPositionEvent();
18102       blackPlaysFirst = savedBlackPlaysFirst;
18103       CopyBoard(boards[0], initial_position);
18104       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18105       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18106       DisplayBothClocks();
18107       DrawPosition(FALSE, boards[currentMove]);
18108     }
18109   }
18110 }
18111
18112 static char cseq[12] = "\\   ";
18113
18114 Boolean
18115 set_cont_sequence (char *new_seq)
18116 {
18117     int len;
18118     Boolean ret;
18119
18120     // handle bad attempts to set the sequence
18121         if (!new_seq)
18122                 return 0; // acceptable error - no debug
18123
18124     len = strlen(new_seq);
18125     ret = (len > 0) && (len < sizeof(cseq));
18126     if (ret)
18127       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18128     else if (appData.debugMode)
18129       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18130     return ret;
18131 }
18132
18133 /*
18134     reformat a source message so words don't cross the width boundary.  internal
18135     newlines are not removed.  returns the wrapped size (no null character unless
18136     included in source message).  If dest is NULL, only calculate the size required
18137     for the dest buffer.  lp argument indicats line position upon entry, and it's
18138     passed back upon exit.
18139 */
18140 int
18141 wrap (char *dest, char *src, int count, int width, int *lp)
18142 {
18143     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18144
18145     cseq_len = strlen(cseq);
18146     old_line = line = *lp;
18147     ansi = len = clen = 0;
18148
18149     for (i=0; i < count; i++)
18150     {
18151         if (src[i] == '\033')
18152             ansi = 1;
18153
18154         // if we hit the width, back up
18155         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18156         {
18157             // store i & len in case the word is too long
18158             old_i = i, old_len = len;
18159
18160             // find the end of the last word
18161             while (i && src[i] != ' ' && src[i] != '\n')
18162             {
18163                 i--;
18164                 len--;
18165             }
18166
18167             // word too long?  restore i & len before splitting it
18168             if ((old_i-i+clen) >= width)
18169             {
18170                 i = old_i;
18171                 len = old_len;
18172             }
18173
18174             // extra space?
18175             if (i && src[i-1] == ' ')
18176                 len--;
18177
18178             if (src[i] != ' ' && src[i] != '\n')
18179             {
18180                 i--;
18181                 if (len)
18182                     len--;
18183             }
18184
18185             // now append the newline and continuation sequence
18186             if (dest)
18187                 dest[len] = '\n';
18188             len++;
18189             if (dest)
18190                 strncpy(dest+len, cseq, cseq_len);
18191             len += cseq_len;
18192             line = cseq_len;
18193             clen = cseq_len;
18194             continue;
18195         }
18196
18197         if (dest)
18198             dest[len] = src[i];
18199         len++;
18200         if (!ansi)
18201             line++;
18202         if (src[i] == '\n')
18203             line = 0;
18204         if (src[i] == 'm')
18205             ansi = 0;
18206     }
18207     if (dest && appData.debugMode)
18208     {
18209         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18210             count, width, line, len, *lp);
18211         show_bytes(debugFP, src, count);
18212         fprintf(debugFP, "\ndest: ");
18213         show_bytes(debugFP, dest, len);
18214         fprintf(debugFP, "\n");
18215     }
18216     *lp = dest ? line : old_line;
18217
18218     return len;
18219 }
18220
18221 // [HGM] vari: routines for shelving variations
18222 Boolean modeRestore = FALSE;
18223
18224 void
18225 PushInner (int firstMove, int lastMove)
18226 {
18227         int i, j, nrMoves = lastMove - firstMove;
18228
18229         // push current tail of game on stack
18230         savedResult[storedGames] = gameInfo.result;
18231         savedDetails[storedGames] = gameInfo.resultDetails;
18232         gameInfo.resultDetails = NULL;
18233         savedFirst[storedGames] = firstMove;
18234         savedLast [storedGames] = lastMove;
18235         savedFramePtr[storedGames] = framePtr;
18236         framePtr -= nrMoves; // reserve space for the boards
18237         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18238             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18239             for(j=0; j<MOVE_LEN; j++)
18240                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18241             for(j=0; j<2*MOVE_LEN; j++)
18242                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18243             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18244             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18245             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18246             pvInfoList[firstMove+i-1].depth = 0;
18247             commentList[framePtr+i] = commentList[firstMove+i];
18248             commentList[firstMove+i] = NULL;
18249         }
18250
18251         storedGames++;
18252         forwardMostMove = firstMove; // truncate game so we can start variation
18253 }
18254
18255 void
18256 PushTail (int firstMove, int lastMove)
18257 {
18258         if(appData.icsActive) { // only in local mode
18259                 forwardMostMove = currentMove; // mimic old ICS behavior
18260                 return;
18261         }
18262         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18263
18264         PushInner(firstMove, lastMove);
18265         if(storedGames == 1) GreyRevert(FALSE);
18266         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18267 }
18268
18269 void
18270 PopInner (Boolean annotate)
18271 {
18272         int i, j, nrMoves;
18273         char buf[8000], moveBuf[20];
18274
18275         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18276         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18277         nrMoves = savedLast[storedGames] - currentMove;
18278         if(annotate) {
18279                 int cnt = 10;
18280                 if(!WhiteOnMove(currentMove))
18281                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18282                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18283                 for(i=currentMove; i<forwardMostMove; i++) {
18284                         if(WhiteOnMove(i))
18285                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18286                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18287                         strcat(buf, moveBuf);
18288                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18289                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18290                 }
18291                 strcat(buf, ")");
18292         }
18293         for(i=1; i<=nrMoves; i++) { // copy last variation back
18294             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18295             for(j=0; j<MOVE_LEN; j++)
18296                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18297             for(j=0; j<2*MOVE_LEN; j++)
18298                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18299             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18300             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18301             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18302             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18303             commentList[currentMove+i] = commentList[framePtr+i];
18304             commentList[framePtr+i] = NULL;
18305         }
18306         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18307         framePtr = savedFramePtr[storedGames];
18308         gameInfo.result = savedResult[storedGames];
18309         if(gameInfo.resultDetails != NULL) {
18310             free(gameInfo.resultDetails);
18311       }
18312         gameInfo.resultDetails = savedDetails[storedGames];
18313         forwardMostMove = currentMove + nrMoves;
18314 }
18315
18316 Boolean
18317 PopTail (Boolean annotate)
18318 {
18319         if(appData.icsActive) return FALSE; // only in local mode
18320         if(!storedGames) return FALSE; // sanity
18321         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18322
18323         PopInner(annotate);
18324         if(currentMove < forwardMostMove) ForwardEvent(); else
18325         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18326
18327         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18328         return TRUE;
18329 }
18330
18331 void
18332 CleanupTail ()
18333 {       // remove all shelved variations
18334         int i;
18335         for(i=0; i<storedGames; i++) {
18336             if(savedDetails[i])
18337                 free(savedDetails[i]);
18338             savedDetails[i] = NULL;
18339         }
18340         for(i=framePtr; i<MAX_MOVES; i++) {
18341                 if(commentList[i]) free(commentList[i]);
18342                 commentList[i] = NULL;
18343         }
18344         framePtr = MAX_MOVES-1;
18345         storedGames = 0;
18346 }
18347
18348 void
18349 LoadVariation (int index, char *text)
18350 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18351         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18352         int level = 0, move;
18353
18354         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18355         // first find outermost bracketing variation
18356         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18357             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18358                 if(*p == '{') wait = '}'; else
18359                 if(*p == '[') wait = ']'; else
18360                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18361                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18362             }
18363             if(*p == wait) wait = NULLCHAR; // closing ]} found
18364             p++;
18365         }
18366         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18367         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18368         end[1] = NULLCHAR; // clip off comment beyond variation
18369         ToNrEvent(currentMove-1);
18370         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18371         // kludge: use ParsePV() to append variation to game
18372         move = currentMove;
18373         ParsePV(start, TRUE, TRUE);
18374         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18375         ClearPremoveHighlights();
18376         CommentPopDown();
18377         ToNrEvent(currentMove+1);
18378 }
18379
18380 void
18381 LoadTheme ()
18382 {
18383     char *p, *q, buf[MSG_SIZ];
18384     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18385         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18386         ParseArgsFromString(buf);
18387         ActivateTheme(TRUE); // also redo colors
18388         return;
18389     }
18390     p = nickName;
18391     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18392     {
18393         int len;
18394         q = appData.themeNames;
18395         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18396       if(appData.useBitmaps) {
18397         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18398                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18399                 appData.liteBackTextureMode,
18400                 appData.darkBackTextureMode );
18401       } else {
18402         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18403                 Col2Text(2),   // lightSquareColor
18404                 Col2Text(3) ); // darkSquareColor
18405       }
18406       if(appData.useBorder) {
18407         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18408                 appData.border);
18409       } else {
18410         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18411       }
18412       if(appData.useFont) {
18413         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18414                 appData.renderPiecesWithFont,
18415                 appData.fontToPieceTable,
18416                 Col2Text(9),    // appData.fontBackColorWhite
18417                 Col2Text(10) ); // appData.fontForeColorBlack
18418       } else {
18419         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18420                 appData.pieceDirectory);
18421         if(!appData.pieceDirectory[0])
18422           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18423                 Col2Text(0),   // whitePieceColor
18424                 Col2Text(1) ); // blackPieceColor
18425       }
18426       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18427                 Col2Text(4),   // highlightSquareColor
18428                 Col2Text(5) ); // premoveHighlightColor
18429         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18430         if(insert != q) insert[-1] = NULLCHAR;
18431         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18432         if(q)   free(q);
18433     }
18434     ActivateTheme(FALSE);
18435 }