Allow pseudo-engines to adjust the clocks
[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     cps->pseudo = appData.pseudo[n];
851
852     /* New features added by Tord: */
853     cps->useFEN960 = FALSE;
854     cps->useOOCastle = TRUE;
855     /* End of new features added by Tord. */
856     cps->fenOverride  = appData.fenOverride[n];
857
858     /* [HGM] time odds: set factor for each machine */
859     cps->timeOdds  = appData.timeOdds[n];
860
861     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
862     cps->accumulateTC = appData.accumulateTC[n];
863     cps->maxNrOfSessions = 1;
864
865     /* [HGM] debug */
866     cps->debug = FALSE;
867
868     cps->drawDepth = appData.drawDepth[n];
869     cps->supportsNPS = UNKNOWN;
870     cps->memSize = FALSE;
871     cps->maxCores = FALSE;
872     ASSIGN(cps->egtFormats, "");
873
874     /* [HGM] options */
875     cps->optionSettings  = appData.engOptions[n];
876
877     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
878     cps->isUCI = appData.isUCI[n]; /* [AS] */
879     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
880     cps->highlight = 0;
881
882     if (appData.protocolVersion[n] > PROTOVER
883         || appData.protocolVersion[n] < 1)
884       {
885         char buf[MSG_SIZ];
886         int len;
887
888         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
889                        appData.protocolVersion[n]);
890         if( (len >= MSG_SIZ) && appData.debugMode )
891           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
892
893         DisplayFatalError(buf, 0, 2);
894       }
895     else
896       {
897         cps->protocolVersion = appData.protocolVersion[n];
898       }
899
900     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
901     ParseFeatures(appData.featureDefaults, cps);
902 }
903
904 ChessProgramState *savCps;
905
906 GameMode oldMode;
907
908 void
909 LoadEngine ()
910 {
911     int i;
912     if(WaitForEngine(savCps, LoadEngine)) return;
913     CommonEngineInit(); // recalculate time odds
914     if(gameInfo.variant != StringToVariant(appData.variant)) {
915         // we changed variant when loading the engine; this forces us to reset
916         Reset(TRUE, savCps != &first);
917         oldMode = BeginningOfGame; // to prevent restoring old mode
918     }
919     InitChessProgram(savCps, FALSE);
920     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
921     DisplayMessage("", "");
922     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
923     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
924     ThawUI();
925     SetGNUMode();
926     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
927 }
928
929 void
930 ReplaceEngine (ChessProgramState *cps, int n)
931 {
932     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
933     keepInfo = 1;
934     if(oldMode != BeginningOfGame) EditGameEvent();
935     keepInfo = 0;
936     UnloadEngine(cps);
937     appData.noChessProgram = FALSE;
938     appData.clockMode = TRUE;
939     InitEngine(cps, n);
940     UpdateLogos(TRUE);
941     if(n) return; // only startup first engine immediately; second can wait
942     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
943     LoadEngine();
944 }
945
946 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
947 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
948
949 static char resetOptions[] =
950         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
951         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
952         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
953         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
954
955 void
956 FloatToFront(char **list, char *engineLine)
957 {
958     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
959     int i=0;
960     if(appData.recentEngines <= 0) return;
961     TidyProgramName(engineLine, "localhost", tidy+1);
962     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
963     strncpy(buf+1, *list, MSG_SIZ-50);
964     if(p = strstr(buf, tidy)) { // tidy name appears in list
965         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
966         while(*p++ = *++q); // squeeze out
967     }
968     strcat(tidy, buf+1); // put list behind tidy name
969     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
970     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
971     ASSIGN(*list, tidy+1);
972 }
973
974 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
975
976 void
977 Load (ChessProgramState *cps, int i)
978 {
979     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
980     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
981         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
982         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
983         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
984         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
985         appData.firstProtocolVersion = PROTOVER;
986         ParseArgsFromString(buf);
987         SwapEngines(i);
988         ReplaceEngine(cps, i);
989         FloatToFront(&appData.recentEngineList, engineLine);
990         return;
991     }
992     p = engineName;
993     while(q = strchr(p, SLASH)) p = q+1;
994     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
995     if(engineDir[0] != NULLCHAR) {
996         ASSIGN(appData.directory[i], engineDir); p = engineName;
997     } else if(p != engineName) { // derive directory from engine path, when not given
998         p[-1] = 0;
999         ASSIGN(appData.directory[i], engineName);
1000         p[-1] = SLASH;
1001         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1002     } else { ASSIGN(appData.directory[i], "."); }
1003     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1004     if(params[0]) {
1005         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1006         snprintf(command, MSG_SIZ, "%s %s", p, params);
1007         p = command;
1008     }
1009     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1010     ASSIGN(appData.chessProgram[i], p);
1011     appData.isUCI[i] = isUCI;
1012     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1013     appData.hasOwnBookUCI[i] = hasBook;
1014     if(!nickName[0]) useNick = FALSE;
1015     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1016     if(addToList) {
1017         int len;
1018         char quote;
1019         q = firstChessProgramNames;
1020         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1021         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1022         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1023                         quote, p, quote, appData.directory[i],
1024                         useNick ? " -fn \"" : "",
1025                         useNick ? nickName : "",
1026                         useNick ? "\"" : "",
1027                         v1 ? " -firstProtocolVersion 1" : "",
1028                         hasBook ? "" : " -fNoOwnBookUCI",
1029                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1030                         storeVariant ? " -variant " : "",
1031                         storeVariant ? VariantName(gameInfo.variant) : "");
1032         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1033         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1034         if(insert != q) insert[-1] = NULLCHAR;
1035         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1036         if(q)   free(q);
1037         FloatToFront(&appData.recentEngineList, buf);
1038     }
1039     ReplaceEngine(cps, i);
1040 }
1041
1042 void
1043 InitTimeControls ()
1044 {
1045     int matched, min, sec;
1046     /*
1047      * Parse timeControl resource
1048      */
1049     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1050                           appData.movesPerSession)) {
1051         char buf[MSG_SIZ];
1052         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1053         DisplayFatalError(buf, 0, 2);
1054     }
1055
1056     /*
1057      * Parse searchTime resource
1058      */
1059     if (*appData.searchTime != NULLCHAR) {
1060         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1061         if (matched == 1) {
1062             searchTime = min * 60;
1063         } else if (matched == 2) {
1064             searchTime = min * 60 + sec;
1065         } else {
1066             char buf[MSG_SIZ];
1067             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1068             DisplayFatalError(buf, 0, 2);
1069         }
1070     }
1071 }
1072
1073 void
1074 InitBackEnd1 ()
1075 {
1076
1077     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1078     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1079
1080     GetTimeMark(&programStartTime);
1081     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1082     appData.seedBase = random() + (random()<<15);
1083     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1084
1085     ClearProgramStats();
1086     programStats.ok_to_send = 1;
1087     programStats.seen_stat = 0;
1088
1089     /*
1090      * Initialize game list
1091      */
1092     ListNew(&gameList);
1093
1094
1095     /*
1096      * Internet chess server status
1097      */
1098     if (appData.icsActive) {
1099         appData.matchMode = FALSE;
1100         appData.matchGames = 0;
1101 #if ZIPPY
1102         appData.noChessProgram = !appData.zippyPlay;
1103 #else
1104         appData.zippyPlay = FALSE;
1105         appData.zippyTalk = FALSE;
1106         appData.noChessProgram = TRUE;
1107 #endif
1108         if (*appData.icsHelper != NULLCHAR) {
1109             appData.useTelnet = TRUE;
1110             appData.telnetProgram = appData.icsHelper;
1111         }
1112     } else {
1113         appData.zippyTalk = appData.zippyPlay = FALSE;
1114     }
1115
1116     /* [AS] Initialize pv info list [HGM] and game state */
1117     {
1118         int i, j;
1119
1120         for( i=0; i<=framePtr; i++ ) {
1121             pvInfoList[i].depth = -1;
1122             boards[i][EP_STATUS] = EP_NONE;
1123             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1124         }
1125     }
1126
1127     InitTimeControls();
1128
1129     /* [AS] Adjudication threshold */
1130     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1131
1132     InitEngine(&first, 0);
1133     InitEngine(&second, 1);
1134     CommonEngineInit();
1135
1136     pairing.which = "pairing"; // pairing engine
1137     pairing.pr = NoProc;
1138     pairing.isr = NULL;
1139     pairing.program = appData.pairingEngine;
1140     pairing.host = "localhost";
1141     pairing.dir = ".";
1142
1143     if (appData.icsActive) {
1144         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1145     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1146         appData.clockMode = FALSE;
1147         first.sendTime = second.sendTime = 0;
1148     }
1149
1150 #if ZIPPY
1151     /* Override some settings from environment variables, for backward
1152        compatibility.  Unfortunately it's not feasible to have the env
1153        vars just set defaults, at least in xboard.  Ugh.
1154     */
1155     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1156       ZippyInit();
1157     }
1158 #endif
1159
1160     if (!appData.icsActive) {
1161       char buf[MSG_SIZ];
1162       int len;
1163
1164       /* Check for variants that are supported only in ICS mode,
1165          or not at all.  Some that are accepted here nevertheless
1166          have bugs; see comments below.
1167       */
1168       VariantClass variant = StringToVariant(appData.variant);
1169       switch (variant) {
1170       case VariantBughouse:     /* need four players and two boards */
1171       case VariantKriegspiel:   /* need to hide pieces and move details */
1172         /* case VariantFischeRandom: (Fabien: moved below) */
1173         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1174         if( (len >= MSG_SIZ) && appData.debugMode )
1175           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1176
1177         DisplayFatalError(buf, 0, 2);
1178         return;
1179
1180       case VariantUnknown:
1181       case VariantLoadable:
1182       case Variant29:
1183       case Variant30:
1184       case Variant31:
1185       case Variant32:
1186       case Variant33:
1187       case Variant34:
1188       case Variant35:
1189       case Variant36:
1190       default:
1191         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1192         if( (len >= MSG_SIZ) && appData.debugMode )
1193           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1194
1195         DisplayFatalError(buf, 0, 2);
1196         return;
1197
1198       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1199       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1200       case VariantGothic:     /* [HGM] should work */
1201       case VariantCapablanca: /* [HGM] should work */
1202       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1203       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1204       case VariantChu:        /* [HGM] experimental */
1205       case VariantKnightmate: /* [HGM] should work */
1206       case VariantCylinder:   /* [HGM] untested */
1207       case VariantFalcon:     /* [HGM] untested */
1208       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1209                                  offboard interposition not understood */
1210       case VariantNormal:     /* definitely works! */
1211       case VariantWildCastle: /* pieces not automatically shuffled */
1212       case VariantNoCastle:   /* pieces not automatically shuffled */
1213       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1214       case VariantLosers:     /* should work except for win condition,
1215                                  and doesn't know captures are mandatory */
1216       case VariantSuicide:    /* should work except for win condition,
1217                                  and doesn't know captures are mandatory */
1218       case VariantGiveaway:   /* should work except for win condition,
1219                                  and doesn't know captures are mandatory */
1220       case VariantTwoKings:   /* should work */
1221       case VariantAtomic:     /* should work except for win condition */
1222       case Variant3Check:     /* should work except for win condition */
1223       case VariantShatranj:   /* should work except for all win conditions */
1224       case VariantMakruk:     /* should work except for draw countdown */
1225       case VariantASEAN :     /* should work except for draw countdown */
1226       case VariantBerolina:   /* might work if TestLegality is off */
1227       case VariantCapaRandom: /* should work */
1228       case VariantJanus:      /* should work */
1229       case VariantSuper:      /* experimental */
1230       case VariantGreat:      /* experimental, requires legality testing to be off */
1231       case VariantSChess:     /* S-Chess, should work */
1232       case VariantGrand:      /* should work */
1233       case VariantSpartan:    /* should work */
1234       case VariantLion:       /* should work */
1235       case VariantChuChess:   /* should work */
1236         break;
1237       }
1238     }
1239
1240 }
1241
1242 int
1243 NextIntegerFromString (char ** str, long * value)
1244 {
1245     int result = -1;
1246     char * s = *str;
1247
1248     while( *s == ' ' || *s == '\t' ) {
1249         s++;
1250     }
1251
1252     *value = 0;
1253
1254     if( *s >= '0' && *s <= '9' ) {
1255         while( *s >= '0' && *s <= '9' ) {
1256             *value = *value * 10 + (*s - '0');
1257             s++;
1258         }
1259
1260         result = 0;
1261     }
1262
1263     *str = s;
1264
1265     return result;
1266 }
1267
1268 int
1269 NextTimeControlFromString (char ** str, long * value)
1270 {
1271     long temp;
1272     int result = NextIntegerFromString( str, &temp );
1273
1274     if( result == 0 ) {
1275         *value = temp * 60; /* Minutes */
1276         if( **str == ':' ) {
1277             (*str)++;
1278             result = NextIntegerFromString( str, &temp );
1279             *value += temp; /* Seconds */
1280         }
1281     }
1282
1283     return result;
1284 }
1285
1286 int
1287 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1288 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1289     int result = -1, type = 0; long temp, temp2;
1290
1291     if(**str != ':') return -1; // old params remain in force!
1292     (*str)++;
1293     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1294     if( NextIntegerFromString( str, &temp ) ) return -1;
1295     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1296
1297     if(**str != '/') {
1298         /* time only: incremental or sudden-death time control */
1299         if(**str == '+') { /* increment follows; read it */
1300             (*str)++;
1301             if(**str == '!') type = *(*str)++; // Bronstein TC
1302             if(result = NextIntegerFromString( str, &temp2)) return -1;
1303             *inc = temp2 * 1000;
1304             if(**str == '.') { // read fraction of increment
1305                 char *start = ++(*str);
1306                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1307                 temp2 *= 1000;
1308                 while(start++ < *str) temp2 /= 10;
1309                 *inc += temp2;
1310             }
1311         } else *inc = 0;
1312         *moves = 0; *tc = temp * 1000; *incType = type;
1313         return 0;
1314     }
1315
1316     (*str)++; /* classical time control */
1317     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1318
1319     if(result == 0) {
1320         *moves = temp;
1321         *tc    = temp2 * 1000;
1322         *inc   = 0;
1323         *incType = type;
1324     }
1325     return result;
1326 }
1327
1328 int
1329 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1330 {   /* [HGM] get time to add from the multi-session time-control string */
1331     int incType, moves=1; /* kludge to force reading of first session */
1332     long time, increment;
1333     char *s = tcString;
1334
1335     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1336     do {
1337         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1338         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1339         if(movenr == -1) return time;    /* last move before new session     */
1340         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1341         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1342         if(!moves) return increment;     /* current session is incremental   */
1343         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1344     } while(movenr >= -1);               /* try again for next session       */
1345
1346     return 0; // no new time quota on this move
1347 }
1348
1349 int
1350 ParseTimeControl (char *tc, float ti, int mps)
1351 {
1352   long tc1;
1353   long tc2;
1354   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1355   int min, sec=0;
1356
1357   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1358   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1359       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1360   if(ti > 0) {
1361
1362     if(mps)
1363       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1364     else
1365       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1366   } else {
1367     if(mps)
1368       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1369     else
1370       snprintf(buf, MSG_SIZ, ":%s", mytc);
1371   }
1372   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1373
1374   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1375     return FALSE;
1376   }
1377
1378   if( *tc == '/' ) {
1379     /* Parse second time control */
1380     tc++;
1381
1382     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1383       return FALSE;
1384     }
1385
1386     if( tc2 == 0 ) {
1387       return FALSE;
1388     }
1389
1390     timeControl_2 = tc2 * 1000;
1391   }
1392   else {
1393     timeControl_2 = 0;
1394   }
1395
1396   if( tc1 == 0 ) {
1397     return FALSE;
1398   }
1399
1400   timeControl = tc1 * 1000;
1401
1402   if (ti >= 0) {
1403     timeIncrement = ti * 1000;  /* convert to ms */
1404     movesPerSession = 0;
1405   } else {
1406     timeIncrement = 0;
1407     movesPerSession = mps;
1408   }
1409   return TRUE;
1410 }
1411
1412 void
1413 InitBackEnd2 ()
1414 {
1415     if (appData.debugMode) {
1416 #    ifdef __GIT_VERSION
1417       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1418 #    else
1419       fprintf(debugFP, "Version: %s\n", programVersion);
1420 #    endif
1421     }
1422     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1423
1424     set_cont_sequence(appData.wrapContSeq);
1425     if (appData.matchGames > 0) {
1426         appData.matchMode = TRUE;
1427     } else if (appData.matchMode) {
1428         appData.matchGames = 1;
1429     }
1430     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1431         appData.matchGames = appData.sameColorGames;
1432     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1433         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1434         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1435     }
1436     Reset(TRUE, FALSE);
1437     if (appData.noChessProgram || first.protocolVersion == 1) {
1438       InitBackEnd3();
1439     } else {
1440       /* kludge: allow timeout for initial "feature" commands */
1441       FreezeUI();
1442       DisplayMessage("", _("Starting chess program"));
1443       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1444     }
1445 }
1446
1447 int
1448 CalculateIndex (int index, int gameNr)
1449 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1450     int res;
1451     if(index > 0) return index; // fixed nmber
1452     if(index == 0) return 1;
1453     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1454     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1455     return res;
1456 }
1457
1458 int
1459 LoadGameOrPosition (int gameNr)
1460 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1461     if (*appData.loadGameFile != NULLCHAR) {
1462         if (!LoadGameFromFile(appData.loadGameFile,
1463                 CalculateIndex(appData.loadGameIndex, gameNr),
1464                               appData.loadGameFile, FALSE)) {
1465             DisplayFatalError(_("Bad game file"), 0, 1);
1466             return 0;
1467         }
1468     } else if (*appData.loadPositionFile != NULLCHAR) {
1469         if (!LoadPositionFromFile(appData.loadPositionFile,
1470                 CalculateIndex(appData.loadPositionIndex, gameNr),
1471                                   appData.loadPositionFile)) {
1472             DisplayFatalError(_("Bad position file"), 0, 1);
1473             return 0;
1474         }
1475     }
1476     return 1;
1477 }
1478
1479 void
1480 ReserveGame (int gameNr, char resChar)
1481 {
1482     FILE *tf = fopen(appData.tourneyFile, "r+");
1483     char *p, *q, c, buf[MSG_SIZ];
1484     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1485     safeStrCpy(buf, lastMsg, MSG_SIZ);
1486     DisplayMessage(_("Pick new game"), "");
1487     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1488     ParseArgsFromFile(tf);
1489     p = q = appData.results;
1490     if(appData.debugMode) {
1491       char *r = appData.participants;
1492       fprintf(debugFP, "results = '%s'\n", p);
1493       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1494       fprintf(debugFP, "\n");
1495     }
1496     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1497     nextGame = q - p;
1498     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1499     safeStrCpy(q, p, strlen(p) + 2);
1500     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1501     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1502     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1503         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1504         q[nextGame] = '*';
1505     }
1506     fseek(tf, -(strlen(p)+4), SEEK_END);
1507     c = fgetc(tf);
1508     if(c != '"') // depending on DOS or Unix line endings we can be one off
1509          fseek(tf, -(strlen(p)+2), SEEK_END);
1510     else fseek(tf, -(strlen(p)+3), SEEK_END);
1511     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1512     DisplayMessage(buf, "");
1513     free(p); appData.results = q;
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1515        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1516       int round = appData.defaultMatchGames * appData.tourneyType;
1517       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1518          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1519         UnloadEngine(&first);  // next game belongs to other pairing;
1520         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1521     }
1522     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1523 }
1524
1525 void
1526 MatchEvent (int mode)
1527 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1528         int dummy;
1529         if(matchMode) { // already in match mode: switch it off
1530             abortMatch = TRUE;
1531             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1532             return;
1533         }
1534 //      if(gameMode != BeginningOfGame) {
1535 //          DisplayError(_("You can only start a match from the initial position."), 0);
1536 //          return;
1537 //      }
1538         abortMatch = FALSE;
1539         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1540         /* Set up machine vs. machine match */
1541         nextGame = 0;
1542         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1543         if(appData.tourneyFile[0]) {
1544             ReserveGame(-1, 0);
1545             if(nextGame > appData.matchGames) {
1546                 char buf[MSG_SIZ];
1547                 if(strchr(appData.results, '*') == NULL) {
1548                     FILE *f;
1549                     appData.tourneyCycles++;
1550                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1551                         fclose(f);
1552                         NextTourneyGame(-1, &dummy);
1553                         ReserveGame(-1, 0);
1554                         if(nextGame <= appData.matchGames) {
1555                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1556                             matchMode = mode;
1557                             ScheduleDelayedEvent(NextMatchGame, 10000);
1558                             return;
1559                         }
1560                     }
1561                 }
1562                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1563                 DisplayError(buf, 0);
1564                 appData.tourneyFile[0] = 0;
1565                 return;
1566             }
1567         } else
1568         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1569             DisplayFatalError(_("Can't have a match with no chess programs"),
1570                               0, 2);
1571             return;
1572         }
1573         matchMode = mode;
1574         matchGame = roundNr = 1;
1575         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1576         NextMatchGame();
1577 }
1578
1579 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1580
1581 void
1582 InitBackEnd3 P((void))
1583 {
1584     GameMode initialMode;
1585     char buf[MSG_SIZ];
1586     int err, len;
1587
1588     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1589        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1590         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1591        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1592        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1593         char c, *q = first.variants, *p = strchr(q, ',');
1594         if(p) *p = NULLCHAR;
1595         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1596             int w, h, s;
1597             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1598                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1599             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1600             Reset(TRUE, FALSE);         // and re-initialize
1601         }
1602         if(p) *p = ',';
1603     }
1604
1605     InitChessProgram(&first, startedFromSetupPosition);
1606
1607     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1608         free(programVersion);
1609         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1610         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1611         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1612     }
1613
1614     if (appData.icsActive) {
1615 #ifdef WIN32
1616         /* [DM] Make a console window if needed [HGM] merged ifs */
1617         ConsoleCreate();
1618 #endif
1619         err = establish();
1620         if (err != 0)
1621           {
1622             if (*appData.icsCommPort != NULLCHAR)
1623               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1624                              appData.icsCommPort);
1625             else
1626               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1627                         appData.icsHost, appData.icsPort);
1628
1629             if( (len >= MSG_SIZ) && appData.debugMode )
1630               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1631
1632             DisplayFatalError(buf, err, 1);
1633             return;
1634         }
1635         SetICSMode();
1636         telnetISR =
1637           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1638         fromUserISR =
1639           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1640         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1641             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1642     } else if (appData.noChessProgram) {
1643         SetNCPMode();
1644     } else {
1645         SetGNUMode();
1646     }
1647
1648     if (*appData.cmailGameName != NULLCHAR) {
1649         SetCmailMode();
1650         OpenLoopback(&cmailPR);
1651         cmailISR =
1652           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1653     }
1654
1655     ThawUI();
1656     DisplayMessage("", "");
1657     if (StrCaseCmp(appData.initialMode, "") == 0) {
1658       initialMode = BeginningOfGame;
1659       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1660         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1661         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1662         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1663         ModeHighlight();
1664       }
1665     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1666       initialMode = TwoMachinesPlay;
1667     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1668       initialMode = AnalyzeFile;
1669     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1670       initialMode = AnalyzeMode;
1671     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1672       initialMode = MachinePlaysWhite;
1673     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1674       initialMode = MachinePlaysBlack;
1675     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1676       initialMode = EditGame;
1677     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1678       initialMode = EditPosition;
1679     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1680       initialMode = Training;
1681     } else {
1682       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1683       if( (len >= MSG_SIZ) && appData.debugMode )
1684         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1685
1686       DisplayFatalError(buf, 0, 2);
1687       return;
1688     }
1689
1690     if (appData.matchMode) {
1691         if(appData.tourneyFile[0]) { // start tourney from command line
1692             FILE *f;
1693             if(f = fopen(appData.tourneyFile, "r")) {
1694                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1695                 fclose(f);
1696                 appData.clockMode = TRUE;
1697                 SetGNUMode();
1698             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1699         }
1700         MatchEvent(TRUE);
1701     } else if (*appData.cmailGameName != NULLCHAR) {
1702         /* Set up cmail mode */
1703         ReloadCmailMsgEvent(TRUE);
1704     } else {
1705         /* Set up other modes */
1706         if (initialMode == AnalyzeFile) {
1707           if (*appData.loadGameFile == NULLCHAR) {
1708             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1709             return;
1710           }
1711         }
1712         if (*appData.loadGameFile != NULLCHAR) {
1713             (void) LoadGameFromFile(appData.loadGameFile,
1714                                     appData.loadGameIndex,
1715                                     appData.loadGameFile, TRUE);
1716         } else if (*appData.loadPositionFile != NULLCHAR) {
1717             (void) LoadPositionFromFile(appData.loadPositionFile,
1718                                         appData.loadPositionIndex,
1719                                         appData.loadPositionFile);
1720             /* [HGM] try to make self-starting even after FEN load */
1721             /* to allow automatic setup of fairy variants with wtm */
1722             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1723                 gameMode = BeginningOfGame;
1724                 setboardSpoiledMachineBlack = 1;
1725             }
1726             /* [HGM] loadPos: make that every new game uses the setup */
1727             /* from file as long as we do not switch variant          */
1728             if(!blackPlaysFirst) {
1729                 startedFromPositionFile = TRUE;
1730                 CopyBoard(filePosition, boards[0]);
1731             }
1732         }
1733         if (initialMode == AnalyzeMode) {
1734           if (appData.noChessProgram) {
1735             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1736             return;
1737           }
1738           if (appData.icsActive) {
1739             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1740             return;
1741           }
1742           AnalyzeModeEvent();
1743         } else if (initialMode == AnalyzeFile) {
1744           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1745           ShowThinkingEvent();
1746           AnalyzeFileEvent();
1747           AnalysisPeriodicEvent(1);
1748         } else if (initialMode == MachinePlaysWhite) {
1749           if (appData.noChessProgram) {
1750             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1751                               0, 2);
1752             return;
1753           }
1754           if (appData.icsActive) {
1755             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1756                               0, 2);
1757             return;
1758           }
1759           MachineWhiteEvent();
1760         } else if (initialMode == MachinePlaysBlack) {
1761           if (appData.noChessProgram) {
1762             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1763                               0, 2);
1764             return;
1765           }
1766           if (appData.icsActive) {
1767             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1768                               0, 2);
1769             return;
1770           }
1771           MachineBlackEvent();
1772         } else if (initialMode == TwoMachinesPlay) {
1773           if (appData.noChessProgram) {
1774             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1775                               0, 2);
1776             return;
1777           }
1778           if (appData.icsActive) {
1779             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1780                               0, 2);
1781             return;
1782           }
1783           TwoMachinesEvent();
1784         } else if (initialMode == EditGame) {
1785           EditGameEvent();
1786         } else if (initialMode == EditPosition) {
1787           EditPositionEvent();
1788         } else if (initialMode == Training) {
1789           if (*appData.loadGameFile == NULLCHAR) {
1790             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1791             return;
1792           }
1793           TrainingEvent();
1794         }
1795     }
1796 }
1797
1798 void
1799 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1800 {
1801     DisplayBook(current+1);
1802
1803     MoveHistorySet( movelist, first, last, current, pvInfoList );
1804
1805     EvalGraphSet( first, last, current, pvInfoList );
1806
1807     MakeEngineOutputTitle();
1808 }
1809
1810 /*
1811  * Establish will establish a contact to a remote host.port.
1812  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1813  *  used to talk to the host.
1814  * Returns 0 if okay, error code if not.
1815  */
1816 int
1817 establish ()
1818 {
1819     char buf[MSG_SIZ];
1820
1821     if (*appData.icsCommPort != NULLCHAR) {
1822         /* Talk to the host through a serial comm port */
1823         return OpenCommPort(appData.icsCommPort, &icsPR);
1824
1825     } else if (*appData.gateway != NULLCHAR) {
1826         if (*appData.remoteShell == NULLCHAR) {
1827             /* Use the rcmd protocol to run telnet program on a gateway host */
1828             snprintf(buf, sizeof(buf), "%s %s %s",
1829                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1830             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1831
1832         } else {
1833             /* Use the rsh program to run telnet program on a gateway host */
1834             if (*appData.remoteUser == NULLCHAR) {
1835                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1836                         appData.gateway, appData.telnetProgram,
1837                         appData.icsHost, appData.icsPort);
1838             } else {
1839                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1840                         appData.remoteShell, appData.gateway,
1841                         appData.remoteUser, appData.telnetProgram,
1842                         appData.icsHost, appData.icsPort);
1843             }
1844             return StartChildProcess(buf, "", &icsPR);
1845
1846         }
1847     } else if (appData.useTelnet) {
1848         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1849
1850     } else {
1851         /* TCP socket interface differs somewhat between
1852            Unix and NT; handle details in the front end.
1853            */
1854         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1855     }
1856 }
1857
1858 void
1859 EscapeExpand (char *p, char *q)
1860 {       // [HGM] initstring: routine to shape up string arguments
1861         while(*p++ = *q++) if(p[-1] == '\\')
1862             switch(*q++) {
1863                 case 'n': p[-1] = '\n'; break;
1864                 case 'r': p[-1] = '\r'; break;
1865                 case 't': p[-1] = '\t'; break;
1866                 case '\\': p[-1] = '\\'; break;
1867                 case 0: *p = 0; return;
1868                 default: p[-1] = q[-1]; break;
1869             }
1870 }
1871
1872 void
1873 show_bytes (FILE *fp, char *buf, int count)
1874 {
1875     while (count--) {
1876         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1877             fprintf(fp, "\\%03o", *buf & 0xff);
1878         } else {
1879             putc(*buf, fp);
1880         }
1881         buf++;
1882     }
1883     fflush(fp);
1884 }
1885
1886 /* Returns an errno value */
1887 int
1888 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1889 {
1890     char buf[8192], *p, *q, *buflim;
1891     int left, newcount, outcount;
1892
1893     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1894         *appData.gateway != NULLCHAR) {
1895         if (appData.debugMode) {
1896             fprintf(debugFP, ">ICS: ");
1897             show_bytes(debugFP, message, count);
1898             fprintf(debugFP, "\n");
1899         }
1900         return OutputToProcess(pr, message, count, outError);
1901     }
1902
1903     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1904     p = message;
1905     q = buf;
1906     left = count;
1907     newcount = 0;
1908     while (left) {
1909         if (q >= buflim) {
1910             if (appData.debugMode) {
1911                 fprintf(debugFP, ">ICS: ");
1912                 show_bytes(debugFP, buf, newcount);
1913                 fprintf(debugFP, "\n");
1914             }
1915             outcount = OutputToProcess(pr, buf, newcount, outError);
1916             if (outcount < newcount) return -1; /* to be sure */
1917             q = buf;
1918             newcount = 0;
1919         }
1920         if (*p == '\n') {
1921             *q++ = '\r';
1922             newcount++;
1923         } else if (((unsigned char) *p) == TN_IAC) {
1924             *q++ = (char) TN_IAC;
1925             newcount ++;
1926         }
1927         *q++ = *p++;
1928         newcount++;
1929         left--;
1930     }
1931     if (appData.debugMode) {
1932         fprintf(debugFP, ">ICS: ");
1933         show_bytes(debugFP, buf, newcount);
1934         fprintf(debugFP, "\n");
1935     }
1936     outcount = OutputToProcess(pr, buf, newcount, outError);
1937     if (outcount < newcount) return -1; /* to be sure */
1938     return count;
1939 }
1940
1941 void
1942 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1943 {
1944     int outError, outCount;
1945     static int gotEof = 0;
1946     static FILE *ini;
1947
1948     /* Pass data read from player on to ICS */
1949     if (count > 0) {
1950         gotEof = 0;
1951         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1952         if (outCount < count) {
1953             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954         }
1955         if(have_sent_ICS_logon == 2) {
1956           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1957             fprintf(ini, "%s", message);
1958             have_sent_ICS_logon = 3;
1959           } else
1960             have_sent_ICS_logon = 1;
1961         } else if(have_sent_ICS_logon == 3) {
1962             fprintf(ini, "%s", message);
1963             fclose(ini);
1964           have_sent_ICS_logon = 1;
1965         }
1966     } else if (count < 0) {
1967         RemoveInputSource(isr);
1968         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1969     } else if (gotEof++ > 0) {
1970         RemoveInputSource(isr);
1971         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1972     }
1973 }
1974
1975 void
1976 KeepAlive ()
1977 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1978     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1979     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1980     SendToICS("date\n");
1981     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1982 }
1983
1984 /* added routine for printf style output to ics */
1985 void
1986 ics_printf (char *format, ...)
1987 {
1988     char buffer[MSG_SIZ];
1989     va_list args;
1990
1991     va_start(args, format);
1992     vsnprintf(buffer, sizeof(buffer), format, args);
1993     buffer[sizeof(buffer)-1] = '\0';
1994     SendToICS(buffer);
1995     va_end(args);
1996 }
1997
1998 void
1999 SendToICS (char *s)
2000 {
2001     int count, outCount, outError;
2002
2003     if (icsPR == NoProc) return;
2004
2005     count = strlen(s);
2006     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2007     if (outCount < count) {
2008         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2009     }
2010 }
2011
2012 /* This is used for sending logon scripts to the ICS. Sending
2013    without a delay causes problems when using timestamp on ICC
2014    (at least on my machine). */
2015 void
2016 SendToICSDelayed (char *s, long msdelay)
2017 {
2018     int count, outCount, outError;
2019
2020     if (icsPR == NoProc) return;
2021
2022     count = strlen(s);
2023     if (appData.debugMode) {
2024         fprintf(debugFP, ">ICS: ");
2025         show_bytes(debugFP, s, count);
2026         fprintf(debugFP, "\n");
2027     }
2028     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2029                                       msdelay);
2030     if (outCount < count) {
2031         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2032     }
2033 }
2034
2035
2036 /* Remove all highlighting escape sequences in s
2037    Also deletes any suffix starting with '('
2038    */
2039 char *
2040 StripHighlightAndTitle (char *s)
2041 {
2042     static char retbuf[MSG_SIZ];
2043     char *p = retbuf;
2044
2045     while (*s != NULLCHAR) {
2046         while (*s == '\033') {
2047             while (*s != NULLCHAR && !isalpha(*s)) s++;
2048             if (*s != NULLCHAR) s++;
2049         }
2050         while (*s != NULLCHAR && *s != '\033') {
2051             if (*s == '(' || *s == '[') {
2052                 *p = NULLCHAR;
2053                 return retbuf;
2054             }
2055             *p++ = *s++;
2056         }
2057     }
2058     *p = NULLCHAR;
2059     return retbuf;
2060 }
2061
2062 /* Remove all highlighting escape sequences in s */
2063 char *
2064 StripHighlight (char *s)
2065 {
2066     static char retbuf[MSG_SIZ];
2067     char *p = retbuf;
2068
2069     while (*s != NULLCHAR) {
2070         while (*s == '\033') {
2071             while (*s != NULLCHAR && !isalpha(*s)) s++;
2072             if (*s != NULLCHAR) s++;
2073         }
2074         while (*s != NULLCHAR && *s != '\033') {
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 char engineVariant[MSG_SIZ];
2083 char *variantNames[] = VARIANT_NAMES;
2084 char *
2085 VariantName (VariantClass v)
2086 {
2087     if(v == VariantUnknown || *engineVariant) return engineVariant;
2088     return variantNames[v];
2089 }
2090
2091
2092 /* Identify a variant from the strings the chess servers use or the
2093    PGN Variant tag names we use. */
2094 VariantClass
2095 StringToVariant (char *e)
2096 {
2097     char *p;
2098     int wnum = -1;
2099     VariantClass v = VariantNormal;
2100     int i, found = FALSE;
2101     char buf[MSG_SIZ];
2102     int len;
2103
2104     if (!e) return v;
2105
2106     /* [HGM] skip over optional board-size prefixes */
2107     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2108         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2109         while( *e++ != '_');
2110     }
2111
2112     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2113         v = VariantNormal;
2114         found = TRUE;
2115     } else
2116     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2117       if (p = StrCaseStr(e, variantNames[i])) {
2118         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2119         v = (VariantClass) i;
2120         found = TRUE;
2121         break;
2122       }
2123     }
2124
2125     if (!found) {
2126       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2127           || StrCaseStr(e, "wild/fr")
2128           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2129         v = VariantFischeRandom;
2130       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2131                  (i = 1, p = StrCaseStr(e, "w"))) {
2132         p += i;
2133         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2134         if (isdigit(*p)) {
2135           wnum = atoi(p);
2136         } else {
2137           wnum = -1;
2138         }
2139         switch (wnum) {
2140         case 0: /* FICS only, actually */
2141         case 1:
2142           /* Castling legal even if K starts on d-file */
2143           v = VariantWildCastle;
2144           break;
2145         case 2:
2146         case 3:
2147         case 4:
2148           /* Castling illegal even if K & R happen to start in
2149              normal positions. */
2150           v = VariantNoCastle;
2151           break;
2152         case 5:
2153         case 7:
2154         case 8:
2155         case 10:
2156         case 11:
2157         case 12:
2158         case 13:
2159         case 14:
2160         case 15:
2161         case 18:
2162         case 19:
2163           /* Castling legal iff K & R start in normal positions */
2164           v = VariantNormal;
2165           break;
2166         case 6:
2167         case 20:
2168         case 21:
2169           /* Special wilds for position setup; unclear what to do here */
2170           v = VariantLoadable;
2171           break;
2172         case 9:
2173           /* Bizarre ICC game */
2174           v = VariantTwoKings;
2175           break;
2176         case 16:
2177           v = VariantKriegspiel;
2178           break;
2179         case 17:
2180           v = VariantLosers;
2181           break;
2182         case 22:
2183           v = VariantFischeRandom;
2184           break;
2185         case 23:
2186           v = VariantCrazyhouse;
2187           break;
2188         case 24:
2189           v = VariantBughouse;
2190           break;
2191         case 25:
2192           v = Variant3Check;
2193           break;
2194         case 26:
2195           /* Not quite the same as FICS suicide! */
2196           v = VariantGiveaway;
2197           break;
2198         case 27:
2199           v = VariantAtomic;
2200           break;
2201         case 28:
2202           v = VariantShatranj;
2203           break;
2204
2205         /* Temporary names for future ICC types.  The name *will* change in
2206            the next xboard/WinBoard release after ICC defines it. */
2207         case 29:
2208           v = Variant29;
2209           break;
2210         case 30:
2211           v = Variant30;
2212           break;
2213         case 31:
2214           v = Variant31;
2215           break;
2216         case 32:
2217           v = Variant32;
2218           break;
2219         case 33:
2220           v = Variant33;
2221           break;
2222         case 34:
2223           v = Variant34;
2224           break;
2225         case 35:
2226           v = Variant35;
2227           break;
2228         case 36:
2229           v = Variant36;
2230           break;
2231         case 37:
2232           v = VariantShogi;
2233           break;
2234         case 38:
2235           v = VariantXiangqi;
2236           break;
2237         case 39:
2238           v = VariantCourier;
2239           break;
2240         case 40:
2241           v = VariantGothic;
2242           break;
2243         case 41:
2244           v = VariantCapablanca;
2245           break;
2246         case 42:
2247           v = VariantKnightmate;
2248           break;
2249         case 43:
2250           v = VariantFairy;
2251           break;
2252         case 44:
2253           v = VariantCylinder;
2254           break;
2255         case 45:
2256           v = VariantFalcon;
2257           break;
2258         case 46:
2259           v = VariantCapaRandom;
2260           break;
2261         case 47:
2262           v = VariantBerolina;
2263           break;
2264         case 48:
2265           v = VariantJanus;
2266           break;
2267         case 49:
2268           v = VariantSuper;
2269           break;
2270         case 50:
2271           v = VariantGreat;
2272           break;
2273         case -1:
2274           /* Found "wild" or "w" in the string but no number;
2275              must assume it's normal chess. */
2276           v = VariantNormal;
2277           break;
2278         default:
2279           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2280           if( (len >= MSG_SIZ) && appData.debugMode )
2281             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2282
2283           DisplayError(buf, 0);
2284           v = VariantUnknown;
2285           break;
2286         }
2287       }
2288     }
2289     if (appData.debugMode) {
2290       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2291               e, wnum, VariantName(v));
2292     }
2293     return v;
2294 }
2295
2296 static int leftover_start = 0, leftover_len = 0;
2297 char star_match[STAR_MATCH_N][MSG_SIZ];
2298
2299 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2300    advance *index beyond it, and set leftover_start to the new value of
2301    *index; else return FALSE.  If pattern contains the character '*', it
2302    matches any sequence of characters not containing '\r', '\n', or the
2303    character following the '*' (if any), and the matched sequence(s) are
2304    copied into star_match.
2305    */
2306 int
2307 looking_at ( char *buf, int *index, char *pattern)
2308 {
2309     char *bufp = &buf[*index], *patternp = pattern;
2310     int star_count = 0;
2311     char *matchp = star_match[0];
2312
2313     for (;;) {
2314         if (*patternp == NULLCHAR) {
2315             *index = leftover_start = bufp - buf;
2316             *matchp = NULLCHAR;
2317             return TRUE;
2318         }
2319         if (*bufp == NULLCHAR) return FALSE;
2320         if (*patternp == '*') {
2321             if (*bufp == *(patternp + 1)) {
2322                 *matchp = NULLCHAR;
2323                 matchp = star_match[++star_count];
2324                 patternp += 2;
2325                 bufp++;
2326                 continue;
2327             } else if (*bufp == '\n' || *bufp == '\r') {
2328                 patternp++;
2329                 if (*patternp == NULLCHAR)
2330                   continue;
2331                 else
2332                   return FALSE;
2333             } else {
2334                 *matchp++ = *bufp++;
2335                 continue;
2336             }
2337         }
2338         if (*patternp != *bufp) return FALSE;
2339         patternp++;
2340         bufp++;
2341     }
2342 }
2343
2344 void
2345 SendToPlayer (char *data, int length)
2346 {
2347     int error, outCount;
2348     outCount = OutputToProcess(NoProc, data, length, &error);
2349     if (outCount < length) {
2350         DisplayFatalError(_("Error writing to display"), error, 1);
2351     }
2352 }
2353
2354 void
2355 PackHolding (char packed[], char *holding)
2356 {
2357     char *p = holding;
2358     char *q = packed;
2359     int runlength = 0;
2360     int curr = 9999;
2361     do {
2362         if (*p == curr) {
2363             runlength++;
2364         } else {
2365             switch (runlength) {
2366               case 0:
2367                 break;
2368               case 1:
2369                 *q++ = curr;
2370                 break;
2371               case 2:
2372                 *q++ = curr;
2373                 *q++ = curr;
2374                 break;
2375               default:
2376                 sprintf(q, "%d", runlength);
2377                 while (*q) q++;
2378                 *q++ = curr;
2379                 break;
2380             }
2381             runlength = 1;
2382             curr = *p;
2383         }
2384     } while (*p++);
2385     *q = NULLCHAR;
2386 }
2387
2388 /* Telnet protocol requests from the front end */
2389 void
2390 TelnetRequest (unsigned char ddww, unsigned char option)
2391 {
2392     unsigned char msg[3];
2393     int outCount, outError;
2394
2395     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2396
2397     if (appData.debugMode) {
2398         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2399         switch (ddww) {
2400           case TN_DO:
2401             ddwwStr = "DO";
2402             break;
2403           case TN_DONT:
2404             ddwwStr = "DONT";
2405             break;
2406           case TN_WILL:
2407             ddwwStr = "WILL";
2408             break;
2409           case TN_WONT:
2410             ddwwStr = "WONT";
2411             break;
2412           default:
2413             ddwwStr = buf1;
2414             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2415             break;
2416         }
2417         switch (option) {
2418           case TN_ECHO:
2419             optionStr = "ECHO";
2420             break;
2421           default:
2422             optionStr = buf2;
2423             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2424             break;
2425         }
2426         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2427     }
2428     msg[0] = TN_IAC;
2429     msg[1] = ddww;
2430     msg[2] = option;
2431     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2432     if (outCount < 3) {
2433         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2434     }
2435 }
2436
2437 void
2438 DoEcho ()
2439 {
2440     if (!appData.icsActive) return;
2441     TelnetRequest(TN_DO, TN_ECHO);
2442 }
2443
2444 void
2445 DontEcho ()
2446 {
2447     if (!appData.icsActive) return;
2448     TelnetRequest(TN_DONT, TN_ECHO);
2449 }
2450
2451 void
2452 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2453 {
2454     /* put the holdings sent to us by the server on the board holdings area */
2455     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2456     char p;
2457     ChessSquare piece;
2458
2459     if(gameInfo.holdingsWidth < 2)  return;
2460     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2461         return; // prevent overwriting by pre-board holdings
2462
2463     if( (int)lowestPiece >= BlackPawn ) {
2464         holdingsColumn = 0;
2465         countsColumn = 1;
2466         holdingsStartRow = BOARD_HEIGHT-1;
2467         direction = -1;
2468     } else {
2469         holdingsColumn = BOARD_WIDTH-1;
2470         countsColumn = BOARD_WIDTH-2;
2471         holdingsStartRow = 0;
2472         direction = 1;
2473     }
2474
2475     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2476         board[i][holdingsColumn] = EmptySquare;
2477         board[i][countsColumn]   = (ChessSquare) 0;
2478     }
2479     while( (p=*holdings++) != NULLCHAR ) {
2480         piece = CharToPiece( ToUpper(p) );
2481         if(piece == EmptySquare) continue;
2482         /*j = (int) piece - (int) WhitePawn;*/
2483         j = PieceToNumber(piece);
2484         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2485         if(j < 0) continue;               /* should not happen */
2486         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2487         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2488         board[holdingsStartRow+j*direction][countsColumn]++;
2489     }
2490 }
2491
2492
2493 void
2494 VariantSwitch (Board board, VariantClass newVariant)
2495 {
2496    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2497    static Board oldBoard;
2498
2499    startedFromPositionFile = FALSE;
2500    if(gameInfo.variant == newVariant) return;
2501
2502    /* [HGM] This routine is called each time an assignment is made to
2503     * gameInfo.variant during a game, to make sure the board sizes
2504     * are set to match the new variant. If that means adding or deleting
2505     * holdings, we shift the playing board accordingly
2506     * This kludge is needed because in ICS observe mode, we get boards
2507     * of an ongoing game without knowing the variant, and learn about the
2508     * latter only later. This can be because of the move list we requested,
2509     * in which case the game history is refilled from the beginning anyway,
2510     * but also when receiving holdings of a crazyhouse game. In the latter
2511     * case we want to add those holdings to the already received position.
2512     */
2513
2514
2515    if (appData.debugMode) {
2516      fprintf(debugFP, "Switch board from %s to %s\n",
2517              VariantName(gameInfo.variant), VariantName(newVariant));
2518      setbuf(debugFP, NULL);
2519    }
2520    shuffleOpenings = 0;       /* [HGM] shuffle */
2521    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2522    switch(newVariant)
2523      {
2524      case VariantShogi:
2525        newWidth = 9;  newHeight = 9;
2526        gameInfo.holdingsSize = 7;
2527      case VariantBughouse:
2528      case VariantCrazyhouse:
2529        newHoldingsWidth = 2; break;
2530      case VariantGreat:
2531        newWidth = 10;
2532      case VariantSuper:
2533        newHoldingsWidth = 2;
2534        gameInfo.holdingsSize = 8;
2535        break;
2536      case VariantGothic:
2537      case VariantCapablanca:
2538      case VariantCapaRandom:
2539        newWidth = 10;
2540      default:
2541        newHoldingsWidth = gameInfo.holdingsSize = 0;
2542      };
2543
2544    if(newWidth  != gameInfo.boardWidth  ||
2545       newHeight != gameInfo.boardHeight ||
2546       newHoldingsWidth != gameInfo.holdingsWidth ) {
2547
2548      /* shift position to new playing area, if needed */
2549      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2550        for(i=0; i<BOARD_HEIGHT; i++)
2551          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2552            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2553              board[i][j];
2554        for(i=0; i<newHeight; i++) {
2555          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2556          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2557        }
2558      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2559        for(i=0; i<BOARD_HEIGHT; i++)
2560          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2561            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2562              board[i][j];
2563      }
2564      board[HOLDINGS_SET] = 0;
2565      gameInfo.boardWidth  = newWidth;
2566      gameInfo.boardHeight = newHeight;
2567      gameInfo.holdingsWidth = newHoldingsWidth;
2568      gameInfo.variant = newVariant;
2569      InitDrawingSizes(-2, 0);
2570    } else gameInfo.variant = newVariant;
2571    CopyBoard(oldBoard, board);   // remember correctly formatted board
2572      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2573    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2574 }
2575
2576 static int loggedOn = FALSE;
2577
2578 /*-- Game start info cache: --*/
2579 int gs_gamenum;
2580 char gs_kind[MSG_SIZ];
2581 static char player1Name[128] = "";
2582 static char player2Name[128] = "";
2583 static char cont_seq[] = "\n\\   ";
2584 static int player1Rating = -1;
2585 static int player2Rating = -1;
2586 /*----------------------------*/
2587
2588 ColorClass curColor = ColorNormal;
2589 int suppressKibitz = 0;
2590
2591 // [HGM] seekgraph
2592 Boolean soughtPending = FALSE;
2593 Boolean seekGraphUp;
2594 #define MAX_SEEK_ADS 200
2595 #define SQUARE 0x80
2596 char *seekAdList[MAX_SEEK_ADS];
2597 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2598 float tcList[MAX_SEEK_ADS];
2599 char colorList[MAX_SEEK_ADS];
2600 int nrOfSeekAds = 0;
2601 int minRating = 1010, maxRating = 2800;
2602 int hMargin = 10, vMargin = 20, h, w;
2603 extern int squareSize, lineGap;
2604
2605 void
2606 PlotSeekAd (int i)
2607 {
2608         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2609         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2610         if(r < minRating+100 && r >=0 ) r = minRating+100;
2611         if(r > maxRating) r = maxRating;
2612         if(tc < 1.f) tc = 1.f;
2613         if(tc > 95.f) tc = 95.f;
2614         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2615         y = ((double)r - minRating)/(maxRating - minRating)
2616             * (h-vMargin-squareSize/8-1) + vMargin;
2617         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2618         if(strstr(seekAdList[i], " u ")) color = 1;
2619         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2620            !strstr(seekAdList[i], "bullet") &&
2621            !strstr(seekAdList[i], "blitz") &&
2622            !strstr(seekAdList[i], "standard") ) color = 2;
2623         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2624         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2625 }
2626
2627 void
2628 PlotSingleSeekAd (int i)
2629 {
2630         PlotSeekAd(i);
2631 }
2632
2633 void
2634 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2635 {
2636         char buf[MSG_SIZ], *ext = "";
2637         VariantClass v = StringToVariant(type);
2638         if(strstr(type, "wild")) {
2639             ext = type + 4; // append wild number
2640             if(v == VariantFischeRandom) type = "chess960"; else
2641             if(v == VariantLoadable) type = "setup"; else
2642             type = VariantName(v);
2643         }
2644         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2645         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2646             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2647             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2648             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2649             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2650             seekNrList[nrOfSeekAds] = nr;
2651             zList[nrOfSeekAds] = 0;
2652             seekAdList[nrOfSeekAds++] = StrSave(buf);
2653             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2654         }
2655 }
2656
2657 void
2658 EraseSeekDot (int i)
2659 {
2660     int x = xList[i], y = yList[i], d=squareSize/4, k;
2661     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2662     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2663     // now replot every dot that overlapped
2664     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2665         int xx = xList[k], yy = yList[k];
2666         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2667             DrawSeekDot(xx, yy, colorList[k]);
2668     }
2669 }
2670
2671 void
2672 RemoveSeekAd (int nr)
2673 {
2674         int i;
2675         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2676             EraseSeekDot(i);
2677             if(seekAdList[i]) free(seekAdList[i]);
2678             seekAdList[i] = seekAdList[--nrOfSeekAds];
2679             seekNrList[i] = seekNrList[nrOfSeekAds];
2680             ratingList[i] = ratingList[nrOfSeekAds];
2681             colorList[i]  = colorList[nrOfSeekAds];
2682             tcList[i] = tcList[nrOfSeekAds];
2683             xList[i]  = xList[nrOfSeekAds];
2684             yList[i]  = yList[nrOfSeekAds];
2685             zList[i]  = zList[nrOfSeekAds];
2686             seekAdList[nrOfSeekAds] = NULL;
2687             break;
2688         }
2689 }
2690
2691 Boolean
2692 MatchSoughtLine (char *line)
2693 {
2694     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2695     int nr, base, inc, u=0; char dummy;
2696
2697     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2699        (u=1) &&
2700        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2701         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2702         // match: compact and save the line
2703         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2704         return TRUE;
2705     }
2706     return FALSE;
2707 }
2708
2709 int
2710 DrawSeekGraph ()
2711 {
2712     int i;
2713     if(!seekGraphUp) return FALSE;
2714     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2715     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2716
2717     DrawSeekBackground(0, 0, w, h);
2718     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2719     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2720     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2721         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2722         yy = h-1-yy;
2723         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2724         if(i%500 == 0) {
2725             char buf[MSG_SIZ];
2726             snprintf(buf, MSG_SIZ, "%d", i);
2727             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2728         }
2729     }
2730     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2731     for(i=1; i<100; i+=(i<10?1:5)) {
2732         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2733         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2734         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2738         }
2739     }
2740     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2741     return TRUE;
2742 }
2743
2744 int
2745 SeekGraphClick (ClickType click, int x, int y, int moving)
2746 {
2747     static int lastDown = 0, displayed = 0, lastSecond;
2748     if(y < 0) return FALSE;
2749     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2750         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2751         if(!seekGraphUp) return FALSE;
2752         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2753         DrawPosition(TRUE, NULL);
2754         return TRUE;
2755     }
2756     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2757         if(click == Release || moving) return FALSE;
2758         nrOfSeekAds = 0;
2759         soughtPending = TRUE;
2760         SendToICS(ics_prefix);
2761         SendToICS("sought\n"); // should this be "sought all"?
2762     } else { // issue challenge based on clicked ad
2763         int dist = 10000; int i, closest = 0, second = 0;
2764         for(i=0; i<nrOfSeekAds; i++) {
2765             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2766             if(d < dist) { dist = d; closest = i; }
2767             second += (d - zList[i] < 120); // count in-range ads
2768             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2769         }
2770         if(dist < 120) {
2771             char buf[MSG_SIZ];
2772             second = (second > 1);
2773             if(displayed != closest || second != lastSecond) {
2774                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2775                 lastSecond = second; displayed = closest;
2776             }
2777             if(click == Press) {
2778                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2779                 lastDown = closest;
2780                 return TRUE;
2781             } // on press 'hit', only show info
2782             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2783             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2784             SendToICS(ics_prefix);
2785             SendToICS(buf);
2786             return TRUE; // let incoming board of started game pop down the graph
2787         } else if(click == Release) { // release 'miss' is ignored
2788             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2789             if(moving == 2) { // right up-click
2790                 nrOfSeekAds = 0; // refresh graph
2791                 soughtPending = TRUE;
2792                 SendToICS(ics_prefix);
2793                 SendToICS("sought\n"); // should this be "sought all"?
2794             }
2795             return TRUE;
2796         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2797         // press miss or release hit 'pop down' seek graph
2798         seekGraphUp = FALSE;
2799         DrawPosition(TRUE, NULL);
2800     }
2801     return TRUE;
2802 }
2803
2804 void
2805 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2806 {
2807 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2808 #define STARTED_NONE 0
2809 #define STARTED_MOVES 1
2810 #define STARTED_BOARD 2
2811 #define STARTED_OBSERVE 3
2812 #define STARTED_HOLDINGS 4
2813 #define STARTED_CHATTER 5
2814 #define STARTED_COMMENT 6
2815 #define STARTED_MOVES_NOHIDE 7
2816
2817     static int started = STARTED_NONE;
2818     static char parse[20000];
2819     static int parse_pos = 0;
2820     static char buf[BUF_SIZE + 1];
2821     static int firstTime = TRUE, intfSet = FALSE;
2822     static ColorClass prevColor = ColorNormal;
2823     static int savingComment = FALSE;
2824     static int cmatch = 0; // continuation sequence match
2825     char *bp;
2826     char str[MSG_SIZ];
2827     int i, oldi;
2828     int buf_len;
2829     int next_out;
2830     int tkind;
2831     int backup;    /* [DM] For zippy color lines */
2832     char *p;
2833     char talker[MSG_SIZ]; // [HGM] chat
2834     int channel, collective=0;
2835
2836     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2837
2838     if (appData.debugMode) {
2839       if (!error) {
2840         fprintf(debugFP, "<ICS: ");
2841         show_bytes(debugFP, data, count);
2842         fprintf(debugFP, "\n");
2843       }
2844     }
2845
2846     if (appData.debugMode) { int f = forwardMostMove;
2847         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2848                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2849                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2850     }
2851     if (count > 0) {
2852         /* If last read ended with a partial line that we couldn't parse,
2853            prepend it to the new read and try again. */
2854         if (leftover_len > 0) {
2855             for (i=0; i<leftover_len; i++)
2856               buf[i] = buf[leftover_start + i];
2857         }
2858
2859     /* copy new characters into the buffer */
2860     bp = buf + leftover_len;
2861     buf_len=leftover_len;
2862     for (i=0; i<count; i++)
2863     {
2864         // ignore these
2865         if (data[i] == '\r')
2866             continue;
2867
2868         // join lines split by ICS?
2869         if (!appData.noJoin)
2870         {
2871             /*
2872                 Joining just consists of finding matches against the
2873                 continuation sequence, and discarding that sequence
2874                 if found instead of copying it.  So, until a match
2875                 fails, there's nothing to do since it might be the
2876                 complete sequence, and thus, something we don't want
2877                 copied.
2878             */
2879             if (data[i] == cont_seq[cmatch])
2880             {
2881                 cmatch++;
2882                 if (cmatch == strlen(cont_seq))
2883                 {
2884                     cmatch = 0; // complete match.  just reset the counter
2885
2886                     /*
2887                         it's possible for the ICS to not include the space
2888                         at the end of the last word, making our [correct]
2889                         join operation fuse two separate words.  the server
2890                         does this when the space occurs at the width setting.
2891                     */
2892                     if (!buf_len || buf[buf_len-1] != ' ')
2893                     {
2894                         *bp++ = ' ';
2895                         buf_len++;
2896                     }
2897                 }
2898                 continue;
2899             }
2900             else if (cmatch)
2901             {
2902                 /*
2903                     match failed, so we have to copy what matched before
2904                     falling through and copying this character.  In reality,
2905                     this will only ever be just the newline character, but
2906                     it doesn't hurt to be precise.
2907                 */
2908                 strncpy(bp, cont_seq, cmatch);
2909                 bp += cmatch;
2910                 buf_len += cmatch;
2911                 cmatch = 0;
2912             }
2913         }
2914
2915         // copy this char
2916         *bp++ = data[i];
2917         buf_len++;
2918     }
2919
2920         buf[buf_len] = NULLCHAR;
2921 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2922         next_out = 0;
2923         leftover_start = 0;
2924
2925         i = 0;
2926         while (i < buf_len) {
2927             /* Deal with part of the TELNET option negotiation
2928                protocol.  We refuse to do anything beyond the
2929                defaults, except that we allow the WILL ECHO option,
2930                which ICS uses to turn off password echoing when we are
2931                directly connected to it.  We reject this option
2932                if localLineEditing mode is on (always on in xboard)
2933                and we are talking to port 23, which might be a real
2934                telnet server that will try to keep WILL ECHO on permanently.
2935              */
2936             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2937                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2938                 unsigned char option;
2939                 oldi = i;
2940                 switch ((unsigned char) buf[++i]) {
2941                   case TN_WILL:
2942                     if (appData.debugMode)
2943                       fprintf(debugFP, "\n<WILL ");
2944                     switch (option = (unsigned char) buf[++i]) {
2945                       case TN_ECHO:
2946                         if (appData.debugMode)
2947                           fprintf(debugFP, "ECHO ");
2948                         /* Reply only if this is a change, according
2949                            to the protocol rules. */
2950                         if (remoteEchoOption) break;
2951                         if (appData.localLineEditing &&
2952                             atoi(appData.icsPort) == TN_PORT) {
2953                             TelnetRequest(TN_DONT, TN_ECHO);
2954                         } else {
2955                             EchoOff();
2956                             TelnetRequest(TN_DO, TN_ECHO);
2957                             remoteEchoOption = TRUE;
2958                         }
2959                         break;
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we don't want it. */
2964                         TelnetRequest(TN_DONT, option);
2965                         break;
2966                     }
2967                     break;
2968                   case TN_WONT:
2969                     if (appData.debugMode)
2970                       fprintf(debugFP, "\n<WONT ");
2971                     switch (option = (unsigned char) buf[++i]) {
2972                       case TN_ECHO:
2973                         if (appData.debugMode)
2974                           fprintf(debugFP, "ECHO ");
2975                         /* Reply only if this is a change, according
2976                            to the protocol rules. */
2977                         if (!remoteEchoOption) break;
2978                         EchoOn();
2979                         TelnetRequest(TN_DONT, TN_ECHO);
2980                         remoteEchoOption = FALSE;
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", (unsigned char) option);
2985                         /* Whatever this is, it must already be turned
2986                            off, because we never agree to turn on
2987                            anything non-default, so according to the
2988                            protocol rules, we don't reply. */
2989                         break;
2990                     }
2991                     break;
2992                   case TN_DO:
2993                     if (appData.debugMode)
2994                       fprintf(debugFP, "\n<DO ");
2995                     switch (option = (unsigned char) buf[++i]) {
2996                       default:
2997                         /* Whatever this is, we refuse to do it. */
2998                         if (appData.debugMode)
2999                           fprintf(debugFP, "%d ", option);
3000                         TelnetRequest(TN_WONT, option);
3001                         break;
3002                     }
3003                     break;
3004                   case TN_DONT:
3005                     if (appData.debugMode)
3006                       fprintf(debugFP, "\n<DONT ");
3007                     switch (option = (unsigned char) buf[++i]) {
3008                       default:
3009                         if (appData.debugMode)
3010                           fprintf(debugFP, "%d ", option);
3011                         /* Whatever this is, we are already not doing
3012                            it, because we never agree to do anything
3013                            non-default, so according to the protocol
3014                            rules, we don't reply. */
3015                         break;
3016                     }
3017                     break;
3018                   case TN_IAC:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<IAC ");
3021                     /* Doubled IAC; pass it through */
3022                     i--;
3023                     break;
3024                   default:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3027                     /* Drop all other telnet commands on the floor */
3028                     break;
3029                 }
3030                 if (oldi > next_out)
3031                   SendToPlayer(&buf[next_out], oldi - next_out);
3032                 if (++i > next_out)
3033                   next_out = i;
3034                 continue;
3035             }
3036
3037             /* OK, this at least will *usually* work */
3038             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3039                 loggedOn = TRUE;
3040             }
3041
3042             if (loggedOn && !intfSet) {
3043                 if (ics_type == ICS_ICC) {
3044                   snprintf(str, MSG_SIZ,
3045                           "/set-quietly interface %s\n/set-quietly style 12\n",
3046                           programVersion);
3047                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3048                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3049                 } else if (ics_type == ICS_CHESSNET) {
3050                   snprintf(str, MSG_SIZ, "/style 12\n");
3051                 } else {
3052                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3053                   strcat(str, programVersion);
3054                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3055                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3056                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3057 #ifdef WIN32
3058                   strcat(str, "$iset nohighlight 1\n");
3059 #endif
3060                   strcat(str, "$iset lock 1\n$style 12\n");
3061                 }
3062                 SendToICS(str);
3063                 NotifyFrontendLogin();
3064                 intfSet = TRUE;
3065             }
3066
3067             if (started == STARTED_COMMENT) {
3068                 /* Accumulate characters in comment */
3069                 parse[parse_pos++] = buf[i];
3070                 if (buf[i] == '\n') {
3071                     parse[parse_pos] = NULLCHAR;
3072                     if(chattingPartner>=0) {
3073                         char mess[MSG_SIZ];
3074                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3075                         OutputChatMessage(chattingPartner, mess);
3076                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3077                             int p;
3078                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3079                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3080                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3081                                 OutputChatMessage(p, mess);
3082                                 break;
3083                             }
3084                         }
3085                         chattingPartner = -1;
3086                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3087                         collective = 0;
3088                     } else
3089                     if(!suppressKibitz) // [HGM] kibitz
3090                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3091                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3092                         int nrDigit = 0, nrAlph = 0, j;
3093                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3094                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3095                         parse[parse_pos] = NULLCHAR;
3096                         // try to be smart: if it does not look like search info, it should go to
3097                         // ICS interaction window after all, not to engine-output window.
3098                         for(j=0; j<parse_pos; j++) { // count letters and digits
3099                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3100                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3101                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3102                         }
3103                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3104                             int depth=0; float score;
3105                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3106                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3107                                 pvInfoList[forwardMostMove-1].depth = depth;
3108                                 pvInfoList[forwardMostMove-1].score = 100*score;
3109                             }
3110                             OutputKibitz(suppressKibitz, parse);
3111                         } else {
3112                             char tmp[MSG_SIZ];
3113                             if(gameMode == IcsObserving) // restore original ICS messages
3114                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3115                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3116                             else
3117                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3118                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3119                             SendToPlayer(tmp, strlen(tmp));
3120                         }
3121                         next_out = i+1; // [HGM] suppress printing in ICS window
3122                     }
3123                     started = STARTED_NONE;
3124                 } else {
3125                     /* Don't match patterns against characters in comment */
3126                     i++;
3127                     continue;
3128                 }
3129             }
3130             if (started == STARTED_CHATTER) {
3131                 if (buf[i] != '\n') {
3132                     /* Don't match patterns against characters in chatter */
3133                     i++;
3134                     continue;
3135                 }
3136                 started = STARTED_NONE;
3137                 if(suppressKibitz) next_out = i+1;
3138             }
3139
3140             /* Kludge to deal with rcmd protocol */
3141             if (firstTime && looking_at(buf, &i, "\001*")) {
3142                 DisplayFatalError(&buf[1], 0, 1);
3143                 continue;
3144             } else {
3145                 firstTime = FALSE;
3146             }
3147
3148             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3149                 ics_type = ICS_ICC;
3150                 ics_prefix = "/";
3151                 if (appData.debugMode)
3152                   fprintf(debugFP, "ics_type %d\n", ics_type);
3153                 continue;
3154             }
3155             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3156                 ics_type = ICS_FICS;
3157                 ics_prefix = "$";
3158                 if (appData.debugMode)
3159                   fprintf(debugFP, "ics_type %d\n", ics_type);
3160                 continue;
3161             }
3162             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3163                 ics_type = ICS_CHESSNET;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169
3170             if (!loggedOn &&
3171                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3172                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3173                  looking_at(buf, &i, "will be \"*\""))) {
3174               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3175               continue;
3176             }
3177
3178             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3179               char buf[MSG_SIZ];
3180               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3181               DisplayIcsInteractionTitle(buf);
3182               have_set_title = TRUE;
3183             }
3184
3185             /* skip finger notes */
3186             if (started == STARTED_NONE &&
3187                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3188                  (buf[i] == '1' && buf[i+1] == '0')) &&
3189                 buf[i+2] == ':' && buf[i+3] == ' ') {
3190               started = STARTED_CHATTER;
3191               i += 3;
3192               continue;
3193             }
3194
3195             oldi = i;
3196             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3197             if(appData.seekGraph) {
3198                 if(soughtPending && MatchSoughtLine(buf+i)) {
3199                     i = strstr(buf+i, "rated") - buf;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     next_out = leftover_start = i;
3202                     started = STARTED_CHATTER;
3203                     suppressKibitz = TRUE;
3204                     continue;
3205                 }
3206                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3207                         && looking_at(buf, &i, "* ads displayed")) {
3208                     soughtPending = FALSE;
3209                     seekGraphUp = TRUE;
3210                     DrawSeekGraph();
3211                     continue;
3212                 }
3213                 if(appData.autoRefresh) {
3214                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3215                         int s = (ics_type == ICS_ICC); // ICC format differs
3216                         if(seekGraphUp)
3217                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3218                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3219                         looking_at(buf, &i, "*% "); // eat prompt
3220                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3221                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                         next_out = i; // suppress
3223                         continue;
3224                     }
3225                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3226                         char *p = star_match[0];
3227                         while(*p) {
3228                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3229                             while(*p && *p++ != ' '); // next
3230                         }
3231                         looking_at(buf, &i, "*% "); // eat prompt
3232                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = i;
3234                         continue;
3235                     }
3236                 }
3237             }
3238
3239             /* skip formula vars */
3240             if (started == STARTED_NONE &&
3241                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3242               started = STARTED_CHATTER;
3243               i += 3;
3244               continue;
3245             }
3246
3247             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3248             if (appData.autoKibitz && started == STARTED_NONE &&
3249                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3250                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3251                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3252                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3253                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3254                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3255                         suppressKibitz = TRUE;
3256                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3257                         next_out = i;
3258                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3259                                 && (gameMode == IcsPlayingWhite)) ||
3260                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3261                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3262                             started = STARTED_CHATTER; // own kibitz we simply discard
3263                         else {
3264                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3265                             parse_pos = 0; parse[0] = NULLCHAR;
3266                             savingComment = TRUE;
3267                             suppressKibitz = gameMode != IcsObserving ? 2 :
3268                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3269                         }
3270                         continue;
3271                 } else
3272                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3273                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3274                          && atoi(star_match[0])) {
3275                     // suppress the acknowledgements of our own autoKibitz
3276                     char *p;
3277                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3279                     SendToPlayer(star_match[0], strlen(star_match[0]));
3280                     if(looking_at(buf, &i, "*% ")) // eat prompt
3281                         suppressKibitz = FALSE;
3282                     next_out = i;
3283                     continue;
3284                 }
3285             } // [HGM] kibitz: end of patch
3286
3287             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3288
3289             // [HGM] chat: intercept tells by users for which we have an open chat window
3290             channel = -1;
3291             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3292                                            looking_at(buf, &i, "* whispers:") ||
3293                                            looking_at(buf, &i, "* kibitzes:") ||
3294                                            looking_at(buf, &i, "* shouts:") ||
3295                                            looking_at(buf, &i, "* c-shouts:") ||
3296                                            looking_at(buf, &i, "--> * ") ||
3297                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3300                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3301                 int p;
3302                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3303                 chattingPartner = -1; collective = 0;
3304
3305                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3306                 for(p=0; p<MAX_CHAT; p++) {
3307                     collective = 1;
3308                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3309                     talker[0] = '['; strcat(talker, "] ");
3310                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3311                     chattingPartner = p; break;
3312                     }
3313                 } else
3314                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3315                 for(p=0; p<MAX_CHAT; p++) {
3316                     collective = 1;
3317                     if(!strcmp("kibitzes", chatPartner[p])) {
3318                         talker[0] = '['; strcat(talker, "] ");
3319                         chattingPartner = p; break;
3320                     }
3321                 } else
3322                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3323                 for(p=0; p<MAX_CHAT; p++) {
3324                     collective = 1;
3325                     if(!strcmp("whispers", chatPartner[p])) {
3326                         talker[0] = '['; strcat(talker, "] ");
3327                         chattingPartner = p; break;
3328                     }
3329                 } else
3330                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3331                   if(buf[i-8] == '-' && buf[i-3] == 't')
3332                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3333                     collective = 1;
3334                     if(!strcmp("c-shouts", chatPartner[p])) {
3335                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3336                         chattingPartner = p; break;
3337                     }
3338                   }
3339                   if(chattingPartner < 0)
3340                   for(p=0; p<MAX_CHAT; p++) {
3341                     collective = 1;
3342                     if(!strcmp("shouts", chatPartner[p])) {
3343                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3344                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3345                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                 }
3350                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3351                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3352                     talker[0] = 0;
3353                     Colorize(ColorTell, FALSE);
3354                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3355                     collective |= 2;
3356                     chattingPartner = p; break;
3357                 }
3358                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3359                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3360                     started = STARTED_COMMENT;
3361                     parse_pos = 0; parse[0] = NULLCHAR;
3362                     savingComment = 3 + chattingPartner; // counts as TRUE
3363                     if(collective == 3) i = oldi; else {
3364                         suppressKibitz = TRUE;
3365                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3366                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3367                         continue;
3368                     }
3369                 }
3370             } // [HGM] chat: end of patch
3371
3372           backup = i;
3373             if (appData.zippyTalk || appData.zippyPlay) {
3374                 /* [DM] Backup address for color zippy lines */
3375 #if ZIPPY
3376                if (loggedOn == TRUE)
3377                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3378                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3379 #endif
3380             } // [DM] 'else { ' deleted
3381                 if (
3382                     /* Regular tells and says */
3383                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3384                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3385                     looking_at(buf, &i, "* says: ") ||
3386                     /* Don't color "message" or "messages" output */
3387                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3388                     looking_at(buf, &i, "*. * at *:*: ") ||
3389                     looking_at(buf, &i, "--* (*:*): ") ||
3390                     /* Message notifications (same color as tells) */
3391                     looking_at(buf, &i, "* has left a message ") ||
3392                     looking_at(buf, &i, "* just sent you a message:\n") ||
3393                     /* Whispers and kibitzes */
3394                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3395                     looking_at(buf, &i, "* kibitzes: ") ||
3396                     /* Channel tells */
3397                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3398
3399                   if (tkind == 1 && strchr(star_match[0], ':')) {
3400                       /* Avoid "tells you:" spoofs in channels */
3401                      tkind = 3;
3402                   }
3403                   if (star_match[0][0] == NULLCHAR ||
3404                       strchr(star_match[0], ' ') ||
3405                       (tkind == 3 && strchr(star_match[1], ' '))) {
3406                     /* Reject bogus matches */
3407                     i = oldi;
3408                   } else {
3409                     if (appData.colorize) {
3410                       if (oldi > next_out) {
3411                         SendToPlayer(&buf[next_out], oldi - next_out);
3412                         next_out = oldi;
3413                       }
3414                       switch (tkind) {
3415                       case 1:
3416                         Colorize(ColorTell, FALSE);
3417                         curColor = ColorTell;
3418                         break;
3419                       case 2:
3420                         Colorize(ColorKibitz, FALSE);
3421                         curColor = ColorKibitz;
3422                         break;
3423                       case 3:
3424                         p = strrchr(star_match[1], '(');
3425                         if (p == NULL) {
3426                           p = star_match[1];
3427                         } else {
3428                           p++;
3429                         }
3430                         if (atoi(p) == 1) {
3431                           Colorize(ColorChannel1, FALSE);
3432                           curColor = ColorChannel1;
3433                         } else {
3434                           Colorize(ColorChannel, FALSE);
3435                           curColor = ColorChannel;
3436                         }
3437                         break;
3438                       case 5:
3439                         curColor = ColorNormal;
3440                         break;
3441                       }
3442                     }
3443                     if (started == STARTED_NONE && appData.autoComment &&
3444                         (gameMode == IcsObserving ||
3445                          gameMode == IcsPlayingWhite ||
3446                          gameMode == IcsPlayingBlack)) {
3447                       parse_pos = i - oldi;
3448                       memcpy(parse, &buf[oldi], parse_pos);
3449                       parse[parse_pos] = NULLCHAR;
3450                       started = STARTED_COMMENT;
3451                       savingComment = TRUE;
3452                     } else if(collective != 3) {
3453                       started = STARTED_CHATTER;
3454                       savingComment = FALSE;
3455                     }
3456                     loggedOn = TRUE;
3457                     continue;
3458                   }
3459                 }
3460
3461                 if (looking_at(buf, &i, "* s-shouts: ") ||
3462                     looking_at(buf, &i, "* c-shouts: ")) {
3463                     if (appData.colorize) {
3464                         if (oldi > next_out) {
3465                             SendToPlayer(&buf[next_out], oldi - next_out);
3466                             next_out = oldi;
3467                         }
3468                         Colorize(ColorSShout, FALSE);
3469                         curColor = ColorSShout;
3470                     }
3471                     loggedOn = TRUE;
3472                     started = STARTED_CHATTER;
3473                     continue;
3474                 }
3475
3476                 if (looking_at(buf, &i, "--->")) {
3477                     loggedOn = TRUE;
3478                     continue;
3479                 }
3480
3481                 if (looking_at(buf, &i, "* shouts: ") ||
3482                     looking_at(buf, &i, "--> ")) {
3483                     if (appData.colorize) {
3484                         if (oldi > next_out) {
3485                             SendToPlayer(&buf[next_out], oldi - next_out);
3486                             next_out = oldi;
3487                         }
3488                         Colorize(ColorShout, FALSE);
3489                         curColor = ColorShout;
3490                     }
3491                     loggedOn = TRUE;
3492                     started = STARTED_CHATTER;
3493                     continue;
3494                 }
3495
3496                 if (looking_at( buf, &i, "Challenge:")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorChallenge, FALSE);
3503                         curColor = ColorChallenge;
3504                     }
3505                     loggedOn = TRUE;
3506                     continue;
3507                 }
3508
3509                 if (looking_at(buf, &i, "* offers you") ||
3510                     looking_at(buf, &i, "* offers to be") ||
3511                     looking_at(buf, &i, "* would like to") ||
3512                     looking_at(buf, &i, "* requests to") ||
3513                     looking_at(buf, &i, "Your opponent offers") ||
3514                     looking_at(buf, &i, "Your opponent requests")) {
3515
3516                     if (appData.colorize) {
3517                         if (oldi > next_out) {
3518                             SendToPlayer(&buf[next_out], oldi - next_out);
3519                             next_out = oldi;
3520                         }
3521                         Colorize(ColorRequest, FALSE);
3522                         curColor = ColorRequest;
3523                     }
3524                     continue;
3525                 }
3526
3527                 if (looking_at(buf, &i, "* (*) seeking")) {
3528                     if (appData.colorize) {
3529                         if (oldi > next_out) {
3530                             SendToPlayer(&buf[next_out], oldi - next_out);
3531                             next_out = oldi;
3532                         }
3533                         Colorize(ColorSeek, FALSE);
3534                         curColor = ColorSeek;
3535                     }
3536                     continue;
3537             }
3538
3539           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3540
3541             if (looking_at(buf, &i, "\\   ")) {
3542                 if (prevColor != ColorNormal) {
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                         next_out = oldi;
3546                     }
3547                     Colorize(prevColor, TRUE);
3548                     curColor = prevColor;
3549                 }
3550                 if (savingComment) {
3551                     parse_pos = i - oldi;
3552                     memcpy(parse, &buf[oldi], parse_pos);
3553                     parse[parse_pos] = NULLCHAR;
3554                     started = STARTED_COMMENT;
3555                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3556                         chattingPartner = savingComment - 3; // kludge to remember the box
3557                 } else {
3558                     started = STARTED_CHATTER;
3559                 }
3560                 continue;
3561             }
3562
3563             if (looking_at(buf, &i, "Black Strength :") ||
3564                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3565                 looking_at(buf, &i, "<10>") ||
3566                 looking_at(buf, &i, "#@#")) {
3567                 /* Wrong board style */
3568                 loggedOn = TRUE;
3569                 SendToICS(ics_prefix);
3570                 SendToICS("set style 12\n");
3571                 SendToICS(ics_prefix);
3572                 SendToICS("refresh\n");
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "login:")) {
3577               if (!have_sent_ICS_logon) {
3578                 if(ICSInitScript())
3579                   have_sent_ICS_logon = 1;
3580                 else // no init script was found
3581                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3582               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3583                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3584               }
3585                 continue;
3586             }
3587
3588             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3589                 (looking_at(buf, &i, "\n<12> ") ||
3590                  looking_at(buf, &i, "<12> "))) {
3591                 loggedOn = TRUE;
3592                 if (oldi > next_out) {
3593                     SendToPlayer(&buf[next_out], oldi - next_out);
3594                 }
3595                 next_out = i;
3596                 started = STARTED_BOARD;
3597                 parse_pos = 0;
3598                 continue;
3599             }
3600
3601             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3602                 looking_at(buf, &i, "<b1> ")) {
3603                 if (oldi > next_out) {
3604                     SendToPlayer(&buf[next_out], oldi - next_out);
3605                 }
3606                 next_out = i;
3607                 started = STARTED_HOLDINGS;
3608                 parse_pos = 0;
3609                 continue;
3610             }
3611
3612             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3613                 loggedOn = TRUE;
3614                 /* Header for a move list -- first line */
3615
3616                 switch (ics_getting_history) {
3617                   case H_FALSE:
3618                     switch (gameMode) {
3619                       case IcsIdle:
3620                       case BeginningOfGame:
3621                         /* User typed "moves" or "oldmoves" while we
3622                            were idle.  Pretend we asked for these
3623                            moves and soak them up so user can step
3624                            through them and/or save them.
3625                            */
3626                         Reset(FALSE, TRUE);
3627                         gameMode = IcsObserving;
3628                         ModeHighlight();
3629                         ics_gamenum = -1;
3630                         ics_getting_history = H_GOT_UNREQ_HEADER;
3631                         break;
3632                       case EditGame: /*?*/
3633                       case EditPosition: /*?*/
3634                         /* Should above feature work in these modes too? */
3635                         /* For now it doesn't */
3636                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3637                         break;
3638                       default:
3639                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3640                         break;
3641                     }
3642                     break;
3643                   case H_REQUESTED:
3644                     /* Is this the right one? */
3645                     if (gameInfo.white && gameInfo.black &&
3646                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3647                         strcmp(gameInfo.black, star_match[2]) == 0) {
3648                         /* All is well */
3649                         ics_getting_history = H_GOT_REQ_HEADER;
3650                     }
3651                     break;
3652                   case H_GOT_REQ_HEADER:
3653                   case H_GOT_UNREQ_HEADER:
3654                   case H_GOT_UNWANTED_HEADER:
3655                   case H_GETTING_MOVES:
3656                     /* Should not happen */
3657                     DisplayError(_("Error gathering move list: two headers"), 0);
3658                     ics_getting_history = H_FALSE;
3659                     break;
3660                 }
3661
3662                 /* Save player ratings into gameInfo if needed */
3663                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3664                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3665                     (gameInfo.whiteRating == -1 ||
3666                      gameInfo.blackRating == -1)) {
3667
3668                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3669                     gameInfo.blackRating = string_to_rating(star_match[3]);
3670                     if (appData.debugMode)
3671                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3672                               gameInfo.whiteRating, gameInfo.blackRating);
3673                 }
3674                 continue;
3675             }
3676
3677             if (looking_at(buf, &i,
3678               "* * match, initial time: * minute*, increment: * second")) {
3679                 /* Header for a move list -- second line */
3680                 /* Initial board will follow if this is a wild game */
3681                 if (gameInfo.event != NULL) free(gameInfo.event);
3682                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3683                 gameInfo.event = StrSave(str);
3684                 /* [HGM] we switched variant. Translate boards if needed. */
3685                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3686                 continue;
3687             }
3688
3689             if (looking_at(buf, &i, "Move  ")) {
3690                 /* Beginning of a move list */
3691                 switch (ics_getting_history) {
3692                   case H_FALSE:
3693                     /* Normally should not happen */
3694                     /* Maybe user hit reset while we were parsing */
3695                     break;
3696                   case H_REQUESTED:
3697                     /* Happens if we are ignoring a move list that is not
3698                      * the one we just requested.  Common if the user
3699                      * tries to observe two games without turning off
3700                      * getMoveList */
3701                     break;
3702                   case H_GETTING_MOVES:
3703                     /* Should not happen */
3704                     DisplayError(_("Error gathering move list: nested"), 0);
3705                     ics_getting_history = H_FALSE;
3706                     break;
3707                   case H_GOT_REQ_HEADER:
3708                     ics_getting_history = H_GETTING_MOVES;
3709                     started = STARTED_MOVES;
3710                     parse_pos = 0;
3711                     if (oldi > next_out) {
3712                         SendToPlayer(&buf[next_out], oldi - next_out);
3713                     }
3714                     break;
3715                   case H_GOT_UNREQ_HEADER:
3716                     ics_getting_history = H_GETTING_MOVES;
3717                     started = STARTED_MOVES_NOHIDE;
3718                     parse_pos = 0;
3719                     break;
3720                   case H_GOT_UNWANTED_HEADER:
3721                     ics_getting_history = H_FALSE;
3722                     break;
3723                 }
3724                 continue;
3725             }
3726
3727             if (looking_at(buf, &i, "% ") ||
3728                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3729                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3730                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3731                     soughtPending = FALSE;
3732                     seekGraphUp = TRUE;
3733                     DrawSeekGraph();
3734                 }
3735                 if(suppressKibitz) next_out = i;
3736                 savingComment = FALSE;
3737                 suppressKibitz = 0;
3738                 switch (started) {
3739                   case STARTED_MOVES:
3740                   case STARTED_MOVES_NOHIDE:
3741                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3742                     parse[parse_pos + i - oldi] = NULLCHAR;
3743                     ParseGameHistory(parse);
3744 #if ZIPPY
3745                     if (appData.zippyPlay && first.initDone) {
3746                         FeedMovesToProgram(&first, forwardMostMove);
3747                         if (gameMode == IcsPlayingWhite) {
3748                             if (WhiteOnMove(forwardMostMove)) {
3749                                 if (first.sendTime) {
3750                                   if (first.useColors) {
3751                                     SendToProgram("black\n", &first);
3752                                   }
3753                                   SendTimeRemaining(&first, TRUE);
3754                                 }
3755                                 if (first.useColors) {
3756                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3757                                 }
3758                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3759                                 first.maybeThinking = TRUE;
3760                             } else {
3761                                 if (first.usePlayother) {
3762                                   if (first.sendTime) {
3763                                     SendTimeRemaining(&first, TRUE);
3764                                   }
3765                                   SendToProgram("playother\n", &first);
3766                                   firstMove = FALSE;
3767                                 } else {
3768                                   firstMove = TRUE;
3769                                 }
3770                             }
3771                         } else if (gameMode == IcsPlayingBlack) {
3772                             if (!WhiteOnMove(forwardMostMove)) {
3773                                 if (first.sendTime) {
3774                                   if (first.useColors) {
3775                                     SendToProgram("white\n", &first);
3776                                   }
3777                                   SendTimeRemaining(&first, FALSE);
3778                                 }
3779                                 if (first.useColors) {
3780                                   SendToProgram("black\n", &first);
3781                                 }
3782                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3783                                 first.maybeThinking = TRUE;
3784                             } else {
3785                                 if (first.usePlayother) {
3786                                   if (first.sendTime) {
3787                                     SendTimeRemaining(&first, FALSE);
3788                                   }
3789                                   SendToProgram("playother\n", &first);
3790                                   firstMove = FALSE;
3791                                 } else {
3792                                   firstMove = TRUE;
3793                                 }
3794                             }
3795                         }
3796                     }
3797 #endif
3798                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3799                         /* Moves came from oldmoves or moves command
3800                            while we weren't doing anything else.
3801                            */
3802                         currentMove = forwardMostMove;
3803                         ClearHighlights();/*!!could figure this out*/
3804                         flipView = appData.flipView;
3805                         DrawPosition(TRUE, boards[currentMove]);
3806                         DisplayBothClocks();
3807                         snprintf(str, MSG_SIZ, "%s %s %s",
3808                                 gameInfo.white, _("vs."),  gameInfo.black);
3809                         DisplayTitle(str);
3810                         gameMode = IcsIdle;
3811                     } else {
3812                         /* Moves were history of an active game */
3813                         if (gameInfo.resultDetails != NULL) {
3814                             free(gameInfo.resultDetails);
3815                             gameInfo.resultDetails = NULL;
3816                         }
3817                     }
3818                     HistorySet(parseList, backwardMostMove,
3819                                forwardMostMove, currentMove-1);
3820                     DisplayMove(currentMove - 1);
3821                     if (started == STARTED_MOVES) next_out = i;
3822                     started = STARTED_NONE;
3823                     ics_getting_history = H_FALSE;
3824                     break;
3825
3826                   case STARTED_OBSERVE:
3827                     started = STARTED_NONE;
3828                     SendToICS(ics_prefix);
3829                     SendToICS("refresh\n");
3830                     break;
3831
3832                   default:
3833                     break;
3834                 }
3835                 if(bookHit) { // [HGM] book: simulate book reply
3836                     static char bookMove[MSG_SIZ]; // a bit generous?
3837
3838                     programStats.nodes = programStats.depth = programStats.time =
3839                     programStats.score = programStats.got_only_move = 0;
3840                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3841
3842                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3843                     strcat(bookMove, bookHit);
3844                     HandleMachineMove(bookMove, &first);
3845                 }
3846                 continue;
3847             }
3848
3849             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3850                  started == STARTED_HOLDINGS ||
3851                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3852                 /* Accumulate characters in move list or board */
3853                 parse[parse_pos++] = buf[i];
3854             }
3855
3856             /* Start of game messages.  Mostly we detect start of game
3857                when the first board image arrives.  On some versions
3858                of the ICS, though, we need to do a "refresh" after starting
3859                to observe in order to get the current board right away. */
3860             if (looking_at(buf, &i, "Adding game * to observation list")) {
3861                 started = STARTED_OBSERVE;
3862                 continue;
3863             }
3864
3865             /* Handle auto-observe */
3866             if (appData.autoObserve &&
3867                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3868                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3869                 char *player;
3870                 /* Choose the player that was highlighted, if any. */
3871                 if (star_match[0][0] == '\033' ||
3872                     star_match[1][0] != '\033') {
3873                     player = star_match[0];
3874                 } else {
3875                     player = star_match[2];
3876                 }
3877                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3878                         ics_prefix, StripHighlightAndTitle(player));
3879                 SendToICS(str);
3880
3881                 /* Save ratings from notify string */
3882                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3883                 player1Rating = string_to_rating(star_match[1]);
3884                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3885                 player2Rating = string_to_rating(star_match[3]);
3886
3887                 if (appData.debugMode)
3888                   fprintf(debugFP,
3889                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3890                           player1Name, player1Rating,
3891                           player2Name, player2Rating);
3892
3893                 continue;
3894             }
3895
3896             /* Deal with automatic examine mode after a game,
3897                and with IcsObserving -> IcsExamining transition */
3898             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3899                 looking_at(buf, &i, "has made you an examiner of game *")) {
3900
3901                 int gamenum = atoi(star_match[0]);
3902                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3903                     gamenum == ics_gamenum) {
3904                     /* We were already playing or observing this game;
3905                        no need to refetch history */
3906                     gameMode = IcsExamining;
3907                     if (pausing) {
3908                         pauseExamForwardMostMove = forwardMostMove;
3909                     } else if (currentMove < forwardMostMove) {
3910                         ForwardInner(forwardMostMove);
3911                     }
3912                 } else {
3913                     /* I don't think this case really can happen */
3914                     SendToICS(ics_prefix);
3915                     SendToICS("refresh\n");
3916                 }
3917                 continue;
3918             }
3919
3920             /* Error messages */
3921 //          if (ics_user_moved) {
3922             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3923                 if (looking_at(buf, &i, "Illegal move") ||
3924                     looking_at(buf, &i, "Not a legal move") ||
3925                     looking_at(buf, &i, "Your king is in check") ||
3926                     looking_at(buf, &i, "It isn't your turn") ||
3927                     looking_at(buf, &i, "It is not your move")) {
3928                     /* Illegal move */
3929                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3930                         currentMove = forwardMostMove-1;
3931                         DisplayMove(currentMove - 1); /* before DMError */
3932                         DrawPosition(FALSE, boards[currentMove]);
3933                         SwitchClocks(forwardMostMove-1); // [HGM] race
3934                         DisplayBothClocks();
3935                     }
3936                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3937                     ics_user_moved = 0;
3938                     continue;
3939                 }
3940             }
3941
3942             if (looking_at(buf, &i, "still have time") ||
3943                 looking_at(buf, &i, "not out of time") ||
3944                 looking_at(buf, &i, "either player is out of time") ||
3945                 looking_at(buf, &i, "has timeseal; checking")) {
3946                 /* We must have called his flag a little too soon */
3947                 whiteFlag = blackFlag = FALSE;
3948                 continue;
3949             }
3950
3951             if (looking_at(buf, &i, "added * seconds to") ||
3952                 looking_at(buf, &i, "seconds were added to")) {
3953                 /* Update the clocks */
3954                 SendToICS(ics_prefix);
3955                 SendToICS("refresh\n");
3956                 continue;
3957             }
3958
3959             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3960                 ics_clock_paused = TRUE;
3961                 StopClocks();
3962                 continue;
3963             }
3964
3965             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3966                 ics_clock_paused = FALSE;
3967                 StartClocks();
3968                 continue;
3969             }
3970
3971             /* Grab player ratings from the Creating: message.
3972                Note we have to check for the special case when
3973                the ICS inserts things like [white] or [black]. */
3974             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3975                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3976                 /* star_matches:
3977                    0    player 1 name (not necessarily white)
3978                    1    player 1 rating
3979                    2    empty, white, or black (IGNORED)
3980                    3    player 2 name (not necessarily black)
3981                    4    player 2 rating
3982
3983                    The names/ratings are sorted out when the game
3984                    actually starts (below).
3985                 */
3986                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3987                 player1Rating = string_to_rating(star_match[1]);
3988                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3989                 player2Rating = string_to_rating(star_match[4]);
3990
3991                 if (appData.debugMode)
3992                   fprintf(debugFP,
3993                           "Ratings from 'Creating:' %s %d, %s %d\n",
3994                           player1Name, player1Rating,
3995                           player2Name, player2Rating);
3996
3997                 continue;
3998             }
3999
4000             /* Improved generic start/end-of-game messages */
4001             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4002                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4003                 /* If tkind == 0: */
4004                 /* star_match[0] is the game number */
4005                 /*           [1] is the white player's name */
4006                 /*           [2] is the black player's name */
4007                 /* For end-of-game: */
4008                 /*           [3] is the reason for the game end */
4009                 /*           [4] is a PGN end game-token, preceded by " " */
4010                 /* For start-of-game: */
4011                 /*           [3] begins with "Creating" or "Continuing" */
4012                 /*           [4] is " *" or empty (don't care). */
4013                 int gamenum = atoi(star_match[0]);
4014                 char *whitename, *blackname, *why, *endtoken;
4015                 ChessMove endtype = EndOfFile;
4016
4017                 if (tkind == 0) {
4018                   whitename = star_match[1];
4019                   blackname = star_match[2];
4020                   why = star_match[3];
4021                   endtoken = star_match[4];
4022                 } else {
4023                   whitename = star_match[1];
4024                   blackname = star_match[3];
4025                   why = star_match[5];
4026                   endtoken = star_match[6];
4027                 }
4028
4029                 /* Game start messages */
4030                 if (strncmp(why, "Creating ", 9) == 0 ||
4031                     strncmp(why, "Continuing ", 11) == 0) {
4032                     gs_gamenum = gamenum;
4033                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4034                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4035                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4036 #if ZIPPY
4037                     if (appData.zippyPlay) {
4038                         ZippyGameStart(whitename, blackname);
4039                     }
4040 #endif /*ZIPPY*/
4041                     partnerBoardValid = FALSE; // [HGM] bughouse
4042                     continue;
4043                 }
4044
4045                 /* Game end messages */
4046                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4047                     ics_gamenum != gamenum) {
4048                     continue;
4049                 }
4050                 while (endtoken[0] == ' ') endtoken++;
4051                 switch (endtoken[0]) {
4052                   case '*':
4053                   default:
4054                     endtype = GameUnfinished;
4055                     break;
4056                   case '0':
4057                     endtype = BlackWins;
4058                     break;
4059                   case '1':
4060                     if (endtoken[1] == '/')
4061                       endtype = GameIsDrawn;
4062                     else
4063                       endtype = WhiteWins;
4064                     break;
4065                 }
4066                 GameEnds(endtype, why, GE_ICS);
4067 #if ZIPPY
4068                 if (appData.zippyPlay && first.initDone) {
4069                     ZippyGameEnd(endtype, why);
4070                     if (first.pr == NoProc) {
4071                       /* Start the next process early so that we'll
4072                          be ready for the next challenge */
4073                       StartChessProgram(&first);
4074                     }
4075                     /* Send "new" early, in case this command takes
4076                        a long time to finish, so that we'll be ready
4077                        for the next challenge. */
4078                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4079                     Reset(TRUE, TRUE);
4080                 }
4081 #endif /*ZIPPY*/
4082                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4083                 continue;
4084             }
4085
4086             if (looking_at(buf, &i, "Removing game * from observation") ||
4087                 looking_at(buf, &i, "no longer observing game *") ||
4088                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4089                 if (gameMode == IcsObserving &&
4090                     atoi(star_match[0]) == ics_gamenum)
4091                   {
4092                       /* icsEngineAnalyze */
4093                       if (appData.icsEngineAnalyze) {
4094                             ExitAnalyzeMode();
4095                             ModeHighlight();
4096                       }
4097                       StopClocks();
4098                       gameMode = IcsIdle;
4099                       ics_gamenum = -1;
4100                       ics_user_moved = FALSE;
4101                   }
4102                 continue;
4103             }
4104
4105             if (looking_at(buf, &i, "no longer examining game *")) {
4106                 if (gameMode == IcsExamining &&
4107                     atoi(star_match[0]) == ics_gamenum)
4108                   {
4109                       gameMode = IcsIdle;
4110                       ics_gamenum = -1;
4111                       ics_user_moved = FALSE;
4112                   }
4113                 continue;
4114             }
4115
4116             /* Advance leftover_start past any newlines we find,
4117                so only partial lines can get reparsed */
4118             if (looking_at(buf, &i, "\n")) {
4119                 prevColor = curColor;
4120                 if (curColor != ColorNormal) {
4121                     if (oldi > next_out) {
4122                         SendToPlayer(&buf[next_out], oldi - next_out);
4123                         next_out = oldi;
4124                     }
4125                     Colorize(ColorNormal, FALSE);
4126                     curColor = ColorNormal;
4127                 }
4128                 if (started == STARTED_BOARD) {
4129                     started = STARTED_NONE;
4130                     parse[parse_pos] = NULLCHAR;
4131                     ParseBoard12(parse);
4132                     ics_user_moved = 0;
4133
4134                     /* Send premove here */
4135                     if (appData.premove) {
4136                       char str[MSG_SIZ];
4137                       if (currentMove == 0 &&
4138                           gameMode == IcsPlayingWhite &&
4139                           appData.premoveWhite) {
4140                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4141                         if (appData.debugMode)
4142                           fprintf(debugFP, "Sending premove:\n");
4143                         SendToICS(str);
4144                       } else if (currentMove == 1 &&
4145                                  gameMode == IcsPlayingBlack &&
4146                                  appData.premoveBlack) {
4147                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4148                         if (appData.debugMode)
4149                           fprintf(debugFP, "Sending premove:\n");
4150                         SendToICS(str);
4151                       } else if (gotPremove) {
4152                         gotPremove = 0;
4153                         ClearPremoveHighlights();
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                           UserMoveEvent(premoveFromX, premoveFromY,
4157                                         premoveToX, premoveToY,
4158                                         premovePromoChar);
4159                       }
4160                     }
4161
4162                     /* Usually suppress following prompt */
4163                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4164                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4165                         if (looking_at(buf, &i, "*% ")) {
4166                             savingComment = FALSE;
4167                             suppressKibitz = 0;
4168                         }
4169                     }
4170                     next_out = i;
4171                 } else if (started == STARTED_HOLDINGS) {
4172                     int gamenum;
4173                     char new_piece[MSG_SIZ];
4174                     started = STARTED_NONE;
4175                     parse[parse_pos] = NULLCHAR;
4176                     if (appData.debugMode)
4177                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4178                                                         parse, currentMove);
4179                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4180                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4181                         if (gameInfo.variant == VariantNormal) {
4182                           /* [HGM] We seem to switch variant during a game!
4183                            * Presumably no holdings were displayed, so we have
4184                            * to move the position two files to the right to
4185                            * create room for them!
4186                            */
4187                           VariantClass newVariant;
4188                           switch(gameInfo.boardWidth) { // base guess on board width
4189                                 case 9:  newVariant = VariantShogi; break;
4190                                 case 10: newVariant = VariantGreat; break;
4191                                 default: newVariant = VariantCrazyhouse; break;
4192                           }
4193                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4194                           /* Get a move list just to see the header, which
4195                              will tell us whether this is really bug or zh */
4196                           if (ics_getting_history == H_FALSE) {
4197                             ics_getting_history = H_REQUESTED;
4198                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4199                             SendToICS(str);
4200                           }
4201                         }
4202                         new_piece[0] = NULLCHAR;
4203                         sscanf(parse, "game %d white [%s black [%s <- %s",
4204                                &gamenum, white_holding, black_holding,
4205                                new_piece);
4206                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4207                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4208                         /* [HGM] copy holdings to board holdings area */
4209                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4210                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4211                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4212 #if ZIPPY
4213                         if (appData.zippyPlay && first.initDone) {
4214                             ZippyHoldings(white_holding, black_holding,
4215                                           new_piece);
4216                         }
4217 #endif /*ZIPPY*/
4218                         if (tinyLayout || smallLayout) {
4219                             char wh[16], bh[16];
4220                             PackHolding(wh, white_holding);
4221                             PackHolding(bh, black_holding);
4222                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4223                                     gameInfo.white, gameInfo.black);
4224                         } else {
4225                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4226                                     gameInfo.white, white_holding, _("vs."),
4227                                     gameInfo.black, black_holding);
4228                         }
4229                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4230                         DrawPosition(FALSE, boards[currentMove]);
4231                         DisplayTitle(str);
4232                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4233                         sscanf(parse, "game %d white [%s black [%s <- %s",
4234                                &gamenum, white_holding, black_holding,
4235                                new_piece);
4236                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4237                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4238                         /* [HGM] copy holdings to partner-board holdings area */
4239                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4240                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4241                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4242                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4243                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4244                       }
4245                     }
4246                     /* Suppress following prompt */
4247                     if (looking_at(buf, &i, "*% ")) {
4248                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4249                         savingComment = FALSE;
4250                         suppressKibitz = 0;
4251                     }
4252                     next_out = i;
4253                 }
4254                 continue;
4255             }
4256
4257             i++;                /* skip unparsed character and loop back */
4258         }
4259
4260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4261 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4262 //          SendToPlayer(&buf[next_out], i - next_out);
4263             started != STARTED_HOLDINGS && leftover_start > next_out) {
4264             SendToPlayer(&buf[next_out], leftover_start - next_out);
4265             next_out = i;
4266         }
4267
4268         leftover_len = buf_len - leftover_start;
4269         /* if buffer ends with something we couldn't parse,
4270            reparse it after appending the next read */
4271
4272     } else if (count == 0) {
4273         RemoveInputSource(isr);
4274         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4275     } else {
4276         DisplayFatalError(_("Error reading from ICS"), error, 1);
4277     }
4278 }
4279
4280
4281 /* Board style 12 looks like this:
4282
4283    <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
4284
4285  * The "<12> " is stripped before it gets to this routine.  The two
4286  * trailing 0's (flip state and clock ticking) are later addition, and
4287  * some chess servers may not have them, or may have only the first.
4288  * Additional trailing fields may be added in the future.
4289  */
4290
4291 #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"
4292
4293 #define RELATION_OBSERVING_PLAYED    0
4294 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4295 #define RELATION_PLAYING_MYMOVE      1
4296 #define RELATION_PLAYING_NOTMYMOVE  -1
4297 #define RELATION_EXAMINING           2
4298 #define RELATION_ISOLATED_BOARD     -3
4299 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4300
4301 void
4302 ParseBoard12 (char *string)
4303 {
4304 #if ZIPPY
4305     int i, takeback;
4306     char *bookHit = NULL; // [HGM] book
4307 #endif
4308     GameMode newGameMode;
4309     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4310     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4311     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4312     char to_play, board_chars[200];
4313     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4314     char black[32], white[32];
4315     Board board;
4316     int prevMove = currentMove;
4317     int ticking = 2;
4318     ChessMove moveType;
4319     int fromX, fromY, toX, toY;
4320     char promoChar;
4321     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4322     Boolean weird = FALSE, reqFlag = FALSE;
4323
4324     fromX = fromY = toX = toY = -1;
4325
4326     newGame = FALSE;
4327
4328     if (appData.debugMode)
4329       fprintf(debugFP, "Parsing board: %s\n", string);
4330
4331     move_str[0] = NULLCHAR;
4332     elapsed_time[0] = NULLCHAR;
4333     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4334         int  i = 0, j;
4335         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4336             if(string[i] == ' ') { ranks++; files = 0; }
4337             else files++;
4338             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4339             i++;
4340         }
4341         for(j = 0; j <i; j++) board_chars[j] = string[j];
4342         board_chars[i] = '\0';
4343         string += i + 1;
4344     }
4345     n = sscanf(string, PATTERN, &to_play, &double_push,
4346                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4347                &gamenum, white, black, &relation, &basetime, &increment,
4348                &white_stren, &black_stren, &white_time, &black_time,
4349                &moveNum, str, elapsed_time, move_str, &ics_flip,
4350                &ticking);
4351
4352     if (n < 21) {
4353         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4354         DisplayError(str, 0);
4355         return;
4356     }
4357
4358     /* Convert the move number to internal form */
4359     moveNum = (moveNum - 1) * 2;
4360     if (to_play == 'B') moveNum++;
4361     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4362       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4363                         0, 1);
4364       return;
4365     }
4366
4367     switch (relation) {
4368       case RELATION_OBSERVING_PLAYED:
4369       case RELATION_OBSERVING_STATIC:
4370         if (gamenum == -1) {
4371             /* Old ICC buglet */
4372             relation = RELATION_OBSERVING_STATIC;
4373         }
4374         newGameMode = IcsObserving;
4375         break;
4376       case RELATION_PLAYING_MYMOVE:
4377       case RELATION_PLAYING_NOTMYMOVE:
4378         newGameMode =
4379           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4380             IcsPlayingWhite : IcsPlayingBlack;
4381         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4382         break;
4383       case RELATION_EXAMINING:
4384         newGameMode = IcsExamining;
4385         break;
4386       case RELATION_ISOLATED_BOARD:
4387       default:
4388         /* Just display this board.  If user was doing something else,
4389            we will forget about it until the next board comes. */
4390         newGameMode = IcsIdle;
4391         break;
4392       case RELATION_STARTING_POSITION:
4393         newGameMode = gameMode;
4394         break;
4395     }
4396
4397     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4398         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4399          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4400       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4401       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4402       static int lastBgGame = -1;
4403       char *toSqr;
4404       for (k = 0; k < ranks; k++) {
4405         for (j = 0; j < files; j++)
4406           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4407         if(gameInfo.holdingsWidth > 1) {
4408              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4409              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4410         }
4411       }
4412       CopyBoard(partnerBoard, board);
4413       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4414         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4415         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4416       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4417       if(toSqr = strchr(str, '-')) {
4418         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4419         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4420       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4421       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4422       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4423       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4424       if(twoBoards) {
4425           DisplayWhiteClock(white_time*fac, to_play == 'W');
4426           DisplayBlackClock(black_time*fac, to_play != 'W');
4427           activePartner = to_play;
4428           if(gamenum != lastBgGame) {
4429               char buf[MSG_SIZ];
4430               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4431               DisplayTitle(buf);
4432           }
4433           lastBgGame = gamenum;
4434           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4435                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4436       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4437                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4438       if(!twoBoards) DisplayMessage(partnerStatus, "");
4439         partnerBoardValid = TRUE;
4440       return;
4441     }
4442
4443     if(appData.dualBoard && appData.bgObserve) {
4444         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4445             SendToICS(ics_prefix), SendToICS("pobserve\n");
4446         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4447             char buf[MSG_SIZ];
4448             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4449             SendToICS(buf);
4450         }
4451     }
4452
4453     /* Modify behavior for initial board display on move listing
4454        of wild games.
4455        */
4456     switch (ics_getting_history) {
4457       case H_FALSE:
4458       case H_REQUESTED:
4459         break;
4460       case H_GOT_REQ_HEADER:
4461       case H_GOT_UNREQ_HEADER:
4462         /* This is the initial position of the current game */
4463         gamenum = ics_gamenum;
4464         moveNum = 0;            /* old ICS bug workaround */
4465         if (to_play == 'B') {
4466           startedFromSetupPosition = TRUE;
4467           blackPlaysFirst = TRUE;
4468           moveNum = 1;
4469           if (forwardMostMove == 0) forwardMostMove = 1;
4470           if (backwardMostMove == 0) backwardMostMove = 1;
4471           if (currentMove == 0) currentMove = 1;
4472         }
4473         newGameMode = gameMode;
4474         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4475         break;
4476       case H_GOT_UNWANTED_HEADER:
4477         /* This is an initial board that we don't want */
4478         return;
4479       case H_GETTING_MOVES:
4480         /* Should not happen */
4481         DisplayError(_("Error gathering move list: extra board"), 0);
4482         ics_getting_history = H_FALSE;
4483         return;
4484     }
4485
4486    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4487                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4488                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4489      /* [HGM] We seem to have switched variant unexpectedly
4490       * Try to guess new variant from board size
4491       */
4492           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4493           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4494           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4495           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4496           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4497           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4498           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4499           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4500           /* Get a move list just to see the header, which
4501              will tell us whether this is really bug or zh */
4502           if (ics_getting_history == H_FALSE) {
4503             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4504             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505             SendToICS(str);
4506           }
4507     }
4508
4509     /* Take action if this is the first board of a new game, or of a
4510        different game than is currently being displayed.  */
4511     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4512         relation == RELATION_ISOLATED_BOARD) {
4513
4514         /* Forget the old game and get the history (if any) of the new one */
4515         if (gameMode != BeginningOfGame) {
4516           Reset(TRUE, TRUE);
4517         }
4518         newGame = TRUE;
4519         if (appData.autoRaiseBoard) BoardToTop();
4520         prevMove = -3;
4521         if (gamenum == -1) {
4522             newGameMode = IcsIdle;
4523         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4524                    appData.getMoveList && !reqFlag) {
4525             /* Need to get game history */
4526             ics_getting_history = H_REQUESTED;
4527             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4528             SendToICS(str);
4529         }
4530
4531         /* Initially flip the board to have black on the bottom if playing
4532            black or if the ICS flip flag is set, but let the user change
4533            it with the Flip View button. */
4534         flipView = appData.autoFlipView ?
4535           (newGameMode == IcsPlayingBlack) || ics_flip :
4536           appData.flipView;
4537
4538         /* Done with values from previous mode; copy in new ones */
4539         gameMode = newGameMode;
4540         ModeHighlight();
4541         ics_gamenum = gamenum;
4542         if (gamenum == gs_gamenum) {
4543             int klen = strlen(gs_kind);
4544             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4545             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4546             gameInfo.event = StrSave(str);
4547         } else {
4548             gameInfo.event = StrSave("ICS game");
4549         }
4550         gameInfo.site = StrSave(appData.icsHost);
4551         gameInfo.date = PGNDate();
4552         gameInfo.round = StrSave("-");
4553         gameInfo.white = StrSave(white);
4554         gameInfo.black = StrSave(black);
4555         timeControl = basetime * 60 * 1000;
4556         timeControl_2 = 0;
4557         timeIncrement = increment * 1000;
4558         movesPerSession = 0;
4559         gameInfo.timeControl = TimeControlTagValue();
4560         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4561   if (appData.debugMode) {
4562     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4563     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4564     setbuf(debugFP, NULL);
4565   }
4566
4567         gameInfo.outOfBook = NULL;
4568
4569         /* Do we have the ratings? */
4570         if (strcmp(player1Name, white) == 0 &&
4571             strcmp(player2Name, black) == 0) {
4572             if (appData.debugMode)
4573               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4574                       player1Rating, player2Rating);
4575             gameInfo.whiteRating = player1Rating;
4576             gameInfo.blackRating = player2Rating;
4577         } else if (strcmp(player2Name, white) == 0 &&
4578                    strcmp(player1Name, black) == 0) {
4579             if (appData.debugMode)
4580               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4581                       player2Rating, player1Rating);
4582             gameInfo.whiteRating = player2Rating;
4583             gameInfo.blackRating = player1Rating;
4584         }
4585         player1Name[0] = player2Name[0] = NULLCHAR;
4586
4587         /* Silence shouts if requested */
4588         if (appData.quietPlay &&
4589             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4590             SendToICS(ics_prefix);
4591             SendToICS("set shout 0\n");
4592         }
4593     }
4594
4595     /* Deal with midgame name changes */
4596     if (!newGame) {
4597         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4598             if (gameInfo.white) free(gameInfo.white);
4599             gameInfo.white = StrSave(white);
4600         }
4601         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4602             if (gameInfo.black) free(gameInfo.black);
4603             gameInfo.black = StrSave(black);
4604         }
4605     }
4606
4607     /* Throw away game result if anything actually changes in examine mode */
4608     if (gameMode == IcsExamining && !newGame) {
4609         gameInfo.result = GameUnfinished;
4610         if (gameInfo.resultDetails != NULL) {
4611             free(gameInfo.resultDetails);
4612             gameInfo.resultDetails = NULL;
4613         }
4614     }
4615
4616     /* In pausing && IcsExamining mode, we ignore boards coming
4617        in if they are in a different variation than we are. */
4618     if (pauseExamInvalid) return;
4619     if (pausing && gameMode == IcsExamining) {
4620         if (moveNum <= pauseExamForwardMostMove) {
4621             pauseExamInvalid = TRUE;
4622             forwardMostMove = pauseExamForwardMostMove;
4623             return;
4624         }
4625     }
4626
4627   if (appData.debugMode) {
4628     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4629   }
4630     /* Parse the board */
4631     for (k = 0; k < ranks; k++) {
4632       for (j = 0; j < files; j++)
4633         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4634       if(gameInfo.holdingsWidth > 1) {
4635            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4636            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4637       }
4638     }
4639     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4640       board[5][BOARD_RGHT+1] = WhiteAngel;
4641       board[6][BOARD_RGHT+1] = WhiteMarshall;
4642       board[1][0] = BlackMarshall;
4643       board[2][0] = BlackAngel;
4644       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4645     }
4646     CopyBoard(boards[moveNum], board);
4647     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4648     if (moveNum == 0) {
4649         startedFromSetupPosition =
4650           !CompareBoards(board, initialPosition);
4651         if(startedFromSetupPosition)
4652             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4653     }
4654
4655     /* [HGM] Set castling rights. Take the outermost Rooks,
4656        to make it also work for FRC opening positions. Note that board12
4657        is really defective for later FRC positions, as it has no way to
4658        indicate which Rook can castle if they are on the same side of King.
4659        For the initial position we grant rights to the outermost Rooks,
4660        and remember thos rights, and we then copy them on positions
4661        later in an FRC game. This means WB might not recognize castlings with
4662        Rooks that have moved back to their original position as illegal,
4663        but in ICS mode that is not its job anyway.
4664     */
4665     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4666     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4667
4668         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4669             if(board[0][i] == WhiteRook) j = i;
4670         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4671         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4672             if(board[0][i] == WhiteRook) j = i;
4673         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4674         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4676         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4679         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4680
4681         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4682         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4683         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4684             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4685         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4686             if(board[BOARD_HEIGHT-1][k] == bKing)
4687                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4688         if(gameInfo.variant == VariantTwoKings) {
4689             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4690             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4691             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4692         }
4693     } else { int r;
4694         r = boards[moveNum][CASTLING][0] = initialRights[0];
4695         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4696         r = boards[moveNum][CASTLING][1] = initialRights[1];
4697         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4698         r = boards[moveNum][CASTLING][3] = initialRights[3];
4699         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4700         r = boards[moveNum][CASTLING][4] = initialRights[4];
4701         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4702         /* wildcastle kludge: always assume King has rights */
4703         r = boards[moveNum][CASTLING][2] = initialRights[2];
4704         r = boards[moveNum][CASTLING][5] = initialRights[5];
4705     }
4706     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4707     boards[moveNum][EP_STATUS] = EP_NONE;
4708     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4709     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4710     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4711
4712
4713     if (ics_getting_history == H_GOT_REQ_HEADER ||
4714         ics_getting_history == H_GOT_UNREQ_HEADER) {
4715         /* This was an initial position from a move list, not
4716            the current position */
4717         return;
4718     }
4719
4720     /* Update currentMove and known move number limits */
4721     newMove = newGame || moveNum > forwardMostMove;
4722
4723     if (newGame) {
4724         forwardMostMove = backwardMostMove = currentMove = moveNum;
4725         if (gameMode == IcsExamining && moveNum == 0) {
4726           /* Workaround for ICS limitation: we are not told the wild
4727              type when starting to examine a game.  But if we ask for
4728              the move list, the move list header will tell us */
4729             ics_getting_history = H_REQUESTED;
4730             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4731             SendToICS(str);
4732         }
4733     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4734                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4735 #if ZIPPY
4736         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4737         /* [HGM] applied this also to an engine that is silently watching        */
4738         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4739             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4740             gameInfo.variant == currentlyInitializedVariant) {
4741           takeback = forwardMostMove - moveNum;
4742           for (i = 0; i < takeback; i++) {
4743             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4744             SendToProgram("undo\n", &first);
4745           }
4746         }
4747 #endif
4748
4749         forwardMostMove = moveNum;
4750         if (!pausing || currentMove > forwardMostMove)
4751           currentMove = forwardMostMove;
4752     } else {
4753         /* New part of history that is not contiguous with old part */
4754         if (pausing && gameMode == IcsExamining) {
4755             pauseExamInvalid = TRUE;
4756             forwardMostMove = pauseExamForwardMostMove;
4757             return;
4758         }
4759         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4760 #if ZIPPY
4761             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4762                 // [HGM] when we will receive the move list we now request, it will be
4763                 // fed to the engine from the first move on. So if the engine is not
4764                 // in the initial position now, bring it there.
4765                 InitChessProgram(&first, 0);
4766             }
4767 #endif
4768             ics_getting_history = H_REQUESTED;
4769             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4770             SendToICS(str);
4771         }
4772         forwardMostMove = backwardMostMove = currentMove = moveNum;
4773     }
4774
4775     /* Update the clocks */
4776     if (strchr(elapsed_time, '.')) {
4777       /* Time is in ms */
4778       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4779       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4780     } else {
4781       /* Time is in seconds */
4782       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4783       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4784     }
4785
4786
4787 #if ZIPPY
4788     if (appData.zippyPlay && newGame &&
4789         gameMode != IcsObserving && gameMode != IcsIdle &&
4790         gameMode != IcsExamining)
4791       ZippyFirstBoard(moveNum, basetime, increment);
4792 #endif
4793
4794     /* Put the move on the move list, first converting
4795        to canonical algebraic form. */
4796     if (moveNum > 0) {
4797   if (appData.debugMode) {
4798     int f = forwardMostMove;
4799     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4800             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4801             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4802     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4803     fprintf(debugFP, "moveNum = %d\n", moveNum);
4804     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4805     setbuf(debugFP, NULL);
4806   }
4807         if (moveNum <= backwardMostMove) {
4808             /* We don't know what the board looked like before
4809                this move.  Punt. */
4810           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4811             strcat(parseList[moveNum - 1], " ");
4812             strcat(parseList[moveNum - 1], elapsed_time);
4813             moveList[moveNum - 1][0] = NULLCHAR;
4814         } else if (strcmp(move_str, "none") == 0) {
4815             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4816             /* Again, we don't know what the board looked like;
4817                this is really the start of the game. */
4818             parseList[moveNum - 1][0] = NULLCHAR;
4819             moveList[moveNum - 1][0] = NULLCHAR;
4820             backwardMostMove = moveNum;
4821             startedFromSetupPosition = TRUE;
4822             fromX = fromY = toX = toY = -1;
4823         } else {
4824           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4825           //                 So we parse the long-algebraic move string in stead of the SAN move
4826           int valid; char buf[MSG_SIZ], *prom;
4827
4828           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4829                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4830           // str looks something like "Q/a1-a2"; kill the slash
4831           if(str[1] == '/')
4832             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4833           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4834           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4835                 strcat(buf, prom); // long move lacks promo specification!
4836           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4837                 if(appData.debugMode)
4838                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4839                 safeStrCpy(move_str, buf, MSG_SIZ);
4840           }
4841           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4842                                 &fromX, &fromY, &toX, &toY, &promoChar)
4843                || ParseOneMove(buf, moveNum - 1, &moveType,
4844                                 &fromX, &fromY, &toX, &toY, &promoChar);
4845           // end of long SAN patch
4846           if (valid) {
4847             (void) CoordsToAlgebraic(boards[moveNum - 1],
4848                                      PosFlags(moveNum - 1),
4849                                      fromY, fromX, toY, toX, promoChar,
4850                                      parseList[moveNum-1]);
4851             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4852               case MT_NONE:
4853               case MT_STALEMATE:
4854               default:
4855                 break;
4856               case MT_CHECK:
4857                 if(!IS_SHOGI(gameInfo.variant))
4858                     strcat(parseList[moveNum - 1], "+");
4859                 break;
4860               case MT_CHECKMATE:
4861               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4862                 strcat(parseList[moveNum - 1], "#");
4863                 break;
4864             }
4865             strcat(parseList[moveNum - 1], " ");
4866             strcat(parseList[moveNum - 1], elapsed_time);
4867             /* currentMoveString is set as a side-effect of ParseOneMove */
4868             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4869             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4870             strcat(moveList[moveNum - 1], "\n");
4871
4872             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4873                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4874               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4875                 ChessSquare old, new = boards[moveNum][k][j];
4876                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4877                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4878                   if(old == new) continue;
4879                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4880                   else if(new == WhiteWazir || new == BlackWazir) {
4881                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4882                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4883                       else boards[moveNum][k][j] = old; // preserve type of Gold
4884                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4885                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4886               }
4887           } else {
4888             /* Move from ICS was illegal!?  Punt. */
4889             if (appData.debugMode) {
4890               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4891               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4892             }
4893             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4894             strcat(parseList[moveNum - 1], " ");
4895             strcat(parseList[moveNum - 1], elapsed_time);
4896             moveList[moveNum - 1][0] = NULLCHAR;
4897             fromX = fromY = toX = toY = -1;
4898           }
4899         }
4900   if (appData.debugMode) {
4901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4902     setbuf(debugFP, NULL);
4903   }
4904
4905 #if ZIPPY
4906         /* Send move to chess program (BEFORE animating it). */
4907         if (appData.zippyPlay && !newGame && newMove &&
4908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4909
4910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4913                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4914                             move_str);
4915                     DisplayError(str, 0);
4916                 } else {
4917                     if (first.sendTime) {
4918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4919                     }
4920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4921                     if (firstMove && !bookHit) {
4922                         firstMove = FALSE;
4923                         if (first.useColors) {
4924                           SendToProgram(gameMode == IcsPlayingWhite ?
4925                                         "white\ngo\n" :
4926                                         "black\ngo\n", &first);
4927                         } else {
4928                           SendToProgram("go\n", &first);
4929                         }
4930                         first.maybeThinking = TRUE;
4931                     }
4932                 }
4933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4934               if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4936                 DisplayError(str, 0);
4937               } else {
4938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4939                 SendMoveToProgram(moveNum - 1, &first);
4940               }
4941             }
4942         }
4943 #endif
4944     }
4945
4946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4947         /* If move comes from a remote source, animate it.  If it
4948            isn't remote, it will have already been animated. */
4949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4951         }
4952         if (!pausing && appData.highlightLastMove) {
4953             SetHighlights(fromX, fromY, toX, toY);
4954         }
4955     }
4956
4957     /* Start the clocks */
4958     whiteFlag = blackFlag = FALSE;
4959     appData.clockMode = !(basetime == 0 && increment == 0);
4960     if (ticking == 0) {
4961       ics_clock_paused = TRUE;
4962       StopClocks();
4963     } else if (ticking == 1) {
4964       ics_clock_paused = FALSE;
4965     }
4966     if (gameMode == IcsIdle ||
4967         relation == RELATION_OBSERVING_STATIC ||
4968         relation == RELATION_EXAMINING ||
4969         ics_clock_paused)
4970       DisplayBothClocks();
4971     else
4972       StartClocks();
4973
4974     /* Display opponents and material strengths */
4975     if (gameInfo.variant != VariantBughouse &&
4976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4977         if (tinyLayout || smallLayout) {
4978             if(gameInfo.variant == VariantNormal)
4979               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4981                     basetime, increment);
4982             else
4983               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4985                     basetime, increment, (int) gameInfo.variant);
4986         } else {
4987             if(gameInfo.variant == VariantNormal)
4988               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4989                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4990                     basetime, increment);
4991             else
4992               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4993                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4994                     basetime, increment, VariantName(gameInfo.variant));
4995         }
4996         DisplayTitle(str);
4997   if (appData.debugMode) {
4998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4999   }
5000     }
5001
5002
5003     /* Display the board */
5004     if (!pausing && !appData.noGUI) {
5005
5006       if (appData.premove)
5007           if (!gotPremove ||
5008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5010               ClearPremoveHighlights();
5011
5012       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5013         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5014       DrawPosition(j, boards[currentMove]);
5015
5016       DisplayMove(moveNum - 1);
5017       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5018             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5019               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5020         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5021       }
5022     }
5023
5024     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5025 #if ZIPPY
5026     if(bookHit) { // [HGM] book: simulate book reply
5027         static char bookMove[MSG_SIZ]; // a bit generous?
5028
5029         programStats.nodes = programStats.depth = programStats.time =
5030         programStats.score = programStats.got_only_move = 0;
5031         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5032
5033         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5034         strcat(bookMove, bookHit);
5035         HandleMachineMove(bookMove, &first);
5036     }
5037 #endif
5038 }
5039
5040 void
5041 GetMoveListEvent ()
5042 {
5043     char buf[MSG_SIZ];
5044     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5045         ics_getting_history = H_REQUESTED;
5046         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5047         SendToICS(buf);
5048     }
5049 }
5050
5051 void
5052 SendToBoth (char *msg)
5053 {   // to make it easy to keep two engines in step in dual analysis
5054     SendToProgram(msg, &first);
5055     if(second.analyzing) SendToProgram(msg, &second);
5056 }
5057
5058 void
5059 AnalysisPeriodicEvent (int force)
5060 {
5061     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5062          && !force) || !appData.periodicUpdates)
5063       return;
5064
5065     /* Send . command to Crafty to collect stats */
5066     SendToBoth(".\n");
5067
5068     /* Don't send another until we get a response (this makes
5069        us stop sending to old Crafty's which don't understand
5070        the "." command (sending illegal cmds resets node count & time,
5071        which looks bad)) */
5072     programStats.ok_to_send = 0;
5073 }
5074
5075 void
5076 ics_update_width (int new_width)
5077 {
5078         ics_printf("set width %d\n", new_width);
5079 }
5080
5081 void
5082 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5083 {
5084     char buf[MSG_SIZ];
5085
5086     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5087         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5088             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5089             SendToProgram(buf, cps);
5090             return;
5091         }
5092         // null move in variant where engine does not understand it (for analysis purposes)
5093         SendBoard(cps, moveNum + 1); // send position after move in stead.
5094         return;
5095     }
5096     if (cps->useUsermove) {
5097       SendToProgram("usermove ", cps);
5098     }
5099     if (cps->useSAN) {
5100       char *space;
5101       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5102         int len = space - parseList[moveNum];
5103         memcpy(buf, parseList[moveNum], len);
5104         buf[len++] = '\n';
5105         buf[len] = NULLCHAR;
5106       } else {
5107         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5108       }
5109       SendToProgram(buf, cps);
5110     } else {
5111       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5112         AlphaRank(moveList[moveNum], 4);
5113         SendToProgram(moveList[moveNum], cps);
5114         AlphaRank(moveList[moveNum], 4); // and back
5115       } else
5116       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5117        * the engine. It would be nice to have a better way to identify castle
5118        * moves here. */
5119       if(appData.fischerCastling && cps->useOOCastle) {
5120         int fromX = moveList[moveNum][0] - AAA;
5121         int fromY = moveList[moveNum][1] - ONE;
5122         int toX = moveList[moveNum][2] - AAA;
5123         int toY = moveList[moveNum][3] - ONE;
5124         if((boards[moveNum][fromY][fromX] == WhiteKing
5125             && boards[moveNum][toY][toX] == WhiteRook)
5126            || (boards[moveNum][fromY][fromX] == BlackKing
5127                && boards[moveNum][toY][toX] == BlackRook)) {
5128           if(toX > fromX) SendToProgram("O-O\n", cps);
5129           else SendToProgram("O-O-O\n", cps);
5130         }
5131         else SendToProgram(moveList[moveNum], cps);
5132       } else
5133       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5134           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5137                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5138           SendToProgram(buf, cps);
5139       } else
5140       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5141         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5142           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5143           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5144                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5145         } else
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5148         SendToProgram(buf, cps);
5149       }
5150       else SendToProgram(moveList[moveNum], cps);
5151       /* End of additions by Tord */
5152     }
5153
5154     /* [HGM] setting up the opening has brought engine in force mode! */
5155     /*       Send 'go' if we are in a mode where machine should play. */
5156     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5157         (gameMode == TwoMachinesPlay   ||
5158 #if ZIPPY
5159          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5160 #endif
5161          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5162         SendToProgram("go\n", cps);
5163   if (appData.debugMode) {
5164     fprintf(debugFP, "(extra)\n");
5165   }
5166     }
5167     setboardSpoiledMachineBlack = 0;
5168 }
5169
5170 void
5171 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5172 {
5173     char user_move[MSG_SIZ];
5174     char suffix[4];
5175
5176     if(gameInfo.variant == VariantSChess && promoChar) {
5177         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5178         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5179     } else suffix[0] = NULLCHAR;
5180
5181     switch (moveType) {
5182       default:
5183         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5184                 (int)moveType, fromX, fromY, toX, toY);
5185         DisplayError(user_move + strlen("say "), 0);
5186         break;
5187       case WhiteKingSideCastle:
5188       case BlackKingSideCastle:
5189       case WhiteQueenSideCastleWild:
5190       case BlackQueenSideCastleWild:
5191       /* PUSH Fabien */
5192       case WhiteHSideCastleFR:
5193       case BlackHSideCastleFR:
5194       /* POP Fabien */
5195         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5196         break;
5197       case WhiteQueenSideCastle:
5198       case BlackQueenSideCastle:
5199       case WhiteKingSideCastleWild:
5200       case BlackKingSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteASideCastleFR:
5203       case BlackASideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5206         break;
5207       case WhiteNonPromotion:
5208       case BlackNonPromotion:
5209         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5210         break;
5211       case WhitePromotion:
5212       case BlackPromotion:
5213         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5214            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5215           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5216                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5217                 PieceToChar(WhiteFerz));
5218         else if(gameInfo.variant == VariantGreat)
5219           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5220                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5221                 PieceToChar(WhiteMan));
5222         else
5223           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5224                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5225                 promoChar);
5226         break;
5227       case WhiteDrop:
5228       case BlackDrop:
5229       drop:
5230         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5231                  ToUpper(PieceToChar((ChessSquare) fromX)),
5232                  AAA + toX, ONE + toY);
5233         break;
5234       case IllegalMove:  /* could be a variant we don't quite understand */
5235         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5236       case NormalMove:
5237       case WhiteCapturesEnPassant:
5238       case BlackCapturesEnPassant:
5239         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5241         break;
5242     }
5243     SendToICS(user_move);
5244     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5245         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5246 }
5247
5248 void
5249 UploadGameEvent ()
5250 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5251     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5252     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5253     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5254       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5255       return;
5256     }
5257     if(gameMode != IcsExamining) { // is this ever not the case?
5258         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5259
5260         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5261           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5262         } else { // on FICS we must first go to general examine mode
5263           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5264         }
5265         if(gameInfo.variant != VariantNormal) {
5266             // try figure out wild number, as xboard names are not always valid on ICS
5267             for(i=1; i<=36; i++) {
5268               snprintf(buf, MSG_SIZ, "wild/%d", i);
5269                 if(StringToVariant(buf) == gameInfo.variant) break;
5270             }
5271             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5272             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5273             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5274         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5275         SendToICS(ics_prefix);
5276         SendToICS(buf);
5277         if(startedFromSetupPosition || backwardMostMove != 0) {
5278           fen = PositionToFEN(backwardMostMove, NULL, 1);
5279           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5280             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5281             SendToICS(buf);
5282           } else { // FICS: everything has to set by separate bsetup commands
5283             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5284             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5285             SendToICS(buf);
5286             if(!WhiteOnMove(backwardMostMove)) {
5287                 SendToICS("bsetup tomove black\n");
5288             }
5289             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5290             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5291             SendToICS(buf);
5292             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5293             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5294             SendToICS(buf);
5295             i = boards[backwardMostMove][EP_STATUS];
5296             if(i >= 0) { // set e.p.
5297               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5298                 SendToICS(buf);
5299             }
5300             bsetup++;
5301           }
5302         }
5303       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5304             SendToICS("bsetup done\n"); // switch to normal examining.
5305     }
5306     for(i = backwardMostMove; i<last; i++) {
5307         char buf[20];
5308         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5309         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5310             int len = strlen(moveList[i]);
5311             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5312             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5313         }
5314         SendToICS(buf);
5315     }
5316     SendToICS(ics_prefix);
5317     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5318 }
5319
5320 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5321
5322 void
5323 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5324 {
5325     if (rf == DROP_RANK) {
5326       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5327       sprintf(move, "%c@%c%c\n",
5328                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5329     } else {
5330         if (promoChar == 'x' || promoChar == NULLCHAR) {
5331           sprintf(move, "%c%c%c%c\n",
5332                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5333           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5334         } else {
5335             sprintf(move, "%c%c%c%c%c\n",
5336                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5337         }
5338     }
5339 }
5340
5341 void
5342 ProcessICSInitScript (FILE *f)
5343 {
5344     char buf[MSG_SIZ];
5345
5346     while (fgets(buf, MSG_SIZ, f)) {
5347         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5348     }
5349
5350     fclose(f);
5351 }
5352
5353
5354 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5355 int dragging;
5356 static ClickType lastClickType;
5357
5358 int
5359 Partner (ChessSquare *p)
5360 { // change piece into promotion partner if one shogi-promotes to the other
5361   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5362   ChessSquare partner;
5363   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5364   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5365   *p = partner;
5366   return 1;
5367 }
5368
5369 void
5370 Sweep (int step)
5371 {
5372     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5373     static int toggleFlag;
5374     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5375     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5376     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5377     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5378     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5379     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5380     do {
5381         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5382         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5383         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5384         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5385         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5386         if(!step) step = -1;
5387     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5388             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5389             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5390             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5391     if(toX >= 0) {
5392         int victim = boards[currentMove][toY][toX];
5393         boards[currentMove][toY][toX] = promoSweep;
5394         DrawPosition(FALSE, boards[currentMove]);
5395         boards[currentMove][toY][toX] = victim;
5396     } else
5397     ChangeDragPiece(promoSweep);
5398 }
5399
5400 int
5401 PromoScroll (int x, int y)
5402 {
5403   int step = 0;
5404
5405   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5406   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5407   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5408   if(!step) return FALSE;
5409   lastX = x; lastY = y;
5410   if((promoSweep < BlackPawn) == flipView) step = -step;
5411   if(step > 0) selectFlag = 1;
5412   if(!selectFlag) Sweep(step);
5413   return FALSE;
5414 }
5415
5416 void
5417 NextPiece (int step)
5418 {
5419     ChessSquare piece = boards[currentMove][toY][toX];
5420     do {
5421         pieceSweep -= step;
5422         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5423         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5424         if(!step) step = -1;
5425     } while(PieceToChar(pieceSweep) == '.');
5426     boards[currentMove][toY][toX] = pieceSweep;
5427     DrawPosition(FALSE, boards[currentMove]);
5428     boards[currentMove][toY][toX] = piece;
5429 }
5430 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5431 void
5432 AlphaRank (char *move, int n)
5433 {
5434 //    char *p = move, c; int x, y;
5435
5436     if (appData.debugMode) {
5437         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5438     }
5439
5440     if(move[1]=='*' &&
5441        move[2]>='0' && move[2]<='9' &&
5442        move[3]>='a' && move[3]<='x'    ) {
5443         move[1] = '@';
5444         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5445         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5446     } else
5447     if(move[0]>='0' && move[0]<='9' &&
5448        move[1]>='a' && move[1]<='x' &&
5449        move[2]>='0' && move[2]<='9' &&
5450        move[3]>='a' && move[3]<='x'    ) {
5451         /* input move, Shogi -> normal */
5452         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5453         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5454         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5455         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5456     } else
5457     if(move[1]=='@' &&
5458        move[3]>='0' && move[3]<='9' &&
5459        move[2]>='a' && move[2]<='x'    ) {
5460         move[1] = '*';
5461         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5462         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5463     } else
5464     if(
5465        move[0]>='a' && move[0]<='x' &&
5466        move[3]>='0' && move[3]<='9' &&
5467        move[2]>='a' && move[2]<='x'    ) {
5468          /* output move, normal -> Shogi */
5469         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5470         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5471         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5472         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5473         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5474     }
5475     if (appData.debugMode) {
5476         fprintf(debugFP, "   out = '%s'\n", move);
5477     }
5478 }
5479
5480 char yy_textstr[8000];
5481
5482 /* Parser for moves from gnuchess, ICS, or user typein box */
5483 Boolean
5484 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5485 {
5486     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5487
5488     switch (*moveType) {
5489       case WhitePromotion:
5490       case BlackPromotion:
5491       case WhiteNonPromotion:
5492       case BlackNonPromotion:
5493       case NormalMove:
5494       case FirstLeg:
5495       case WhiteCapturesEnPassant:
5496       case BlackCapturesEnPassant:
5497       case WhiteKingSideCastle:
5498       case WhiteQueenSideCastle:
5499       case BlackKingSideCastle:
5500       case BlackQueenSideCastle:
5501       case WhiteKingSideCastleWild:
5502       case WhiteQueenSideCastleWild:
5503       case BlackKingSideCastleWild:
5504       case BlackQueenSideCastleWild:
5505       /* Code added by Tord: */
5506       case WhiteHSideCastleFR:
5507       case WhiteASideCastleFR:
5508       case BlackHSideCastleFR:
5509       case BlackASideCastleFR:
5510       /* End of code added by Tord */
5511       case IllegalMove:         /* bug or odd chess variant */
5512         *fromX = currentMoveString[0] - AAA;
5513         *fromY = currentMoveString[1] - ONE;
5514         *toX = currentMoveString[2] - AAA;
5515         *toY = currentMoveString[3] - ONE;
5516         *promoChar = currentMoveString[4];
5517         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5518             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5519     if (appData.debugMode) {
5520         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5521     }
5522             *fromX = *fromY = *toX = *toY = 0;
5523             return FALSE;
5524         }
5525         if (appData.testLegality) {
5526           return (*moveType != IllegalMove);
5527         } else {
5528           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5529                          // [HGM] lion: if this is a double move we are less critical
5530                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5531         }
5532
5533       case WhiteDrop:
5534       case BlackDrop:
5535         *fromX = *moveType == WhiteDrop ?
5536           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5537           (int) CharToPiece(ToLower(currentMoveString[0]));
5538         *fromY = DROP_RANK;
5539         *toX = currentMoveString[2] - AAA;
5540         *toY = currentMoveString[3] - ONE;
5541         *promoChar = NULLCHAR;
5542         return TRUE;
5543
5544       case AmbiguousMove:
5545       case ImpossibleMove:
5546       case EndOfFile:
5547       case ElapsedTime:
5548       case Comment:
5549       case PGNTag:
5550       case NAG:
5551       case WhiteWins:
5552       case BlackWins:
5553       case GameIsDrawn:
5554       default:
5555     if (appData.debugMode) {
5556         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5557     }
5558         /* bug? */
5559         *fromX = *fromY = *toX = *toY = 0;
5560         *promoChar = NULLCHAR;
5561         return FALSE;
5562     }
5563 }
5564
5565 Boolean pushed = FALSE;
5566 char *lastParseAttempt;
5567
5568 void
5569 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5570 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5571   int fromX, fromY, toX, toY; char promoChar;
5572   ChessMove moveType;
5573   Boolean valid;
5574   int nr = 0;
5575
5576   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5577   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5578     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5579     pushed = TRUE;
5580   }
5581   endPV = forwardMostMove;
5582   do {
5583     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5584     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5585     lastParseAttempt = pv;
5586     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5587     if(!valid && nr == 0 &&
5588        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5589         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5590         // Hande case where played move is different from leading PV move
5591         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5592         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5593         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5594         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5595           endPV += 2; // if position different, keep this
5596           moveList[endPV-1][0] = fromX + AAA;
5597           moveList[endPV-1][1] = fromY + ONE;
5598           moveList[endPV-1][2] = toX + AAA;
5599           moveList[endPV-1][3] = toY + ONE;
5600           parseList[endPV-1][0] = NULLCHAR;
5601           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5602         }
5603       }
5604     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5605     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5606     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5607     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5608         valid++; // allow comments in PV
5609         continue;
5610     }
5611     nr++;
5612     if(endPV+1 > framePtr) break; // no space, truncate
5613     if(!valid) break;
5614     endPV++;
5615     CopyBoard(boards[endPV], boards[endPV-1]);
5616     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5617     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5618     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5619     CoordsToAlgebraic(boards[endPV - 1],
5620                              PosFlags(endPV - 1),
5621                              fromY, fromX, toY, toX, promoChar,
5622                              parseList[endPV - 1]);
5623   } while(valid);
5624   if(atEnd == 2) return; // used hidden, for PV conversion
5625   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5626   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5627   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5628                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5629   DrawPosition(TRUE, boards[currentMove]);
5630 }
5631
5632 int
5633 MultiPV (ChessProgramState *cps)
5634 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5635         int i;
5636         for(i=0; i<cps->nrOptions; i++)
5637             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5638                 return i;
5639         return -1;
5640 }
5641
5642 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5643
5644 Boolean
5645 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5646 {
5647         int startPV, multi, lineStart, origIndex = index;
5648         char *p, buf2[MSG_SIZ];
5649         ChessProgramState *cps = (pane ? &second : &first);
5650
5651         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5652         lastX = x; lastY = y;
5653         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5654         lineStart = startPV = index;
5655         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5656         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5657         index = startPV;
5658         do{ while(buf[index] && buf[index] != '\n') index++;
5659         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5660         buf[index] = 0;
5661         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5662                 int n = cps->option[multi].value;
5663                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5664                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5665                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5666                 cps->option[multi].value = n;
5667                 *start = *end = 0;
5668                 return FALSE;
5669         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5670                 ExcludeClick(origIndex - lineStart);
5671                 return FALSE;
5672         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5673                 Collapse(origIndex - lineStart);
5674                 return FALSE;
5675         }
5676         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5677         *start = startPV; *end = index-1;
5678         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5679         return TRUE;
5680 }
5681
5682 char *
5683 PvToSAN (char *pv)
5684 {
5685         static char buf[10*MSG_SIZ];
5686         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5687         *buf = NULLCHAR;
5688         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5689         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5690         for(i = forwardMostMove; i<endPV; i++){
5691             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5692             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5693             k += strlen(buf+k);
5694         }
5695         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5696         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5697         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5698         endPV = savedEnd;
5699         return buf;
5700 }
5701
5702 Boolean
5703 LoadPV (int x, int y)
5704 { // called on right mouse click to load PV
5705   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5706   lastX = x; lastY = y;
5707   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5708   extendGame = FALSE;
5709   return TRUE;
5710 }
5711
5712 void
5713 UnLoadPV ()
5714 {
5715   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5716   if(endPV < 0) return;
5717   if(appData.autoCopyPV) CopyFENToClipboard();
5718   endPV = -1;
5719   if(extendGame && currentMove > forwardMostMove) {
5720         Boolean saveAnimate = appData.animate;
5721         if(pushed) {
5722             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5723                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5724             } else storedGames--; // abandon shelved tail of original game
5725         }
5726         pushed = FALSE;
5727         forwardMostMove = currentMove;
5728         currentMove = oldFMM;
5729         appData.animate = FALSE;
5730         ToNrEvent(forwardMostMove);
5731         appData.animate = saveAnimate;
5732   }
5733   currentMove = forwardMostMove;
5734   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5735   ClearPremoveHighlights();
5736   DrawPosition(TRUE, boards[currentMove]);
5737 }
5738
5739 void
5740 MovePV (int x, int y, int h)
5741 { // step through PV based on mouse coordinates (called on mouse move)
5742   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5743
5744   // we must somehow check if right button is still down (might be released off board!)
5745   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5746   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5747   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5748   if(!step) return;
5749   lastX = x; lastY = y;
5750
5751   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5752   if(endPV < 0) return;
5753   if(y < margin) step = 1; else
5754   if(y > h - margin) step = -1;
5755   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5756   currentMove += step;
5757   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5758   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5759                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5760   DrawPosition(FALSE, boards[currentMove]);
5761 }
5762
5763
5764 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5765 // All positions will have equal probability, but the current method will not provide a unique
5766 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5767 #define DARK 1
5768 #define LITE 2
5769 #define ANY 3
5770
5771 int squaresLeft[4];
5772 int piecesLeft[(int)BlackPawn];
5773 int seed, nrOfShuffles;
5774
5775 void
5776 GetPositionNumber ()
5777 {       // sets global variable seed
5778         int i;
5779
5780         seed = appData.defaultFrcPosition;
5781         if(seed < 0) { // randomize based on time for negative FRC position numbers
5782                 for(i=0; i<50; i++) seed += random();
5783                 seed = random() ^ random() >> 8 ^ random() << 8;
5784                 if(seed<0) seed = -seed;
5785         }
5786 }
5787
5788 int
5789 put (Board board, int pieceType, int rank, int n, int shade)
5790 // put the piece on the (n-1)-th empty squares of the given shade
5791 {
5792         int i;
5793
5794         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5795                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5796                         board[rank][i] = (ChessSquare) pieceType;
5797                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5798                         squaresLeft[ANY]--;
5799                         piecesLeft[pieceType]--;
5800                         return i;
5801                 }
5802         }
5803         return -1;
5804 }
5805
5806
5807 void
5808 AddOnePiece (Board board, int pieceType, int rank, int shade)
5809 // calculate where the next piece goes, (any empty square), and put it there
5810 {
5811         int i;
5812
5813         i = seed % squaresLeft[shade];
5814         nrOfShuffles *= squaresLeft[shade];
5815         seed /= squaresLeft[shade];
5816         put(board, pieceType, rank, i, shade);
5817 }
5818
5819 void
5820 AddTwoPieces (Board board, int pieceType, int rank)
5821 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5822 {
5823         int i, n=squaresLeft[ANY], j=n-1, k;
5824
5825         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5826         i = seed % k;  // pick one
5827         nrOfShuffles *= k;
5828         seed /= k;
5829         while(i >= j) i -= j--;
5830         j = n - 1 - j; i += j;
5831         put(board, pieceType, rank, j, ANY);
5832         put(board, pieceType, rank, i, ANY);
5833 }
5834
5835 void
5836 SetUpShuffle (Board board, int number)
5837 {
5838         int i, p, first=1;
5839
5840         GetPositionNumber(); nrOfShuffles = 1;
5841
5842         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5843         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5844         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5845
5846         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5847
5848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5849             p = (int) board[0][i];
5850             if(p < (int) BlackPawn) piecesLeft[p] ++;
5851             board[0][i] = EmptySquare;
5852         }
5853
5854         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5855             // shuffles restricted to allow normal castling put KRR first
5856             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5857                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5858             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5859                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5860             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5861                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5862             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5863                 put(board, WhiteRook, 0, 0, ANY);
5864             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5865         }
5866
5867         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5868             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5869             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5870                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5871                 while(piecesLeft[p] >= 2) {
5872                     AddOnePiece(board, p, 0, LITE);
5873                     AddOnePiece(board, p, 0, DARK);
5874                 }
5875                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5876             }
5877
5878         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5879             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5880             // but we leave King and Rooks for last, to possibly obey FRC restriction
5881             if(p == (int)WhiteRook) continue;
5882             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5883             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5884         }
5885
5886         // now everything is placed, except perhaps King (Unicorn) and Rooks
5887
5888         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5889             // Last King gets castling rights
5890             while(piecesLeft[(int)WhiteUnicorn]) {
5891                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5892                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5893             }
5894
5895             while(piecesLeft[(int)WhiteKing]) {
5896                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900
5901         } else {
5902             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5903             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5904         }
5905
5906         // Only Rooks can be left; simply place them all
5907         while(piecesLeft[(int)WhiteRook]) {
5908                 i = put(board, WhiteRook, 0, 0, ANY);
5909                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5910                         if(first) {
5911                                 first=0;
5912                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5913                         }
5914                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5915                 }
5916         }
5917         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5918             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5919         }
5920
5921         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5922 }
5923
5924 int
5925 SetCharTable (char *table, const char * map)
5926 /* [HGM] moved here from winboard.c because of its general usefulness */
5927 /*       Basically a safe strcpy that uses the last character as King */
5928 {
5929     int result = FALSE; int NrPieces;
5930
5931     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5932                     && NrPieces >= 12 && !(NrPieces&1)) {
5933         int i; /* [HGM] Accept even length from 12 to 34 */
5934
5935         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5936         for( i=0; i<NrPieces/2-1; i++ ) {
5937             table[i] = map[i];
5938             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5939         }
5940         table[(int) WhiteKing]  = map[NrPieces/2-1];
5941         table[(int) BlackKing]  = map[NrPieces-1];
5942
5943         result = TRUE;
5944     }
5945
5946     return result;
5947 }
5948
5949 void
5950 Prelude (Board board)
5951 {       // [HGM] superchess: random selection of exo-pieces
5952         int i, j, k; ChessSquare p;
5953         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5954
5955         GetPositionNumber(); // use FRC position number
5956
5957         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5958             SetCharTable(pieceToChar, appData.pieceToCharTable);
5959             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5960                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5961         }
5962
5963         j = seed%4;                 seed /= 4;
5964         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5965         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5966         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5967         j = seed%3 + (seed%3 >= j); seed /= 3;
5968         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5969         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5970         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5971         j = seed%3;                 seed /= 3;
5972         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5973         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5974         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5975         j = seed%2 + (seed%2 >= j); seed /= 2;
5976         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5977         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5978         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5979         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5980         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5981         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5982         put(board, exoPieces[0],    0, 0, ANY);
5983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5984 }
5985
5986 void
5987 InitPosition (int redraw)
5988 {
5989     ChessSquare (* pieces)[BOARD_FILES];
5990     int i, j, pawnRow=1, pieceRows=1, overrule,
5991     oldx = gameInfo.boardWidth,
5992     oldy = gameInfo.boardHeight,
5993     oldh = gameInfo.holdingsWidth;
5994     static int oldv;
5995
5996     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5997
5998     /* [AS] Initialize pv info list [HGM] and game status */
5999     {
6000         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6001             pvInfoList[i].depth = 0;
6002             boards[i][EP_STATUS] = EP_NONE;
6003             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6004         }
6005
6006         initialRulePlies = 0; /* 50-move counter start */
6007
6008         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6009         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6010     }
6011
6012
6013     /* [HGM] logic here is completely changed. In stead of full positions */
6014     /* the initialized data only consist of the two backranks. The switch */
6015     /* selects which one we will use, which is than copied to the Board   */
6016     /* initialPosition, which for the rest is initialized by Pawns and    */
6017     /* empty squares. This initial position is then copied to boards[0],  */
6018     /* possibly after shuffling, so that it remains available.            */
6019
6020     gameInfo.holdingsWidth = 0; /* default board sizes */
6021     gameInfo.boardWidth    = 8;
6022     gameInfo.boardHeight   = 8;
6023     gameInfo.holdingsSize  = 0;
6024     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6025     for(i=0; i<BOARD_FILES-2; i++)
6026       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6027     initialPosition[EP_STATUS] = EP_NONE;
6028     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6029     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6030          SetCharTable(pieceNickName, appData.pieceNickNames);
6031     else SetCharTable(pieceNickName, "............");
6032     pieces = FIDEArray;
6033
6034     switch (gameInfo.variant) {
6035     case VariantFischeRandom:
6036       shuffleOpenings = TRUE;
6037       appData.fischerCastling = TRUE;
6038     default:
6039       break;
6040     case VariantShatranj:
6041       pieces = ShatranjArray;
6042       nrCastlingRights = 0;
6043       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6044       break;
6045     case VariantMakruk:
6046       pieces = makrukArray;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6049       break;
6050     case VariantASEAN:
6051       pieces = aseanArray;
6052       nrCastlingRights = 0;
6053       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6054       break;
6055     case VariantTwoKings:
6056       pieces = twoKingsArray;
6057       break;
6058     case VariantGrand:
6059       pieces = GrandArray;
6060       nrCastlingRights = 0;
6061       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6062       gameInfo.boardWidth = 10;
6063       gameInfo.boardHeight = 10;
6064       gameInfo.holdingsSize = 7;
6065       break;
6066     case VariantCapaRandom:
6067       shuffleOpenings = TRUE;
6068       appData.fischerCastling = TRUE;
6069     case VariantCapablanca:
6070       pieces = CapablancaArray;
6071       gameInfo.boardWidth = 10;
6072       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6073       break;
6074     case VariantGothic:
6075       pieces = GothicArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6078       break;
6079     case VariantSChess:
6080       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6081       gameInfo.holdingsSize = 7;
6082       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6083       break;
6084     case VariantJanus:
6085       pieces = JanusArray;
6086       gameInfo.boardWidth = 10;
6087       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6088       nrCastlingRights = 6;
6089         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6090         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6091         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6092         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6093         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6094         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6095       break;
6096     case VariantFalcon:
6097       pieces = FalconArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6100       break;
6101     case VariantXiangqi:
6102       pieces = XiangqiArray;
6103       gameInfo.boardWidth  = 9;
6104       gameInfo.boardHeight = 10;
6105       nrCastlingRights = 0;
6106       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6107       break;
6108     case VariantShogi:
6109       pieces = ShogiArray;
6110       gameInfo.boardWidth  = 9;
6111       gameInfo.boardHeight = 9;
6112       gameInfo.holdingsSize = 7;
6113       nrCastlingRights = 0;
6114       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6115       break;
6116     case VariantChu:
6117       pieces = ChuArray; pieceRows = 3;
6118       gameInfo.boardWidth  = 12;
6119       gameInfo.boardHeight = 12;
6120       nrCastlingRights = 0;
6121       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6122                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6123       break;
6124     case VariantCourier:
6125       pieces = CourierArray;
6126       gameInfo.boardWidth  = 12;
6127       nrCastlingRights = 0;
6128       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6129       break;
6130     case VariantKnightmate:
6131       pieces = KnightmateArray;
6132       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6133       break;
6134     case VariantSpartan:
6135       pieces = SpartanArray;
6136       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6137       break;
6138     case VariantLion:
6139       pieces = lionArray;
6140       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6141       break;
6142     case VariantChuChess:
6143       pieces = ChuChessArray;
6144       gameInfo.boardWidth = 10;
6145       gameInfo.boardHeight = 10;
6146       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6147       break;
6148     case VariantFairy:
6149       pieces = fairyArray;
6150       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6151       break;
6152     case VariantGreat:
6153       pieces = GreatArray;
6154       gameInfo.boardWidth = 10;
6155       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6156       gameInfo.holdingsSize = 8;
6157       break;
6158     case VariantSuper:
6159       pieces = FIDEArray;
6160       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6161       gameInfo.holdingsSize = 8;
6162       startedFromSetupPosition = TRUE;
6163       break;
6164     case VariantCrazyhouse:
6165     case VariantBughouse:
6166       pieces = FIDEArray;
6167       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6168       gameInfo.holdingsSize = 5;
6169       break;
6170     case VariantWildCastle:
6171       pieces = FIDEArray;
6172       /* !!?shuffle with kings guaranteed to be on d or e file */
6173       shuffleOpenings = 1;
6174       break;
6175     case VariantNoCastle:
6176       pieces = FIDEArray;
6177       nrCastlingRights = 0;
6178       /* !!?unconstrained back-rank shuffle */
6179       shuffleOpenings = 1;
6180       break;
6181     }
6182
6183     overrule = 0;
6184     if(appData.NrFiles >= 0) {
6185         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6186         gameInfo.boardWidth = appData.NrFiles;
6187     }
6188     if(appData.NrRanks >= 0) {
6189         gameInfo.boardHeight = appData.NrRanks;
6190     }
6191     if(appData.holdingsSize >= 0) {
6192         i = appData.holdingsSize;
6193         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6194         gameInfo.holdingsSize = i;
6195     }
6196     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6197     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6198         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6199
6200     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6201     if(pawnRow < 1) pawnRow = 1;
6202     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6203        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6204     if(gameInfo.variant == VariantChu) pawnRow = 3;
6205
6206     /* User pieceToChar list overrules defaults */
6207     if(appData.pieceToCharTable != NULL)
6208         SetCharTable(pieceToChar, appData.pieceToCharTable);
6209
6210     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6211
6212         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6213             s = (ChessSquare) 0; /* account holding counts in guard band */
6214         for( i=0; i<BOARD_HEIGHT; i++ )
6215             initialPosition[i][j] = s;
6216
6217         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6218         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6219         initialPosition[pawnRow][j] = WhitePawn;
6220         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6221         if(gameInfo.variant == VariantXiangqi) {
6222             if(j&1) {
6223                 initialPosition[pawnRow][j] =
6224                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6225                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6226                    initialPosition[2][j] = WhiteCannon;
6227                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6228                 }
6229             }
6230         }
6231         if(gameInfo.variant == VariantChu) {
6232              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6233                initialPosition[pawnRow+1][j] = WhiteCobra,
6234                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6235              for(i=1; i<pieceRows; i++) {
6236                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6237                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6238              }
6239         }
6240         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6241             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6242                initialPosition[0][j] = WhiteRook;
6243                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6244             }
6245         }
6246         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6247     }
6248     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6249     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6250
6251             j=BOARD_LEFT+1;
6252             initialPosition[1][j] = WhiteBishop;
6253             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6254             j=BOARD_RGHT-2;
6255             initialPosition[1][j] = WhiteRook;
6256             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6257     }
6258
6259     if( nrCastlingRights == -1) {
6260         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6261         /*       This sets default castling rights from none to normal corners   */
6262         /* Variants with other castling rights must set them themselves above    */
6263         nrCastlingRights = 6;
6264
6265         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6266         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6267         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6268         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6269         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6270         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6271      }
6272
6273      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6274      if(gameInfo.variant == VariantGreat) { // promotion commoners
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6276         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6278         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6279      }
6280      if( gameInfo.variant == VariantSChess ) {
6281       initialPosition[1][0] = BlackMarshall;
6282       initialPosition[2][0] = BlackAngel;
6283       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6284       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6285       initialPosition[1][1] = initialPosition[2][1] =
6286       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6287      }
6288   if (appData.debugMode) {
6289     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6290   }
6291     if(shuffleOpenings) {
6292         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6293         startedFromSetupPosition = TRUE;
6294     }
6295     if(startedFromPositionFile) {
6296       /* [HGM] loadPos: use PositionFile for every new game */
6297       CopyBoard(initialPosition, filePosition);
6298       for(i=0; i<nrCastlingRights; i++)
6299           initialRights[i] = filePosition[CASTLING][i];
6300       startedFromSetupPosition = TRUE;
6301     }
6302
6303     CopyBoard(boards[0], initialPosition);
6304
6305     if(oldx != gameInfo.boardWidth ||
6306        oldy != gameInfo.boardHeight ||
6307        oldv != gameInfo.variant ||
6308        oldh != gameInfo.holdingsWidth
6309                                          )
6310             InitDrawingSizes(-2 ,0);
6311
6312     oldv = gameInfo.variant;
6313     if (redraw)
6314       DrawPosition(TRUE, boards[currentMove]);
6315 }
6316
6317 void
6318 SendBoard (ChessProgramState *cps, int moveNum)
6319 {
6320     char message[MSG_SIZ];
6321
6322     if (cps->useSetboard) {
6323       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6324       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6325       SendToProgram(message, cps);
6326       free(fen);
6327
6328     } else {
6329       ChessSquare *bp;
6330       int i, j, left=0, right=BOARD_WIDTH;
6331       /* Kludge to set black to move, avoiding the troublesome and now
6332        * deprecated "black" command.
6333        */
6334       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6335         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6336
6337       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6338
6339       SendToProgram("edit\n", cps);
6340       SendToProgram("#\n", cps);
6341       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6342         bp = &boards[moveNum][i][left];
6343         for (j = left; j < right; j++, bp++) {
6344           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6345           if ((int) *bp < (int) BlackPawn) {
6346             if(j == BOARD_RGHT+1)
6347                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6348             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6349             if(message[0] == '+' || message[0] == '~') {
6350               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6351                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6352                         AAA + j, ONE + i);
6353             }
6354             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6355                 message[1] = BOARD_RGHT   - 1 - j + '1';
6356                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6357             }
6358             SendToProgram(message, cps);
6359           }
6360         }
6361       }
6362
6363       SendToProgram("c\n", cps);
6364       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6365         bp = &boards[moveNum][i][left];
6366         for (j = left; j < right; j++, bp++) {
6367           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6368           if (((int) *bp != (int) EmptySquare)
6369               && ((int) *bp >= (int) BlackPawn)) {
6370             if(j == BOARD_LEFT-2)
6371                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6372             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6373                     AAA + j, ONE + i);
6374             if(message[0] == '+' || message[0] == '~') {
6375               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6376                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6377                         AAA + j, ONE + i);
6378             }
6379             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6380                 message[1] = BOARD_RGHT   - 1 - j + '1';
6381                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6382             }
6383             SendToProgram(message, cps);
6384           }
6385         }
6386       }
6387
6388       SendToProgram(".\n", cps);
6389     }
6390     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6391 }
6392
6393 char exclusionHeader[MSG_SIZ];
6394 int exCnt, excludePtr;
6395 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6396 static Exclusion excluTab[200];
6397 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6398
6399 static void
6400 WriteMap (int s)
6401 {
6402     int j;
6403     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6404     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6405 }
6406
6407 static void
6408 ClearMap ()
6409 {
6410     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6411     excludePtr = 24; exCnt = 0;
6412     WriteMap(0);
6413 }
6414
6415 static void
6416 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6417 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6418     char buf[2*MOVE_LEN], *p;
6419     Exclusion *e = excluTab;
6420     int i;
6421     for(i=0; i<exCnt; i++)
6422         if(e[i].ff == fromX && e[i].fr == fromY &&
6423            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6424     if(i == exCnt) { // was not in exclude list; add it
6425         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6426         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6427             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6428             return; // abort
6429         }
6430         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6431         excludePtr++; e[i].mark = excludePtr++;
6432         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6433         exCnt++;
6434     }
6435     exclusionHeader[e[i].mark] = state;
6436 }
6437
6438 static int
6439 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6440 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6441     char buf[MSG_SIZ];
6442     int j, k;
6443     ChessMove moveType;
6444     if((signed char)promoChar == -1) { // kludge to indicate best move
6445         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6446             return 1; // if unparsable, abort
6447     }
6448     // update exclusion map (resolving toggle by consulting existing state)
6449     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6450     j = k%8; k >>= 3;
6451     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6452     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6453          excludeMap[k] |=   1<<j;
6454     else excludeMap[k] &= ~(1<<j);
6455     // update header
6456     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6457     // inform engine
6458     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6459     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6460     SendToBoth(buf);
6461     return (state == '+');
6462 }
6463
6464 static void
6465 ExcludeClick (int index)
6466 {
6467     int i, j;
6468     Exclusion *e = excluTab;
6469     if(index < 25) { // none, best or tail clicked
6470         if(index < 13) { // none: include all
6471             WriteMap(0); // clear map
6472             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6473             SendToBoth("include all\n"); // and inform engine
6474         } else if(index > 18) { // tail
6475             if(exclusionHeader[19] == '-') { // tail was excluded
6476                 SendToBoth("include all\n");
6477                 WriteMap(0); // clear map completely
6478                 // now re-exclude selected moves
6479                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6480                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6481             } else { // tail was included or in mixed state
6482                 SendToBoth("exclude all\n");
6483                 WriteMap(0xFF); // fill map completely
6484                 // now re-include selected moves
6485                 j = 0; // count them
6486                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6487                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6488                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6489             }
6490         } else { // best
6491             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6492         }
6493     } else {
6494         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6495             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6496             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6497             break;
6498         }
6499     }
6500 }
6501
6502 ChessSquare
6503 DefaultPromoChoice (int white)
6504 {
6505     ChessSquare result;
6506     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6507        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6508         result = WhiteFerz; // no choice
6509     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6510         result= WhiteKing; // in Suicide Q is the last thing we want
6511     else if(gameInfo.variant == VariantSpartan)
6512         result = white ? WhiteQueen : WhiteAngel;
6513     else result = WhiteQueen;
6514     if(!white) result = WHITE_TO_BLACK result;
6515     return result;
6516 }
6517
6518 static int autoQueen; // [HGM] oneclick
6519
6520 int
6521 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6522 {
6523     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6524     /* [HGM] add Shogi promotions */
6525     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6526     ChessSquare piece, partner;
6527     ChessMove moveType;
6528     Boolean premove;
6529
6530     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6531     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6532
6533     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6534       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6535         return FALSE;
6536
6537     piece = boards[currentMove][fromY][fromX];
6538     if(gameInfo.variant == VariantChu) {
6539         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6540         promotionZoneSize = BOARD_HEIGHT/3;
6541         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6542     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6543         promotionZoneSize = BOARD_HEIGHT/3;
6544         highestPromotingPiece = (int)WhiteAlfil;
6545     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6546         promotionZoneSize = 3;
6547     }
6548
6549     // Treat Lance as Pawn when it is not representing Amazon or Lance
6550     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6551         if(piece == WhiteLance) piece = WhitePawn; else
6552         if(piece == BlackLance) piece = BlackPawn;
6553     }
6554
6555     // next weed out all moves that do not touch the promotion zone at all
6556     if((int)piece >= BlackPawn) {
6557         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6558              return FALSE;
6559         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6560         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6561     } else {
6562         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6563            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6564         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6565              return FALSE;
6566     }
6567
6568     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6569
6570     // weed out mandatory Shogi promotions
6571     if(gameInfo.variant == VariantShogi) {
6572         if(piece >= BlackPawn) {
6573             if(toY == 0 && piece == BlackPawn ||
6574                toY == 0 && piece == BlackQueen ||
6575                toY <= 1 && piece == BlackKnight) {
6576                 *promoChoice = '+';
6577                 return FALSE;
6578             }
6579         } else {
6580             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6581                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6582                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6583                 *promoChoice = '+';
6584                 return FALSE;
6585             }
6586         }
6587     }
6588
6589     // weed out obviously illegal Pawn moves
6590     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6591         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6592         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6593         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6594         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6595         // note we are not allowed to test for valid (non-)capture, due to premove
6596     }
6597
6598     // we either have a choice what to promote to, or (in Shogi) whether to promote
6599     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6600        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6601         ChessSquare p=BlackFerz;  // no choice
6602         while(p < EmptySquare) {  //but make sure we use piece that exists
6603             *promoChoice = PieceToChar(p++);
6604             if(*promoChoice != '.') break;
6605         }
6606         return FALSE;
6607     }
6608     // no sense asking what we must promote to if it is going to explode...
6609     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6610         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6611         return FALSE;
6612     }
6613     // give caller the default choice even if we will not make it
6614     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6615     partner = piece; // pieces can promote if the pieceToCharTable says so
6616     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6617     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6618     if(        sweepSelect && gameInfo.variant != VariantGreat
6619                            && gameInfo.variant != VariantGrand
6620                            && gameInfo.variant != VariantSuper) return FALSE;
6621     if(autoQueen) return FALSE; // predetermined
6622
6623     // suppress promotion popup on illegal moves that are not premoves
6624     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6625               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6626     if(appData.testLegality && !premove) {
6627         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6628                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6629         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6630         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6631             return FALSE;
6632     }
6633
6634     return TRUE;
6635 }
6636
6637 int
6638 InPalace (int row, int column)
6639 {   /* [HGM] for Xiangqi */
6640     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6641          column < (BOARD_WIDTH + 4)/2 &&
6642          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6643     return FALSE;
6644 }
6645
6646 int
6647 PieceForSquare (int x, int y)
6648 {
6649   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6650      return -1;
6651   else
6652      return boards[currentMove][y][x];
6653 }
6654
6655 int
6656 OKToStartUserMove (int x, int y)
6657 {
6658     ChessSquare from_piece;
6659     int white_piece;
6660
6661     if (matchMode) return FALSE;
6662     if (gameMode == EditPosition) return TRUE;
6663
6664     if (x >= 0 && y >= 0)
6665       from_piece = boards[currentMove][y][x];
6666     else
6667       from_piece = EmptySquare;
6668
6669     if (from_piece == EmptySquare) return FALSE;
6670
6671     white_piece = (int)from_piece >= (int)WhitePawn &&
6672       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6673
6674     switch (gameMode) {
6675       case AnalyzeFile:
6676       case TwoMachinesPlay:
6677       case EndOfGame:
6678         return FALSE;
6679
6680       case IcsObserving:
6681       case IcsIdle:
6682         return FALSE;
6683
6684       case MachinePlaysWhite:
6685       case IcsPlayingBlack:
6686         if (appData.zippyPlay) return FALSE;
6687         if (white_piece) {
6688             DisplayMoveError(_("You are playing Black"));
6689             return FALSE;
6690         }
6691         break;
6692
6693       case MachinePlaysBlack:
6694       case IcsPlayingWhite:
6695         if (appData.zippyPlay) return FALSE;
6696         if (!white_piece) {
6697             DisplayMoveError(_("You are playing White"));
6698             return FALSE;
6699         }
6700         break;
6701
6702       case PlayFromGameFile:
6703             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6704       case EditGame:
6705         if (!white_piece && WhiteOnMove(currentMove)) {
6706             DisplayMoveError(_("It is White's turn"));
6707             return FALSE;
6708         }
6709         if (white_piece && !WhiteOnMove(currentMove)) {
6710             DisplayMoveError(_("It is Black's turn"));
6711             return FALSE;
6712         }
6713         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6714             /* Editing correspondence game history */
6715             /* Could disallow this or prompt for confirmation */
6716             cmailOldMove = -1;
6717         }
6718         break;
6719
6720       case BeginningOfGame:
6721         if (appData.icsActive) return FALSE;
6722         if (!appData.noChessProgram) {
6723             if (!white_piece) {
6724                 DisplayMoveError(_("You are playing White"));
6725                 return FALSE;
6726             }
6727         }
6728         break;
6729
6730       case Training:
6731         if (!white_piece && WhiteOnMove(currentMove)) {
6732             DisplayMoveError(_("It is White's turn"));
6733             return FALSE;
6734         }
6735         if (white_piece && !WhiteOnMove(currentMove)) {
6736             DisplayMoveError(_("It is Black's turn"));
6737             return FALSE;
6738         }
6739         break;
6740
6741       default:
6742       case IcsExamining:
6743         break;
6744     }
6745     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6746         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6747         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6748         && gameMode != AnalyzeFile && gameMode != Training) {
6749         DisplayMoveError(_("Displayed position is not current"));
6750         return FALSE;
6751     }
6752     return TRUE;
6753 }
6754
6755 Boolean
6756 OnlyMove (int *x, int *y, Boolean captures)
6757 {
6758     DisambiguateClosure cl;
6759     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6760     switch(gameMode) {
6761       case MachinePlaysBlack:
6762       case IcsPlayingWhite:
6763       case BeginningOfGame:
6764         if(!WhiteOnMove(currentMove)) return FALSE;
6765         break;
6766       case MachinePlaysWhite:
6767       case IcsPlayingBlack:
6768         if(WhiteOnMove(currentMove)) return FALSE;
6769         break;
6770       case EditGame:
6771         break;
6772       default:
6773         return FALSE;
6774     }
6775     cl.pieceIn = EmptySquare;
6776     cl.rfIn = *y;
6777     cl.ffIn = *x;
6778     cl.rtIn = -1;
6779     cl.ftIn = -1;
6780     cl.promoCharIn = NULLCHAR;
6781     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6782     if( cl.kind == NormalMove ||
6783         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6784         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6785         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6786       fromX = cl.ff;
6787       fromY = cl.rf;
6788       *x = cl.ft;
6789       *y = cl.rt;
6790       return TRUE;
6791     }
6792     if(cl.kind != ImpossibleMove) return FALSE;
6793     cl.pieceIn = EmptySquare;
6794     cl.rfIn = -1;
6795     cl.ffIn = -1;
6796     cl.rtIn = *y;
6797     cl.ftIn = *x;
6798     cl.promoCharIn = NULLCHAR;
6799     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6800     if( cl.kind == NormalMove ||
6801         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6802         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6803         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6804       fromX = cl.ff;
6805       fromY = cl.rf;
6806       *x = cl.ft;
6807       *y = cl.rt;
6808       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6809       return TRUE;
6810     }
6811     return FALSE;
6812 }
6813
6814 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6815 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6816 int lastLoadGameUseList = FALSE;
6817 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6818 ChessMove lastLoadGameStart = EndOfFile;
6819 int doubleClick;
6820 Boolean addToBookFlag;
6821
6822 void
6823 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6824 {
6825     ChessMove moveType;
6826     ChessSquare pup;
6827     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6828
6829     /* Check if the user is playing in turn.  This is complicated because we
6830        let the user "pick up" a piece before it is his turn.  So the piece he
6831        tried to pick up may have been captured by the time he puts it down!
6832        Therefore we use the color the user is supposed to be playing in this
6833        test, not the color of the piece that is currently on the starting
6834        square---except in EditGame mode, where the user is playing both
6835        sides; fortunately there the capture race can't happen.  (It can
6836        now happen in IcsExamining mode, but that's just too bad.  The user
6837        will get a somewhat confusing message in that case.)
6838        */
6839
6840     switch (gameMode) {
6841       case AnalyzeFile:
6842       case TwoMachinesPlay:
6843       case EndOfGame:
6844       case IcsObserving:
6845       case IcsIdle:
6846         /* We switched into a game mode where moves are not accepted,
6847            perhaps while the mouse button was down. */
6848         return;
6849
6850       case MachinePlaysWhite:
6851         /* User is moving for Black */
6852         if (WhiteOnMove(currentMove)) {
6853             DisplayMoveError(_("It is White's turn"));
6854             return;
6855         }
6856         break;
6857
6858       case MachinePlaysBlack:
6859         /* User is moving for White */
6860         if (!WhiteOnMove(currentMove)) {
6861             DisplayMoveError(_("It is Black's turn"));
6862             return;
6863         }
6864         break;
6865
6866       case PlayFromGameFile:
6867             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6868       case EditGame:
6869       case IcsExamining:
6870       case BeginningOfGame:
6871       case AnalyzeMode:
6872       case Training:
6873         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6874         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6875             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6876             /* User is moving for Black */
6877             if (WhiteOnMove(currentMove)) {
6878                 DisplayMoveError(_("It is White's turn"));
6879                 return;
6880             }
6881         } else {
6882             /* User is moving for White */
6883             if (!WhiteOnMove(currentMove)) {
6884                 DisplayMoveError(_("It is Black's turn"));
6885                 return;
6886             }
6887         }
6888         break;
6889
6890       case IcsPlayingBlack:
6891         /* User is moving for Black */
6892         if (WhiteOnMove(currentMove)) {
6893             if (!appData.premove) {
6894                 DisplayMoveError(_("It is White's turn"));
6895             } else if (toX >= 0 && toY >= 0) {
6896                 premoveToX = toX;
6897                 premoveToY = toY;
6898                 premoveFromX = fromX;
6899                 premoveFromY = fromY;
6900                 premovePromoChar = promoChar;
6901                 gotPremove = 1;
6902                 if (appData.debugMode)
6903                     fprintf(debugFP, "Got premove: fromX %d,"
6904                             "fromY %d, toX %d, toY %d\n",
6905                             fromX, fromY, toX, toY);
6906             }
6907             return;
6908         }
6909         break;
6910
6911       case IcsPlayingWhite:
6912         /* User is moving for White */
6913         if (!WhiteOnMove(currentMove)) {
6914             if (!appData.premove) {
6915                 DisplayMoveError(_("It is Black's turn"));
6916             } else if (toX >= 0 && toY >= 0) {
6917                 premoveToX = toX;
6918                 premoveToY = toY;
6919                 premoveFromX = fromX;
6920                 premoveFromY = fromY;
6921                 premovePromoChar = promoChar;
6922                 gotPremove = 1;
6923                 if (appData.debugMode)
6924                     fprintf(debugFP, "Got premove: fromX %d,"
6925                             "fromY %d, toX %d, toY %d\n",
6926                             fromX, fromY, toX, toY);
6927             }
6928             return;
6929         }
6930         break;
6931
6932       default:
6933         break;
6934
6935       case EditPosition:
6936         /* EditPosition, empty square, or different color piece;
6937            click-click move is possible */
6938         if (toX == -2 || toY == -2) {
6939             boards[0][fromY][fromX] = EmptySquare;
6940             DrawPosition(FALSE, boards[currentMove]);
6941             return;
6942         } else if (toX >= 0 && toY >= 0) {
6943             boards[0][toY][toX] = boards[0][fromY][fromX];
6944             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6945                 if(boards[0][fromY][0] != EmptySquare) {
6946                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6947                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6948                 }
6949             } else
6950             if(fromX == BOARD_RGHT+1) {
6951                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6952                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6953                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6954                 }
6955             } else
6956             boards[0][fromY][fromX] = gatingPiece;
6957             DrawPosition(FALSE, boards[currentMove]);
6958             return;
6959         }
6960         return;
6961     }
6962
6963     if(toX < 0 || toY < 0) return;
6964     pup = boards[currentMove][toY][toX];
6965
6966     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6967     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6968          if( pup != EmptySquare ) return;
6969          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6970            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6971                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6972            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6973            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6974            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6975            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6976          fromY = DROP_RANK;
6977     }
6978
6979     /* [HGM] always test for legality, to get promotion info */
6980     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6981                                          fromY, fromX, toY, toX, promoChar);
6982
6983     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6984
6985     /* [HGM] but possibly ignore an IllegalMove result */
6986     if (appData.testLegality) {
6987         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6988             DisplayMoveError(_("Illegal move"));
6989             return;
6990         }
6991     }
6992
6993     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6994         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6995              ClearPremoveHighlights(); // was included
6996         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6997         return;
6998     }
6999
7000     if(addToBookFlag) { // adding moves to book
7001         char buf[MSG_SIZ], move[MSG_SIZ];
7002         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7003         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7004         AddBookMove(buf);
7005         addToBookFlag = FALSE;
7006         ClearHighlights();
7007         return;
7008     }
7009
7010     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7011 }
7012
7013 /* Common tail of UserMoveEvent and DropMenuEvent */
7014 int
7015 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7016 {
7017     char *bookHit = 0;
7018
7019     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7020         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7021         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7022         if(WhiteOnMove(currentMove)) {
7023             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7024         } else {
7025             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7026         }
7027     }
7028
7029     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7030        move type in caller when we know the move is a legal promotion */
7031     if(moveType == NormalMove && promoChar)
7032         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7033
7034     /* [HGM] <popupFix> The following if has been moved here from
7035        UserMoveEvent(). Because it seemed to belong here (why not allow
7036        piece drops in training games?), and because it can only be
7037        performed after it is known to what we promote. */
7038     if (gameMode == Training) {
7039       /* compare the move played on the board to the next move in the
7040        * game. If they match, display the move and the opponent's response.
7041        * If they don't match, display an error message.
7042        */
7043       int saveAnimate;
7044       Board testBoard;
7045       CopyBoard(testBoard, boards[currentMove]);
7046       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7047
7048       if (CompareBoards(testBoard, boards[currentMove+1])) {
7049         ForwardInner(currentMove+1);
7050
7051         /* Autoplay the opponent's response.
7052          * if appData.animate was TRUE when Training mode was entered,
7053          * the response will be animated.
7054          */
7055         saveAnimate = appData.animate;
7056         appData.animate = animateTraining;
7057         ForwardInner(currentMove+1);
7058         appData.animate = saveAnimate;
7059
7060         /* check for the end of the game */
7061         if (currentMove >= forwardMostMove) {
7062           gameMode = PlayFromGameFile;
7063           ModeHighlight();
7064           SetTrainingModeOff();
7065           DisplayInformation(_("End of game"));
7066         }
7067       } else {
7068         DisplayError(_("Incorrect move"), 0);
7069       }
7070       return 1;
7071     }
7072
7073   /* Ok, now we know that the move is good, so we can kill
7074      the previous line in Analysis Mode */
7075   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7076                                 && currentMove < forwardMostMove) {
7077     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7078     else forwardMostMove = currentMove;
7079   }
7080
7081   ClearMap();
7082
7083   /* If we need the chess program but it's dead, restart it */
7084   ResurrectChessProgram();
7085
7086   /* A user move restarts a paused game*/
7087   if (pausing)
7088     PauseEvent();
7089
7090   thinkOutput[0] = NULLCHAR;
7091
7092   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7093
7094   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7095     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7096     return 1;
7097   }
7098
7099   if (gameMode == BeginningOfGame) {
7100     if (appData.noChessProgram) {
7101       gameMode = EditGame;
7102       SetGameInfo();
7103     } else {
7104       char buf[MSG_SIZ];
7105       gameMode = MachinePlaysBlack;
7106       StartClocks();
7107       SetGameInfo();
7108       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7109       DisplayTitle(buf);
7110       if (first.sendName) {
7111         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7112         SendToProgram(buf, &first);
7113       }
7114       StartClocks();
7115     }
7116     ModeHighlight();
7117   }
7118
7119   /* Relay move to ICS or chess engine */
7120   if (appData.icsActive) {
7121     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7122         gameMode == IcsExamining) {
7123       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7124         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7125         SendToICS("draw ");
7126         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7127       }
7128       // also send plain move, in case ICS does not understand atomic claims
7129       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7130       ics_user_moved = 1;
7131     }
7132   } else {
7133     if (first.sendTime && (gameMode == BeginningOfGame ||
7134                            gameMode == MachinePlaysWhite ||
7135                            gameMode == MachinePlaysBlack)) {
7136       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7137     }
7138     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7139          // [HGM] book: if program might be playing, let it use book
7140         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7141         first.maybeThinking = TRUE;
7142     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7143         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7144         SendBoard(&first, currentMove+1);
7145         if(second.analyzing) {
7146             if(!second.useSetboard) SendToProgram("undo\n", &second);
7147             SendBoard(&second, currentMove+1);
7148         }
7149     } else {
7150         SendMoveToProgram(forwardMostMove-1, &first);
7151         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7152     }
7153     if (currentMove == cmailOldMove + 1) {
7154       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7155     }
7156   }
7157
7158   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7159
7160   switch (gameMode) {
7161   case EditGame:
7162     if(appData.testLegality)
7163     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7164     case MT_NONE:
7165     case MT_CHECK:
7166       break;
7167     case MT_CHECKMATE:
7168     case MT_STAINMATE:
7169       if (WhiteOnMove(currentMove)) {
7170         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7171       } else {
7172         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7173       }
7174       break;
7175     case MT_STALEMATE:
7176       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7177       break;
7178     }
7179     break;
7180
7181   case MachinePlaysBlack:
7182   case MachinePlaysWhite:
7183     /* disable certain menu options while machine is thinking */
7184     SetMachineThinkingEnables();
7185     break;
7186
7187   default:
7188     break;
7189   }
7190
7191   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7192   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7193
7194   if(bookHit) { // [HGM] book: simulate book reply
7195         static char bookMove[MSG_SIZ]; // a bit generous?
7196
7197         programStats.nodes = programStats.depth = programStats.time =
7198         programStats.score = programStats.got_only_move = 0;
7199         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7200
7201         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7202         strcat(bookMove, bookHit);
7203         HandleMachineMove(bookMove, &first);
7204   }
7205   return 1;
7206 }
7207
7208 void
7209 MarkByFEN(char *fen)
7210 {
7211         int r, f;
7212         if(!appData.markers || !appData.highlightDragging) return;
7213         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7214         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7215         while(*fen) {
7216             int s = 0;
7217             marker[r][f] = 0;
7218             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7219             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7220             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7221             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7222             if(*fen == 'T') marker[r][f++] = 0; else
7223             if(*fen == 'Y') marker[r][f++] = 1; else
7224             if(*fen == 'G') marker[r][f++] = 3; else
7225             if(*fen == 'B') marker[r][f++] = 4; else
7226             if(*fen == 'C') marker[r][f++] = 5; else
7227             if(*fen == 'M') marker[r][f++] = 6; else
7228             if(*fen == 'W') marker[r][f++] = 7; else
7229             if(*fen == 'D') marker[r][f++] = 8; else
7230             if(*fen == 'R') marker[r][f++] = 2; else {
7231                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7232               f += s; fen -= s>0;
7233             }
7234             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7235             if(r < 0) break;
7236             fen++;
7237         }
7238         DrawPosition(TRUE, NULL);
7239 }
7240
7241 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7242
7243 void
7244 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7245 {
7246     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7247     Markers *m = (Markers *) closure;
7248     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7249         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7250                          || kind == WhiteCapturesEnPassant
7251                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7252     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7253 }
7254
7255 static int hoverSavedValid;
7256
7257 void
7258 MarkTargetSquares (int clear)
7259 {
7260   int x, y, sum=0;
7261   if(clear) { // no reason to ever suppress clearing
7262     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7263     hoverSavedValid = 0;
7264     if(!sum) return; // nothing was cleared,no redraw needed
7265   } else {
7266     int capt = 0;
7267     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7268        !appData.testLegality || gameMode == EditPosition) return;
7269     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7270     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7271       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7272       if(capt)
7273       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7274     }
7275   }
7276   DrawPosition(FALSE, NULL);
7277 }
7278
7279 int
7280 Explode (Board board, int fromX, int fromY, int toX, int toY)
7281 {
7282     if(gameInfo.variant == VariantAtomic &&
7283        (board[toY][toX] != EmptySquare ||                     // capture?
7284         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7285                          board[fromY][fromX] == BlackPawn   )
7286       )) {
7287         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7288         return TRUE;
7289     }
7290     return FALSE;
7291 }
7292
7293 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7294
7295 int
7296 CanPromote (ChessSquare piece, int y)
7297 {
7298         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7299         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7300         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7301         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7302            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7303            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7304          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7305         return (piece == BlackPawn && y <= zone ||
7306                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7307                 piece == BlackLance && y == 1 ||
7308                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7309 }
7310
7311 void
7312 HoverEvent (int xPix, int yPix, int x, int y)
7313 {
7314         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7315         int r, f;
7316         if(!first.highlight) return;
7317         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7318         if(x == oldX && y == oldY) return; // only do something if we enter new square
7319         oldFromX = fromX; oldFromY = fromY;
7320         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7321           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7322             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7323           hoverSavedValid = 1;
7324         } else if(oldX != x || oldY != y) {
7325           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7326           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7327           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7328             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7329           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7330             char buf[MSG_SIZ];
7331             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7332             SendToProgram(buf, &first);
7333           }
7334           oldX = x; oldY = y;
7335 //        SetHighlights(fromX, fromY, x, y);
7336         }
7337 }
7338
7339 void ReportClick(char *action, int x, int y)
7340 {
7341         char buf[MSG_SIZ]; // Inform engine of what user does
7342         int r, f;
7343         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7344           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7345         if(!first.highlight || gameMode == EditPosition) return;
7346         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7347         SendToProgram(buf, &first);
7348 }
7349
7350 void
7351 LeftClick (ClickType clickType, int xPix, int yPix)
7352 {
7353     int x, y;
7354     Boolean saveAnimate;
7355     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7356     char promoChoice = NULLCHAR;
7357     ChessSquare piece;
7358     static TimeMark lastClickTime, prevClickTime;
7359
7360     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7361
7362     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7363
7364     if (clickType == Press) ErrorPopDown();
7365     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7366
7367     x = EventToSquare(xPix, BOARD_WIDTH);
7368     y = EventToSquare(yPix, BOARD_HEIGHT);
7369     if (!flipView && y >= 0) {
7370         y = BOARD_HEIGHT - 1 - y;
7371     }
7372     if (flipView && x >= 0) {
7373         x = BOARD_WIDTH - 1 - x;
7374     }
7375
7376     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7377         defaultPromoChoice = promoSweep;
7378         promoSweep = EmptySquare;   // terminate sweep
7379         promoDefaultAltered = TRUE;
7380         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7381     }
7382
7383     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7384         if(clickType == Release) return; // ignore upclick of click-click destination
7385         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7386         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7387         if(gameInfo.holdingsWidth &&
7388                 (WhiteOnMove(currentMove)
7389                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7390                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7391             // click in right holdings, for determining promotion piece
7392             ChessSquare p = boards[currentMove][y][x];
7393             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7394             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7395             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7396                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7397                 fromX = fromY = -1;
7398                 return;
7399             }
7400         }
7401         DrawPosition(FALSE, boards[currentMove]);
7402         return;
7403     }
7404
7405     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7406     if(clickType == Press
7407             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7408               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7409               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7410         return;
7411
7412     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7413         // could be static click on premove from-square: abort premove
7414         gotPremove = 0;
7415         ClearPremoveHighlights();
7416     }
7417
7418     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7419         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7420
7421     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7422         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7423                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7424         defaultPromoChoice = DefaultPromoChoice(side);
7425     }
7426
7427     autoQueen = appData.alwaysPromoteToQueen;
7428
7429     if (fromX == -1) {
7430       int originalY = y;
7431       gatingPiece = EmptySquare;
7432       if (clickType != Press) {
7433         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7434             DragPieceEnd(xPix, yPix); dragging = 0;
7435             DrawPosition(FALSE, NULL);
7436         }
7437         return;
7438       }
7439       doubleClick = FALSE;
7440       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7441         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7442       }
7443       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7444       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7445          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7446          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7447             /* First square */
7448             if (OKToStartUserMove(fromX, fromY)) {
7449                 second = 0;
7450                 ReportClick("lift", x, y);
7451                 MarkTargetSquares(0);
7452                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7453                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7454                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7455                     promoSweep = defaultPromoChoice;
7456                     selectFlag = 0; lastX = xPix; lastY = yPix;
7457                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7458                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7459                 }
7460                 if (appData.highlightDragging) {
7461                     SetHighlights(fromX, fromY, -1, -1);
7462                 } else {
7463                     ClearHighlights();
7464                 }
7465             } else fromX = fromY = -1;
7466             return;
7467         }
7468     }
7469
7470     /* fromX != -1 */
7471     if (clickType == Press && gameMode != EditPosition) {
7472         ChessSquare fromP;
7473         ChessSquare toP;
7474         int frc;
7475
7476         // ignore off-board to clicks
7477         if(y < 0 || x < 0) return;
7478
7479         /* Check if clicking again on the same color piece */
7480         fromP = boards[currentMove][fromY][fromX];
7481         toP = boards[currentMove][y][x];
7482         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7483         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7484            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7485              WhitePawn <= toP && toP <= WhiteKing &&
7486              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7487              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7488             (BlackPawn <= fromP && fromP <= BlackKing &&
7489              BlackPawn <= toP && toP <= BlackKing &&
7490              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7491              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7492             /* Clicked again on same color piece -- changed his mind */
7493             second = (x == fromX && y == fromY);
7494             killX = killY = -1;
7495             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7496                 second = FALSE; // first double-click rather than scond click
7497                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7498             }
7499             promoDefaultAltered = FALSE;
7500             MarkTargetSquares(1);
7501            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7502             if (appData.highlightDragging) {
7503                 SetHighlights(x, y, -1, -1);
7504             } else {
7505                 ClearHighlights();
7506             }
7507             if (OKToStartUserMove(x, y)) {
7508                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7509                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7510                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7511                  gatingPiece = boards[currentMove][fromY][fromX];
7512                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7513                 fromX = x;
7514                 fromY = y; dragging = 1;
7515                 ReportClick("lift", x, y);
7516                 MarkTargetSquares(0);
7517                 DragPieceBegin(xPix, yPix, FALSE);
7518                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7519                     promoSweep = defaultPromoChoice;
7520                     selectFlag = 0; lastX = xPix; lastY = yPix;
7521                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7522                 }
7523             }
7524            }
7525            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7526            second = FALSE;
7527         }
7528         // ignore clicks on holdings
7529         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7530     }
7531
7532     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7533         DragPieceEnd(xPix, yPix); dragging = 0;
7534         if(clearFlag) {
7535             // a deferred attempt to click-click move an empty square on top of a piece
7536             boards[currentMove][y][x] = EmptySquare;
7537             ClearHighlights();
7538             DrawPosition(FALSE, boards[currentMove]);
7539             fromX = fromY = -1; clearFlag = 0;
7540             return;
7541         }
7542         if (appData.animateDragging) {
7543             /* Undo animation damage if any */
7544             DrawPosition(FALSE, NULL);
7545         }
7546         if (second || sweepSelecting) {
7547             /* Second up/down in same square; just abort move */
7548             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7549             second = sweepSelecting = 0;
7550             fromX = fromY = -1;
7551             gatingPiece = EmptySquare;
7552             MarkTargetSquares(1);
7553             ClearHighlights();
7554             gotPremove = 0;
7555             ClearPremoveHighlights();
7556         } else {
7557             /* First upclick in same square; start click-click mode */
7558             SetHighlights(x, y, -1, -1);
7559         }
7560         return;
7561     }
7562
7563     clearFlag = 0;
7564
7565     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7566         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7567         DisplayMessage(_("only marked squares are legal"),"");
7568         DrawPosition(TRUE, NULL);
7569         return; // ignore to-click
7570     }
7571
7572     /* we now have a different from- and (possibly off-board) to-square */
7573     /* Completed move */
7574     if(!sweepSelecting) {
7575         toX = x;
7576         toY = y;
7577     }
7578
7579     piece = boards[currentMove][fromY][fromX];
7580
7581     saveAnimate = appData.animate;
7582     if (clickType == Press) {
7583         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7584         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7585             // must be Edit Position mode with empty-square selected
7586             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7587             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7588             return;
7589         }
7590         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7591             return;
7592         }
7593         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7594             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7595         } else
7596         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7597         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7598           if(appData.sweepSelect) {
7599             promoSweep = defaultPromoChoice;
7600             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7601             selectFlag = 0; lastX = xPix; lastY = yPix;
7602             Sweep(0); // Pawn that is going to promote: preview promotion piece
7603             sweepSelecting = 1;
7604             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7605             MarkTargetSquares(1);
7606           }
7607           return; // promo popup appears on up-click
7608         }
7609         /* Finish clickclick move */
7610         if (appData.animate || appData.highlightLastMove) {
7611             SetHighlights(fromX, fromY, toX, toY);
7612         } else {
7613             ClearHighlights();
7614         }
7615     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7616         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7617         if (appData.animate || appData.highlightLastMove) {
7618             SetHighlights(fromX, fromY, toX, toY);
7619         } else {
7620             ClearHighlights();
7621         }
7622     } else {
7623 #if 0
7624 // [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
7625         /* Finish drag move */
7626         if (appData.highlightLastMove) {
7627             SetHighlights(fromX, fromY, toX, toY);
7628         } else {
7629             ClearHighlights();
7630         }
7631 #endif
7632         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7633         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7634           dragging *= 2;            // flag button-less dragging if we are dragging
7635           MarkTargetSquares(1);
7636           if(x == killX && y == killY) killX = killY = -1; else {
7637             killX = x; killY = y;     //remeber this square as intermediate
7638             ReportClick("put", x, y); // and inform engine
7639             ReportClick("lift", x, y);
7640             MarkTargetSquares(0);
7641             return;
7642           }
7643         }
7644         DragPieceEnd(xPix, yPix); dragging = 0;
7645         /* Don't animate move and drag both */
7646         appData.animate = FALSE;
7647     }
7648
7649     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7650     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7651         ChessSquare piece = boards[currentMove][fromY][fromX];
7652         if(gameMode == EditPosition && piece != EmptySquare &&
7653            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7654             int n;
7655
7656             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7657                 n = PieceToNumber(piece - (int)BlackPawn);
7658                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7659                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7660                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7661             } else
7662             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7663                 n = PieceToNumber(piece);
7664                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7665                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7666                 boards[currentMove][n][BOARD_WIDTH-2]++;
7667             }
7668             boards[currentMove][fromY][fromX] = EmptySquare;
7669         }
7670         ClearHighlights();
7671         fromX = fromY = -1;
7672         MarkTargetSquares(1);
7673         DrawPosition(TRUE, boards[currentMove]);
7674         return;
7675     }
7676
7677     // off-board moves should not be highlighted
7678     if(x < 0 || y < 0) ClearHighlights();
7679     else ReportClick("put", x, y);
7680
7681     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7682
7683     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7684         SetHighlights(fromX, fromY, toX, toY);
7685         MarkTargetSquares(1);
7686         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7687             // [HGM] super: promotion to captured piece selected from holdings
7688             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7689             promotionChoice = TRUE;
7690             // kludge follows to temporarily execute move on display, without promoting yet
7691             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7692             boards[currentMove][toY][toX] = p;
7693             DrawPosition(FALSE, boards[currentMove]);
7694             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7695             boards[currentMove][toY][toX] = q;
7696             DisplayMessage("Click in holdings to choose piece", "");
7697             return;
7698         }
7699         PromotionPopUp(promoChoice);
7700     } else {
7701         int oldMove = currentMove;
7702         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7703         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7704         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7705         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7706            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7707             DrawPosition(TRUE, boards[currentMove]);
7708         MarkTargetSquares(1);
7709         fromX = fromY = -1;
7710     }
7711     appData.animate = saveAnimate;
7712     if (appData.animate || appData.animateDragging) {
7713         /* Undo animation damage if needed */
7714         DrawPosition(FALSE, NULL);
7715     }
7716 }
7717
7718 int
7719 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7720 {   // front-end-free part taken out of PieceMenuPopup
7721     int whichMenu; int xSqr, ySqr;
7722
7723     if(seekGraphUp) { // [HGM] seekgraph
7724         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7725         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7726         return -2;
7727     }
7728
7729     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7730          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7731         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7732         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7733         if(action == Press)   {
7734             originalFlip = flipView;
7735             flipView = !flipView; // temporarily flip board to see game from partners perspective
7736             DrawPosition(TRUE, partnerBoard);
7737             DisplayMessage(partnerStatus, "");
7738             partnerUp = TRUE;
7739         } else if(action == Release) {
7740             flipView = originalFlip;
7741             DrawPosition(TRUE, boards[currentMove]);
7742             partnerUp = FALSE;
7743         }
7744         return -2;
7745     }
7746
7747     xSqr = EventToSquare(x, BOARD_WIDTH);
7748     ySqr = EventToSquare(y, BOARD_HEIGHT);
7749     if (action == Release) {
7750         if(pieceSweep != EmptySquare) {
7751             EditPositionMenuEvent(pieceSweep, toX, toY);
7752             pieceSweep = EmptySquare;
7753         } else UnLoadPV(); // [HGM] pv
7754     }
7755     if (action != Press) return -2; // return code to be ignored
7756     switch (gameMode) {
7757       case IcsExamining:
7758         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7759       case EditPosition:
7760         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7761         if (xSqr < 0 || ySqr < 0) return -1;
7762         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7763         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7764         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7765         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7766         NextPiece(0);
7767         return 2; // grab
7768       case IcsObserving:
7769         if(!appData.icsEngineAnalyze) return -1;
7770       case IcsPlayingWhite:
7771       case IcsPlayingBlack:
7772         if(!appData.zippyPlay) goto noZip;
7773       case AnalyzeMode:
7774       case AnalyzeFile:
7775       case MachinePlaysWhite:
7776       case MachinePlaysBlack:
7777       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7778         if (!appData.dropMenu) {
7779           LoadPV(x, y);
7780           return 2; // flag front-end to grab mouse events
7781         }
7782         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7783            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7784       case EditGame:
7785       noZip:
7786         if (xSqr < 0 || ySqr < 0) return -1;
7787         if (!appData.dropMenu || appData.testLegality &&
7788             gameInfo.variant != VariantBughouse &&
7789             gameInfo.variant != VariantCrazyhouse) return -1;
7790         whichMenu = 1; // drop menu
7791         break;
7792       default:
7793         return -1;
7794     }
7795
7796     if (((*fromX = xSqr) < 0) ||
7797         ((*fromY = ySqr) < 0)) {
7798         *fromX = *fromY = -1;
7799         return -1;
7800     }
7801     if (flipView)
7802       *fromX = BOARD_WIDTH - 1 - *fromX;
7803     else
7804       *fromY = BOARD_HEIGHT - 1 - *fromY;
7805
7806     return whichMenu;
7807 }
7808
7809 void
7810 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7811 {
7812 //    char * hint = lastHint;
7813     FrontEndProgramStats stats;
7814
7815     stats.which = cps == &first ? 0 : 1;
7816     stats.depth = cpstats->depth;
7817     stats.nodes = cpstats->nodes;
7818     stats.score = cpstats->score;
7819     stats.time = cpstats->time;
7820     stats.pv = cpstats->movelist;
7821     stats.hint = lastHint;
7822     stats.an_move_index = 0;
7823     stats.an_move_count = 0;
7824
7825     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7826         stats.hint = cpstats->move_name;
7827         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7828         stats.an_move_count = cpstats->nr_moves;
7829     }
7830
7831     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
7832
7833     SetProgramStats( &stats );
7834 }
7835
7836 void
7837 ClearEngineOutputPane (int which)
7838 {
7839     static FrontEndProgramStats dummyStats;
7840     dummyStats.which = which;
7841     dummyStats.pv = "#";
7842     SetProgramStats( &dummyStats );
7843 }
7844
7845 #define MAXPLAYERS 500
7846
7847 char *
7848 TourneyStandings (int display)
7849 {
7850     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7851     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7852     char result, *p, *names[MAXPLAYERS];
7853
7854     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7855         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7856     names[0] = p = strdup(appData.participants);
7857     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7858
7859     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7860
7861     while(result = appData.results[nr]) {
7862         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7863         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7864         wScore = bScore = 0;
7865         switch(result) {
7866           case '+': wScore = 2; break;
7867           case '-': bScore = 2; break;
7868           case '=': wScore = bScore = 1; break;
7869           case ' ':
7870           case '*': return strdup("busy"); // tourney not finished
7871         }
7872         score[w] += wScore;
7873         score[b] += bScore;
7874         games[w]++;
7875         games[b]++;
7876         nr++;
7877     }
7878     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7879     for(w=0; w<nPlayers; w++) {
7880         bScore = -1;
7881         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7882         ranking[w] = b; points[w] = bScore; score[b] = -2;
7883     }
7884     p = malloc(nPlayers*34+1);
7885     for(w=0; w<nPlayers && w<display; w++)
7886         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7887     free(names[0]);
7888     return p;
7889 }
7890
7891 void
7892 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7893 {       // count all piece types
7894         int p, f, r;
7895         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7896         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7897         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7898                 p = board[r][f];
7899                 pCnt[p]++;
7900                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7901                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7902                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7903                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7904                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7905                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7906         }
7907 }
7908
7909 int
7910 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7911 {
7912         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7913         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7914
7915         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7916         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7917         if(myPawns == 2 && nMine == 3) // KPP
7918             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7919         if(myPawns == 1 && nMine == 2) // KP
7920             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7921         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7922             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7923         if(myPawns) return FALSE;
7924         if(pCnt[WhiteRook+side])
7925             return pCnt[BlackRook-side] ||
7926                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7927                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7928                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7929         if(pCnt[WhiteCannon+side]) {
7930             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7931             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7932         }
7933         if(pCnt[WhiteKnight+side])
7934             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7935         return FALSE;
7936 }
7937
7938 int
7939 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7940 {
7941         VariantClass v = gameInfo.variant;
7942
7943         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7944         if(v == VariantShatranj) return TRUE; // always winnable through baring
7945         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7946         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7947
7948         if(v == VariantXiangqi) {
7949                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7950
7951                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7952                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7953                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7954                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7955                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7956                 if(stale) // we have at least one last-rank P plus perhaps C
7957                     return majors // KPKX
7958                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7959                 else // KCA*E*
7960                     return pCnt[WhiteFerz+side] // KCAK
7961                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7962                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7963                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7964
7965         } else if(v == VariantKnightmate) {
7966                 if(nMine == 1) return FALSE;
7967                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7968         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7969                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7970
7971                 if(nMine == 1) return FALSE; // bare King
7972                 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
7973                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7974                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7975                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7976                 if(pCnt[WhiteKnight+side])
7977                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7978                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7979                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7980                 if(nBishops)
7981                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7982                 if(pCnt[WhiteAlfil+side])
7983                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7984                 if(pCnt[WhiteWazir+side])
7985                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7986         }
7987
7988         return TRUE;
7989 }
7990
7991 int
7992 CompareWithRights (Board b1, Board b2)
7993 {
7994     int rights = 0;
7995     if(!CompareBoards(b1, b2)) return FALSE;
7996     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7997     /* compare castling rights */
7998     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7999            rights++; /* King lost rights, while rook still had them */
8000     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8001         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8002            rights++; /* but at least one rook lost them */
8003     }
8004     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8005            rights++;
8006     if( b1[CASTLING][5] != NoRights ) {
8007         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8008            rights++;
8009     }
8010     return rights == 0;
8011 }
8012
8013 int
8014 Adjudicate (ChessProgramState *cps)
8015 {       // [HGM] some adjudications useful with buggy engines
8016         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8017         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8018         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8019         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8020         int k, drop, count = 0; static int bare = 1;
8021         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8022         Boolean canAdjudicate = !appData.icsActive;
8023
8024         // most tests only when we understand the game, i.e. legality-checking on
8025             if( appData.testLegality )
8026             {   /* [HGM] Some more adjudications for obstinate engines */
8027                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8028                 static int moveCount = 6;
8029                 ChessMove result;
8030                 char *reason = NULL;
8031
8032                 /* Count what is on board. */
8033                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8034
8035                 /* Some material-based adjudications that have to be made before stalemate test */
8036                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8037                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8038                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8039                      if(canAdjudicate && appData.checkMates) {
8040                          if(engineOpponent)
8041                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8042                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8043                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8044                          return 1;
8045                      }
8046                 }
8047
8048                 /* Bare King in Shatranj (loses) or Losers (wins) */
8049                 if( nrW == 1 || nrB == 1) {
8050                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8051                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8052                      if(canAdjudicate && appData.checkMates) {
8053                          if(engineOpponent)
8054                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8055                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8056                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8057                          return 1;
8058                      }
8059                   } else
8060                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8061                   {    /* bare King */
8062                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8063                         if(canAdjudicate && appData.checkMates) {
8064                             /* but only adjudicate if adjudication enabled */
8065                             if(engineOpponent)
8066                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8067                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8069                             return 1;
8070                         }
8071                   }
8072                 } else bare = 1;
8073
8074
8075             // don't wait for engine to announce game end if we can judge ourselves
8076             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8077               case MT_CHECK:
8078                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8079                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8080                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8081                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8082                             checkCnt++;
8083                         if(checkCnt >= 2) {
8084                             reason = "Xboard adjudication: 3rd check";
8085                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8086                             break;
8087                         }
8088                     }
8089                 }
8090               case MT_NONE:
8091               default:
8092                 break;
8093               case MT_STEALMATE:
8094               case MT_STALEMATE:
8095               case MT_STAINMATE:
8096                 reason = "Xboard adjudication: Stalemate";
8097                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8098                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8099                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8100                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8101                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8102                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8103                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8104                                                                         EP_CHECKMATE : EP_WINS);
8105                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8106                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8107                 }
8108                 break;
8109               case MT_CHECKMATE:
8110                 reason = "Xboard adjudication: Checkmate";
8111                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8112                 if(gameInfo.variant == VariantShogi) {
8113                     if(forwardMostMove > backwardMostMove
8114                        && moveList[forwardMostMove-1][1] == '@'
8115                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8116                         reason = "XBoard adjudication: pawn-drop mate";
8117                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8118                     }
8119                 }
8120                 break;
8121             }
8122
8123                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8124                     case EP_STALEMATE:
8125                         result = GameIsDrawn; break;
8126                     case EP_CHECKMATE:
8127                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8128                     case EP_WINS:
8129                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8130                     default:
8131                         result = EndOfFile;
8132                 }
8133                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8134                     if(engineOpponent)
8135                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8136                     GameEnds( result, reason, GE_XBOARD );
8137                     return 1;
8138                 }
8139
8140                 /* Next absolutely insufficient mating material. */
8141                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8142                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8143                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8144
8145                      /* always flag draws, for judging claims */
8146                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8147
8148                      if(canAdjudicate && appData.materialDraws) {
8149                          /* but only adjudicate them if adjudication enabled */
8150                          if(engineOpponent) {
8151                            SendToProgram("force\n", engineOpponent); // suppress reply
8152                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8153                          }
8154                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8155                          return 1;
8156                      }
8157                 }
8158
8159                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8160                 if(gameInfo.variant == VariantXiangqi ?
8161                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8162                  : nrW + nrB == 4 &&
8163                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8164                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8165                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8166                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8167                    ) ) {
8168                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8169                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8170                           if(engineOpponent) {
8171                             SendToProgram("force\n", engineOpponent); // suppress reply
8172                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8173                           }
8174                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8175                           return 1;
8176                      }
8177                 } else moveCount = 6;
8178             }
8179
8180         // Repetition draws and 50-move rule can be applied independently of legality testing
8181
8182                 /* Check for rep-draws */
8183                 count = 0;
8184                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8185                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8186                 for(k = forwardMostMove-2;
8187                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8188                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8189                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8190                     k-=2)
8191                 {   int rights=0;
8192                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8193                         /* compare castling rights */
8194                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8195                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8196                                 rights++; /* King lost rights, while rook still had them */
8197                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8198                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8199                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8200                                    rights++; /* but at least one rook lost them */
8201                         }
8202                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8203                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8204                                 rights++;
8205                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8206                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8207                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8208                                    rights++;
8209                         }
8210                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8211                             && appData.drawRepeats > 1) {
8212                              /* adjudicate after user-specified nr of repeats */
8213                              int result = GameIsDrawn;
8214                              char *details = "XBoard adjudication: repetition draw";
8215                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8216                                 // [HGM] xiangqi: check for forbidden perpetuals
8217                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8218                                 for(m=forwardMostMove; m>k; m-=2) {
8219                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8220                                         ourPerpetual = 0; // the current mover did not always check
8221                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8222                                         hisPerpetual = 0; // the opponent did not always check
8223                                 }
8224                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8225                                                                         ourPerpetual, hisPerpetual);
8226                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8227                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8228                                     details = "Xboard adjudication: perpetual checking";
8229                                 } else
8230                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8231                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8232                                 } else
8233                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8234                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8235                                         result = BlackWins;
8236                                         details = "Xboard adjudication: repetition";
8237                                     }
8238                                 } else // it must be XQ
8239                                 // Now check for perpetual chases
8240                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8241                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8242                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8243                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8244                                         static char resdet[MSG_SIZ];
8245                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8246                                         details = resdet;
8247                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8248                                     } else
8249                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8250                                         break; // Abort repetition-checking loop.
8251                                 }
8252                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8253                              }
8254                              if(engineOpponent) {
8255                                SendToProgram("force\n", engineOpponent); // suppress reply
8256                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8257                              }
8258                              GameEnds( result, details, GE_XBOARD );
8259                              return 1;
8260                         }
8261                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8262                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8263                     }
8264                 }
8265
8266                 /* Now we test for 50-move draws. Determine ply count */
8267                 count = forwardMostMove;
8268                 /* look for last irreversble move */
8269                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8270                     count--;
8271                 /* if we hit starting position, add initial plies */
8272                 if( count == backwardMostMove )
8273                     count -= initialRulePlies;
8274                 count = forwardMostMove - count;
8275                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8276                         // adjust reversible move counter for checks in Xiangqi
8277                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8278                         if(i < backwardMostMove) i = backwardMostMove;
8279                         while(i <= forwardMostMove) {
8280                                 lastCheck = inCheck; // check evasion does not count
8281                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8282                                 if(inCheck || lastCheck) count--; // check does not count
8283                                 i++;
8284                         }
8285                 }
8286                 if( count >= 100)
8287                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8288                          /* this is used to judge if draw claims are legal */
8289                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8290                          if(engineOpponent) {
8291                            SendToProgram("force\n", engineOpponent); // suppress reply
8292                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8293                          }
8294                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8295                          return 1;
8296                 }
8297
8298                 /* if draw offer is pending, treat it as a draw claim
8299                  * when draw condition present, to allow engines a way to
8300                  * claim draws before making their move to avoid a race
8301                  * condition occurring after their move
8302                  */
8303                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8304                          char *p = NULL;
8305                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8306                              p = "Draw claim: 50-move rule";
8307                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8308                              p = "Draw claim: 3-fold repetition";
8309                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8310                              p = "Draw claim: insufficient mating material";
8311                          if( p != NULL && canAdjudicate) {
8312                              if(engineOpponent) {
8313                                SendToProgram("force\n", engineOpponent); // suppress reply
8314                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8315                              }
8316                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8317                              return 1;
8318                          }
8319                 }
8320
8321                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8322                     if(engineOpponent) {
8323                       SendToProgram("force\n", engineOpponent); // suppress reply
8324                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8325                     }
8326                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8327                     return 1;
8328                 }
8329         return 0;
8330 }
8331
8332 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8333 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8334 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8335
8336 static int
8337 BitbaseProbe ()
8338 {
8339     int pieces[10], squares[10], cnt=0, r, f, res;
8340     static int loaded;
8341     static PPROBE_EGBB probeBB;
8342     if(!appData.testLegality) return 10;
8343     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8344     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8345     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8346     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8347         ChessSquare piece = boards[forwardMostMove][r][f];
8348         int black = (piece >= BlackPawn);
8349         int type = piece - black*BlackPawn;
8350         if(piece == EmptySquare) continue;
8351         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8352         if(type == WhiteKing) type = WhiteQueen + 1;
8353         type = egbbCode[type];
8354         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8355         pieces[cnt] = type + black*6;
8356         if(++cnt > 5) return 11;
8357     }
8358     pieces[cnt] = squares[cnt] = 0;
8359     // probe EGBB
8360     if(loaded == 2) return 13; // loading failed before
8361     if(loaded == 0) {
8362         loaded = 2; // prepare for failure
8363         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8364         HMODULE lib;
8365         PLOAD_EGBB loadBB;
8366         if(!path) return 13; // no egbb installed
8367         strncpy(buf, path + 8, MSG_SIZ);
8368         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8369         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8370         lib = LoadLibrary(buf);
8371         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8372         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8373         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8374         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8375         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8376         loaded = 1; // success!
8377     }
8378     res = probeBB(forwardMostMove & 1, pieces, squares);
8379     return res > 0 ? 1 : res < 0 ? -1 : 0;
8380 }
8381
8382 char *
8383 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8384 {   // [HGM] book: this routine intercepts moves to simulate book replies
8385     char *bookHit = NULL;
8386
8387     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8388         char buf[MSG_SIZ];
8389         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8390         SendToProgram(buf, cps);
8391     }
8392     //first determine if the incoming move brings opponent into his book
8393     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8394         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8395     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8396     if(bookHit != NULL && !cps->bookSuspend) {
8397         // make sure opponent is not going to reply after receiving move to book position
8398         SendToProgram("force\n", cps);
8399         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8400     }
8401     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8402     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8403     // now arrange restart after book miss
8404     if(bookHit) {
8405         // after a book hit we never send 'go', and the code after the call to this routine
8406         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8407         char buf[MSG_SIZ], *move = bookHit;
8408         if(cps->useSAN) {
8409             int fromX, fromY, toX, toY;
8410             char promoChar;
8411             ChessMove moveType;
8412             move = buf + 30;
8413             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8414                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8415                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8416                                     PosFlags(forwardMostMove),
8417                                     fromY, fromX, toY, toX, promoChar, move);
8418             } else {
8419                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8420                 bookHit = NULL;
8421             }
8422         }
8423         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8424         SendToProgram(buf, cps);
8425         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8426     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8427         SendToProgram("go\n", cps);
8428         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8429     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8430         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8431             SendToProgram("go\n", cps);
8432         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8433     }
8434     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8435 }
8436
8437 int
8438 LoadError (char *errmess, ChessProgramState *cps)
8439 {   // unloads engine and switches back to -ncp mode if it was first
8440     if(cps->initDone) return FALSE;
8441     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8442     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8443     cps->pr = NoProc;
8444     if(cps == &first) {
8445         appData.noChessProgram = TRUE;
8446         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8447         gameMode = BeginningOfGame; ModeHighlight();
8448         SetNCPMode();
8449     }
8450     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8451     DisplayMessage("", ""); // erase waiting message
8452     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8453     return TRUE;
8454 }
8455
8456 char *savedMessage;
8457 ChessProgramState *savedState;
8458 void
8459 DeferredBookMove (void)
8460 {
8461         if(savedState->lastPing != savedState->lastPong)
8462                     ScheduleDelayedEvent(DeferredBookMove, 10);
8463         else
8464         HandleMachineMove(savedMessage, savedState);
8465 }
8466
8467 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8468 static ChessProgramState *stalledEngine;
8469 static char stashedInputMove[MSG_SIZ];
8470
8471 void
8472 HandleMachineMove (char *message, ChessProgramState *cps)
8473 {
8474     static char firstLeg[20];
8475     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8476     char realname[MSG_SIZ];
8477     int fromX, fromY, toX, toY;
8478     ChessMove moveType;
8479     char promoChar, roar;
8480     char *p, *pv=buf1;
8481     int machineWhite, oldError;
8482     char *bookHit;
8483
8484     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8485         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8486         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8487             DisplayError(_("Invalid pairing from pairing engine"), 0);
8488             return;
8489         }
8490         pairingReceived = 1;
8491         NextMatchGame();
8492         return; // Skim the pairing messages here.
8493     }
8494
8495     oldError = cps->userError; cps->userError = 0;
8496
8497 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8498     /*
8499      * Kludge to ignore BEL characters
8500      */
8501     while (*message == '\007') message++;
8502
8503     /*
8504      * [HGM] engine debug message: ignore lines starting with '#' character
8505      */
8506     if(cps->debug && *message == '#') return;
8507
8508     /*
8509      * Look for book output
8510      */
8511     if (cps == &first && bookRequested) {
8512         if (message[0] == '\t' || message[0] == ' ') {
8513             /* Part of the book output is here; append it */
8514             strcat(bookOutput, message);
8515             strcat(bookOutput, "  \n");
8516             return;
8517         } else if (bookOutput[0] != NULLCHAR) {
8518             /* All of book output has arrived; display it */
8519             char *p = bookOutput;
8520             while (*p != NULLCHAR) {
8521                 if (*p == '\t') *p = ' ';
8522                 p++;
8523             }
8524             DisplayInformation(bookOutput);
8525             bookRequested = FALSE;
8526             /* Fall through to parse the current output */
8527         }
8528     }
8529
8530     /*
8531      * Look for machine move.
8532      */
8533     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8534         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8535     {
8536         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8537             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8538             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8539             stalledEngine = cps;
8540             if(appData.ponderNextMove) { // bring opponent out of ponder
8541                 if(gameMode == TwoMachinesPlay) {
8542                     if(cps->other->pause)
8543                         PauseEngine(cps->other);
8544                     else
8545                         SendToProgram("easy\n", cps->other);
8546                 }
8547             }
8548             StopClocks();
8549             return;
8550         }
8551
8552         /* This method is only useful on engines that support ping */
8553         if (cps->lastPing != cps->lastPong) {
8554           if (gameMode == BeginningOfGame) {
8555             /* Extra move from before last new; ignore */
8556             if (appData.debugMode) {
8557                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8558             }
8559           } else {
8560             if (appData.debugMode) {
8561                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8562                         cps->which, gameMode);
8563             }
8564
8565             SendToProgram("undo\n", cps);
8566           }
8567           return;
8568         }
8569
8570         switch (gameMode) {
8571           case BeginningOfGame:
8572             /* Extra move from before last reset; ignore */
8573             if (appData.debugMode) {
8574                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8575             }
8576             return;
8577
8578           case EndOfGame:
8579           case IcsIdle:
8580           default:
8581             /* Extra move after we tried to stop.  The mode test is
8582                not a reliable way of detecting this problem, but it's
8583                the best we can do on engines that don't support ping.
8584             */
8585             if (appData.debugMode) {
8586                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8587                         cps->which, gameMode);
8588             }
8589             SendToProgram("undo\n", cps);
8590             return;
8591
8592           case MachinePlaysWhite:
8593           case IcsPlayingWhite:
8594             machineWhite = TRUE;
8595             break;
8596
8597           case MachinePlaysBlack:
8598           case IcsPlayingBlack:
8599             machineWhite = FALSE;
8600             break;
8601
8602           case TwoMachinesPlay:
8603             machineWhite = (cps->twoMachinesColor[0] == 'w');
8604             break;
8605         }
8606         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8607             if (appData.debugMode) {
8608                 fprintf(debugFP,
8609                         "Ignoring move out of turn by %s, gameMode %d"
8610                         ", forwardMost %d\n",
8611                         cps->which, gameMode, forwardMostMove);
8612             }
8613             return;
8614         }
8615
8616         if(cps->alphaRank) AlphaRank(machineMove, 4);
8617
8618         // [HGM] lion: (some very limited) support for Alien protocol
8619         killX = killY = -1;
8620         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8621             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8622             return;
8623         } else if(firstLeg[0]) { // there was a previous leg;
8624             // only support case where same piece makes two step (and don't even test that!)
8625             char buf[20], *p = machineMove+1, *q = buf+1, f;
8626             safeStrCpy(buf, machineMove, 20);
8627             while(isdigit(*q)) q++; // find start of to-square
8628             safeStrCpy(machineMove, firstLeg, 20);
8629             while(isdigit(*p)) p++;
8630             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8631             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8632             firstLeg[0] = NULLCHAR;
8633         }
8634
8635         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8636                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8637             /* Machine move could not be parsed; ignore it. */
8638           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8639                     machineMove, _(cps->which));
8640             DisplayMoveError(buf1);
8641             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8642                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8643             if (gameMode == TwoMachinesPlay) {
8644               GameEnds(machineWhite ? BlackWins : WhiteWins,
8645                        buf1, GE_XBOARD);
8646             }
8647             return;
8648         }
8649
8650         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8651         /* So we have to redo legality test with true e.p. status here,  */
8652         /* to make sure an illegal e.p. capture does not slip through,   */
8653         /* to cause a forfeit on a justified illegal-move complaint      */
8654         /* of the opponent.                                              */
8655         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8656            ChessMove moveType;
8657            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8658                              fromY, fromX, toY, toX, promoChar);
8659             if(moveType == IllegalMove) {
8660               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8661                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8662                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8663                            buf1, GE_XBOARD);
8664                 return;
8665            } else if(!appData.fischerCastling)
8666            /* [HGM] Kludge to handle engines that send FRC-style castling
8667               when they shouldn't (like TSCP-Gothic) */
8668            switch(moveType) {
8669              case WhiteASideCastleFR:
8670              case BlackASideCastleFR:
8671                toX+=2;
8672                currentMoveString[2]++;
8673                break;
8674              case WhiteHSideCastleFR:
8675              case BlackHSideCastleFR:
8676                toX--;
8677                currentMoveString[2]--;
8678                break;
8679              default: ; // nothing to do, but suppresses warning of pedantic compilers
8680            }
8681         }
8682         hintRequested = FALSE;
8683         lastHint[0] = NULLCHAR;
8684         bookRequested = FALSE;
8685         /* Program may be pondering now */
8686         cps->maybeThinking = TRUE;
8687         if (cps->sendTime == 2) cps->sendTime = 1;
8688         if (cps->offeredDraw) cps->offeredDraw--;
8689
8690         /* [AS] Save move info*/
8691         pvInfoList[ forwardMostMove ].score = programStats.score;
8692         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8693         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8694
8695         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8696
8697         /* Test suites abort the 'game' after one move */
8698         if(*appData.finger) {
8699            static FILE *f;
8700            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8701            if(!f) f = fopen(appData.finger, "w");
8702            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8703            else { DisplayFatalError("Bad output file", errno, 0); return; }
8704            free(fen);
8705            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8706         }
8707
8708         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8709         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8710             int count = 0;
8711
8712             while( count < adjudicateLossPlies ) {
8713                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8714
8715                 if( count & 1 ) {
8716                     score = -score; /* Flip score for winning side */
8717                 }
8718 printf("score=%d count=%d\n",score,count);
8719                 if( score > appData.adjudicateLossThreshold ) {
8720                     break;
8721                 }
8722
8723                 count++;
8724             }
8725
8726             if( count >= adjudicateLossPlies ) {
8727                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8728
8729                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8730                     "Xboard adjudication",
8731                     GE_XBOARD );
8732
8733                 return;
8734             }
8735         }
8736
8737         if(Adjudicate(cps)) {
8738             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8739             return; // [HGM] adjudicate: for all automatic game ends
8740         }
8741
8742 #if ZIPPY
8743         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8744             first.initDone) {
8745           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8746                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8747                 SendToICS("draw ");
8748                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8749           }
8750           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8751           ics_user_moved = 1;
8752           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8753                 char buf[3*MSG_SIZ];
8754
8755                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8756                         programStats.score / 100.,
8757                         programStats.depth,
8758                         programStats.time / 100.,
8759                         (unsigned int)programStats.nodes,
8760                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8761                         programStats.movelist);
8762                 SendToICS(buf);
8763           }
8764         }
8765 #endif
8766
8767         /* [AS] Clear stats for next move */
8768         ClearProgramStats();
8769         thinkOutput[0] = NULLCHAR;
8770         hiddenThinkOutputState = 0;
8771
8772         bookHit = NULL;
8773         if (gameMode == TwoMachinesPlay) {
8774             /* [HGM] relaying draw offers moved to after reception of move */
8775             /* and interpreting offer as claim if it brings draw condition */
8776             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8777                 SendToProgram("draw\n", cps->other);
8778             }
8779             if (cps->other->sendTime) {
8780                 SendTimeRemaining(cps->other,
8781                                   cps->other->twoMachinesColor[0] == 'w');
8782             }
8783             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8784             if (firstMove && !bookHit) {
8785                 firstMove = FALSE;
8786                 if (cps->other->useColors) {
8787                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8788                 }
8789                 SendToProgram("go\n", cps->other);
8790             }
8791             cps->other->maybeThinking = TRUE;
8792         }
8793
8794         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8795
8796         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8797
8798         if (!pausing && appData.ringBellAfterMoves) {
8799             if(!roar) RingBell();
8800         }
8801
8802         /*
8803          * Reenable menu items that were disabled while
8804          * machine was thinking
8805          */
8806         if (gameMode != TwoMachinesPlay)
8807             SetUserThinkingEnables();
8808
8809         // [HGM] book: after book hit opponent has received move and is now in force mode
8810         // force the book reply into it, and then fake that it outputted this move by jumping
8811         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8812         if(bookHit) {
8813                 static char bookMove[MSG_SIZ]; // a bit generous?
8814
8815                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8816                 strcat(bookMove, bookHit);
8817                 message = bookMove;
8818                 cps = cps->other;
8819                 programStats.nodes = programStats.depth = programStats.time =
8820                 programStats.score = programStats.got_only_move = 0;
8821                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8822
8823                 if(cps->lastPing != cps->lastPong) {
8824                     savedMessage = message; // args for deferred call
8825                     savedState = cps;
8826                     ScheduleDelayedEvent(DeferredBookMove, 10);
8827                     return;
8828                 }
8829                 goto FakeBookMove;
8830         }
8831
8832         return;
8833     }
8834
8835     /* Set special modes for chess engines.  Later something general
8836      *  could be added here; for now there is just one kludge feature,
8837      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8838      *  when "xboard" is given as an interactive command.
8839      */
8840     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8841         cps->useSigint = FALSE;
8842         cps->useSigterm = FALSE;
8843     }
8844     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8845       ParseFeatures(message+8, cps);
8846       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8847     }
8848
8849     if (!strncmp(message, "setup ", 6) && 
8850         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8851           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8852                                         ) { // [HGM] allow first engine to define opening position
8853       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8854       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8855       *buf = NULLCHAR;
8856       if(sscanf(message, "setup (%s", buf) == 1) {
8857         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8858         ASSIGN(appData.pieceToCharTable, buf);
8859       }
8860       if(startedFromSetupPosition) return;
8861       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8862       if(dummy >= 3) {
8863         while(message[s] && message[s++] != ' ');
8864         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8865            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8866             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8867             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8868           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8869           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8870         }
8871       }
8872       ParseFEN(boards[0], &dummy, message+s, FALSE);
8873       DrawPosition(TRUE, boards[0]);
8874       startedFromSetupPosition = TRUE;
8875       return;
8876     }
8877     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8878      * want this, I was asked to put it in, and obliged.
8879      */
8880     if (!strncmp(message, "setboard ", 9)) {
8881         Board initial_position;
8882
8883         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8884
8885         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8886             DisplayError(_("Bad FEN received from engine"), 0);
8887             return ;
8888         } else {
8889            Reset(TRUE, FALSE);
8890            CopyBoard(boards[0], initial_position);
8891            initialRulePlies = FENrulePlies;
8892            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8893            else gameMode = MachinePlaysBlack;
8894            DrawPosition(FALSE, boards[currentMove]);
8895         }
8896         return;
8897     }
8898
8899     /*
8900      * Look for communication commands
8901      */
8902     if (!strncmp(message, "telluser ", 9)) {
8903         if(message[9] == '\\' && message[10] == '\\')
8904             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8905         PlayTellSound();
8906         DisplayNote(message + 9);
8907         return;
8908     }
8909     if (!strncmp(message, "tellusererror ", 14)) {
8910         cps->userError = 1;
8911         if(message[14] == '\\' && message[15] == '\\')
8912             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8913         PlayTellSound();
8914         DisplayError(message + 14, 0);
8915         return;
8916     }
8917     if (!strncmp(message, "tellopponent ", 13)) {
8918       if (appData.icsActive) {
8919         if (loggedOn) {
8920           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8921           SendToICS(buf1);
8922         }
8923       } else {
8924         DisplayNote(message + 13);
8925       }
8926       return;
8927     }
8928     if (!strncmp(message, "tellothers ", 11)) {
8929       if (appData.icsActive) {
8930         if (loggedOn) {
8931           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8932           SendToICS(buf1);
8933         }
8934       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8935       return;
8936     }
8937     if (!strncmp(message, "tellall ", 8)) {
8938       if (appData.icsActive) {
8939         if (loggedOn) {
8940           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8941           SendToICS(buf1);
8942         }
8943       } else {
8944         DisplayNote(message + 8);
8945       }
8946       return;
8947     }
8948     if (strncmp(message, "warning", 7) == 0) {
8949         /* Undocumented feature, use tellusererror in new code */
8950         DisplayError(message, 0);
8951         return;
8952     }
8953     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8954         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8955         strcat(realname, " query");
8956         AskQuestion(realname, buf2, buf1, cps->pr);
8957         return;
8958     }
8959     /* Commands from the engine directly to ICS.  We don't allow these to be
8960      *  sent until we are logged on. Crafty kibitzes have been known to
8961      *  interfere with the login process.
8962      */
8963     if (loggedOn) {
8964         if (!strncmp(message, "tellics ", 8)) {
8965             SendToICS(message + 8);
8966             SendToICS("\n");
8967             return;
8968         }
8969         if (!strncmp(message, "tellicsnoalias ", 15)) {
8970             SendToICS(ics_prefix);
8971             SendToICS(message + 15);
8972             SendToICS("\n");
8973             return;
8974         }
8975         /* The following are for backward compatibility only */
8976         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8977             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8978             SendToICS(ics_prefix);
8979             SendToICS(message);
8980             SendToICS("\n");
8981             return;
8982         }
8983     }
8984     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8985         if(initPing == cps->lastPong) {
8986             if(gameInfo.variant == VariantUnknown) {
8987                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8988                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8989                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8990             }
8991             initPing = -1;
8992         }
8993         return;
8994     }
8995     if(!strncmp(message, "highlight ", 10)) {
8996         if(appData.testLegality && appData.markers) return;
8997         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8998         return;
8999     }
9000     if(!strncmp(message, "click ", 6)) {
9001         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9002         if(appData.testLegality || !appData.oneClick) return;
9003         sscanf(message+6, "%c%d%c", &f, &y, &c);
9004         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9005         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9006         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9007         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9008         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9009         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9010             LeftClick(Release, lastLeftX, lastLeftY);
9011         controlKey  = (c == ',');
9012         LeftClick(Press, x, y);
9013         LeftClick(Release, x, y);
9014         first.highlight = f;
9015         return;
9016     }
9017     /*
9018      * If the move is illegal, cancel it and redraw the board.
9019      * Also deal with other error cases.  Matching is rather loose
9020      * here to accommodate engines written before the spec.
9021      */
9022     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9023         strncmp(message, "Error", 5) == 0) {
9024         if (StrStr(message, "name") ||
9025             StrStr(message, "rating") || StrStr(message, "?") ||
9026             StrStr(message, "result") || StrStr(message, "board") ||
9027             StrStr(message, "bk") || StrStr(message, "computer") ||
9028             StrStr(message, "variant") || StrStr(message, "hint") ||
9029             StrStr(message, "random") || StrStr(message, "depth") ||
9030             StrStr(message, "accepted")) {
9031             return;
9032         }
9033         if (StrStr(message, "protover")) {
9034           /* Program is responding to input, so it's apparently done
9035              initializing, and this error message indicates it is
9036              protocol version 1.  So we don't need to wait any longer
9037              for it to initialize and send feature commands. */
9038           FeatureDone(cps, 1);
9039           cps->protocolVersion = 1;
9040           return;
9041         }
9042         cps->maybeThinking = FALSE;
9043
9044         if (StrStr(message, "draw")) {
9045             /* Program doesn't have "draw" command */
9046             cps->sendDrawOffers = 0;
9047             return;
9048         }
9049         if (cps->sendTime != 1 &&
9050             (StrStr(message, "time") || StrStr(message, "otim"))) {
9051           /* Program apparently doesn't have "time" or "otim" command */
9052           cps->sendTime = 0;
9053           return;
9054         }
9055         if (StrStr(message, "analyze")) {
9056             cps->analysisSupport = FALSE;
9057             cps->analyzing = FALSE;
9058 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9059             EditGameEvent(); // [HGM] try to preserve loaded game
9060             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9061             DisplayError(buf2, 0);
9062             return;
9063         }
9064         if (StrStr(message, "(no matching move)st")) {
9065           /* Special kludge for GNU Chess 4 only */
9066           cps->stKludge = TRUE;
9067           SendTimeControl(cps, movesPerSession, timeControl,
9068                           timeIncrement, appData.searchDepth,
9069                           searchTime);
9070           return;
9071         }
9072         if (StrStr(message, "(no matching move)sd")) {
9073           /* Special kludge for GNU Chess 4 only */
9074           cps->sdKludge = TRUE;
9075           SendTimeControl(cps, movesPerSession, timeControl,
9076                           timeIncrement, appData.searchDepth,
9077                           searchTime);
9078           return;
9079         }
9080         if (!StrStr(message, "llegal")) {
9081             return;
9082         }
9083         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9084             gameMode == IcsIdle) return;
9085         if (forwardMostMove <= backwardMostMove) return;
9086         if (pausing) PauseEvent();
9087       if(appData.forceIllegal) {
9088             // [HGM] illegal: machine refused move; force position after move into it
9089           SendToProgram("force\n", cps);
9090           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9091                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9092                 // when black is to move, while there might be nothing on a2 or black
9093                 // might already have the move. So send the board as if white has the move.
9094                 // But first we must change the stm of the engine, as it refused the last move
9095                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9096                 if(WhiteOnMove(forwardMostMove)) {
9097                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9098                     SendBoard(cps, forwardMostMove); // kludgeless board
9099                 } else {
9100                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9101                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9102                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9103                 }
9104           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9105             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9106                  gameMode == TwoMachinesPlay)
9107               SendToProgram("go\n", cps);
9108             return;
9109       } else
9110         if (gameMode == PlayFromGameFile) {
9111             /* Stop reading this game file */
9112             gameMode = EditGame;
9113             ModeHighlight();
9114         }
9115         /* [HGM] illegal-move claim should forfeit game when Xboard */
9116         /* only passes fully legal moves                            */
9117         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9118             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9119                                 "False illegal-move claim", GE_XBOARD );
9120             return; // do not take back move we tested as valid
9121         }
9122         currentMove = forwardMostMove-1;
9123         DisplayMove(currentMove-1); /* before DisplayMoveError */
9124         SwitchClocks(forwardMostMove-1); // [HGM] race
9125         DisplayBothClocks();
9126         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9127                 parseList[currentMove], _(cps->which));
9128         DisplayMoveError(buf1);
9129         DrawPosition(FALSE, boards[currentMove]);
9130
9131         SetUserThinkingEnables();
9132         return;
9133     }
9134     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9135         /* Program has a broken "time" command that
9136            outputs a string not ending in newline.
9137            Don't use it. */
9138         cps->sendTime = 0;
9139     }
9140     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9141         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9142             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9143     }
9144
9145     /*
9146      * If chess program startup fails, exit with an error message.
9147      * Attempts to recover here are futile. [HGM] Well, we try anyway
9148      */
9149     if ((StrStr(message, "unknown host") != NULL)
9150         || (StrStr(message, "No remote directory") != NULL)
9151         || (StrStr(message, "not found") != NULL)
9152         || (StrStr(message, "No such file") != NULL)
9153         || (StrStr(message, "can't alloc") != NULL)
9154         || (StrStr(message, "Permission denied") != NULL)) {
9155
9156         cps->maybeThinking = FALSE;
9157         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9158                 _(cps->which), cps->program, cps->host, message);
9159         RemoveInputSource(cps->isr);
9160         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9161             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9162             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9163         }
9164         return;
9165     }
9166
9167     /*
9168      * Look for hint output
9169      */
9170     if (sscanf(message, "Hint: %s", buf1) == 1) {
9171         if (cps == &first && hintRequested) {
9172             hintRequested = FALSE;
9173             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9174                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9175                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9176                                     PosFlags(forwardMostMove),
9177                                     fromY, fromX, toY, toX, promoChar, buf1);
9178                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9179                 DisplayInformation(buf2);
9180             } else {
9181                 /* Hint move could not be parsed!? */
9182               snprintf(buf2, sizeof(buf2),
9183                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9184                         buf1, _(cps->which));
9185                 DisplayError(buf2, 0);
9186             }
9187         } else {
9188           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9189         }
9190         return;
9191     }
9192
9193     /*
9194      * Ignore other messages if game is not in progress
9195      */
9196     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9197         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9198
9199     /*
9200      * look for win, lose, draw, or draw offer
9201      */
9202     if (strncmp(message, "1-0", 3) == 0) {
9203         char *p, *q, *r = "";
9204         p = strchr(message, '{');
9205         if (p) {
9206             q = strchr(p, '}');
9207             if (q) {
9208                 *q = NULLCHAR;
9209                 r = p + 1;
9210             }
9211         }
9212         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9213         return;
9214     } else if (strncmp(message, "0-1", 3) == 0) {
9215         char *p, *q, *r = "";
9216         p = strchr(message, '{');
9217         if (p) {
9218             q = strchr(p, '}');
9219             if (q) {
9220                 *q = NULLCHAR;
9221                 r = p + 1;
9222             }
9223         }
9224         /* Kludge for Arasan 4.1 bug */
9225         if (strcmp(r, "Black resigns") == 0) {
9226             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9227             return;
9228         }
9229         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9230         return;
9231     } else if (strncmp(message, "1/2", 3) == 0) {
9232         char *p, *q, *r = "";
9233         p = strchr(message, '{');
9234         if (p) {
9235             q = strchr(p, '}');
9236             if (q) {
9237                 *q = NULLCHAR;
9238                 r = p + 1;
9239             }
9240         }
9241
9242         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9243         return;
9244
9245     } else if (strncmp(message, "White resign", 12) == 0) {
9246         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9247         return;
9248     } else if (strncmp(message, "Black resign", 12) == 0) {
9249         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9250         return;
9251     } else if (strncmp(message, "White matches", 13) == 0 ||
9252                strncmp(message, "Black matches", 13) == 0   ) {
9253         /* [HGM] ignore GNUShogi noises */
9254         return;
9255     } else if (strncmp(message, "White", 5) == 0 &&
9256                message[5] != '(' &&
9257                StrStr(message, "Black") == NULL) {
9258         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9259         return;
9260     } else if (strncmp(message, "Black", 5) == 0 &&
9261                message[5] != '(') {
9262         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9263         return;
9264     } else if (strcmp(message, "resign") == 0 ||
9265                strcmp(message, "computer resigns") == 0) {
9266         switch (gameMode) {
9267           case MachinePlaysBlack:
9268           case IcsPlayingBlack:
9269             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9270             break;
9271           case MachinePlaysWhite:
9272           case IcsPlayingWhite:
9273             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9274             break;
9275           case TwoMachinesPlay:
9276             if (cps->twoMachinesColor[0] == 'w')
9277               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9278             else
9279               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9280             break;
9281           default:
9282             /* can't happen */
9283             break;
9284         }
9285         return;
9286     } else if (strncmp(message, "opponent mates", 14) == 0) {
9287         switch (gameMode) {
9288           case MachinePlaysBlack:
9289           case IcsPlayingBlack:
9290             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9291             break;
9292           case MachinePlaysWhite:
9293           case IcsPlayingWhite:
9294             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9295             break;
9296           case TwoMachinesPlay:
9297             if (cps->twoMachinesColor[0] == 'w')
9298               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9299             else
9300               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9301             break;
9302           default:
9303             /* can't happen */
9304             break;
9305         }
9306         return;
9307     } else if (strncmp(message, "computer mates", 14) == 0) {
9308         switch (gameMode) {
9309           case MachinePlaysBlack:
9310           case IcsPlayingBlack:
9311             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9312             break;
9313           case MachinePlaysWhite:
9314           case IcsPlayingWhite:
9315             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9316             break;
9317           case TwoMachinesPlay:
9318             if (cps->twoMachinesColor[0] == 'w')
9319               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9320             else
9321               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9322             break;
9323           default:
9324             /* can't happen */
9325             break;
9326         }
9327         return;
9328     } else if (strncmp(message, "checkmate", 9) == 0) {
9329         if (WhiteOnMove(forwardMostMove)) {
9330             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9331         } else {
9332             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9333         }
9334         return;
9335     } else if (strstr(message, "Draw") != NULL ||
9336                strstr(message, "game is a draw") != NULL) {
9337         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9338         return;
9339     } else if (strstr(message, "offer") != NULL &&
9340                strstr(message, "draw") != NULL) {
9341 #if ZIPPY
9342         if (appData.zippyPlay && first.initDone) {
9343             /* Relay offer to ICS */
9344             SendToICS(ics_prefix);
9345             SendToICS("draw\n");
9346         }
9347 #endif
9348         cps->offeredDraw = 2; /* valid until this engine moves twice */
9349         if (gameMode == TwoMachinesPlay) {
9350             if (cps->other->offeredDraw) {
9351                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9352             /* [HGM] in two-machine mode we delay relaying draw offer      */
9353             /* until after we also have move, to see if it is really claim */
9354             }
9355         } else if (gameMode == MachinePlaysWhite ||
9356                    gameMode == MachinePlaysBlack) {
9357           if (userOfferedDraw) {
9358             DisplayInformation(_("Machine accepts your draw offer"));
9359             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9360           } else {
9361             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9362           }
9363         }
9364     }
9365
9366
9367     /*
9368      * Look for thinking output
9369      */
9370     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9371           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9372                                 ) {
9373         int plylev, mvleft, mvtot, curscore, time;
9374         char mvname[MOVE_LEN];
9375         u64 nodes; // [DM]
9376         char plyext;
9377         int ignore = FALSE;
9378         int prefixHint = FALSE;
9379         mvname[0] = NULLCHAR;
9380
9381         switch (gameMode) {
9382           case MachinePlaysBlack:
9383           case IcsPlayingBlack:
9384             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9385             break;
9386           case MachinePlaysWhite:
9387           case IcsPlayingWhite:
9388             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9389             break;
9390           case AnalyzeMode:
9391           case AnalyzeFile:
9392             break;
9393           case IcsObserving: /* [DM] icsEngineAnalyze */
9394             if (!appData.icsEngineAnalyze) ignore = TRUE;
9395             break;
9396           case TwoMachinesPlay:
9397             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9398                 ignore = TRUE;
9399             }
9400             break;
9401           default:
9402             ignore = TRUE;
9403             break;
9404         }
9405
9406         if (!ignore) {
9407             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9408             buf1[0] = NULLCHAR;
9409             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9410                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9411
9412                 if (plyext != ' ' && plyext != '\t') {
9413                     time *= 100;
9414                 }
9415
9416                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9417                 if( cps->scoreIsAbsolute &&
9418                     ( gameMode == MachinePlaysBlack ||
9419                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9420                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9421                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9422                      !WhiteOnMove(currentMove)
9423                     ) )
9424                 {
9425                     curscore = -curscore;
9426                 }
9427
9428                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9429
9430                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9431                         char buf[MSG_SIZ];
9432                         FILE *f;
9433                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9434                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9435                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9436                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9437                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9438                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9439                                 fclose(f);
9440                         }
9441                         else
9442                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9443                           DisplayError(_("failed writing PV"), 0);
9444                 }
9445
9446                 tempStats.depth = plylev;
9447                 tempStats.nodes = nodes;
9448                 tempStats.time = time;
9449                 tempStats.score = curscore;
9450                 tempStats.got_only_move = 0;
9451
9452                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9453                         int ticklen;
9454
9455                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9456                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9457                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9458                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9459                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9460                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9461                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9462                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9463                 }
9464
9465                 /* Buffer overflow protection */
9466                 if (pv[0] != NULLCHAR) {
9467                     if (strlen(pv) >= sizeof(tempStats.movelist)
9468                         && appData.debugMode) {
9469                         fprintf(debugFP,
9470                                 "PV is too long; using the first %u bytes.\n",
9471                                 (unsigned) sizeof(tempStats.movelist) - 1);
9472                     }
9473
9474                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9475                 } else {
9476                     sprintf(tempStats.movelist, " no PV\n");
9477                 }
9478
9479                 if (tempStats.seen_stat) {
9480                     tempStats.ok_to_send = 1;
9481                 }
9482
9483                 if (strchr(tempStats.movelist, '(') != NULL) {
9484                     tempStats.line_is_book = 1;
9485                     tempStats.nr_moves = 0;
9486                     tempStats.moves_left = 0;
9487                 } else {
9488                     tempStats.line_is_book = 0;
9489                 }
9490
9491                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9492                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9493
9494                 SendProgramStatsToFrontend( cps, &tempStats );
9495
9496                 /*
9497                     [AS] Protect the thinkOutput buffer from overflow... this
9498                     is only useful if buf1 hasn't overflowed first!
9499                 */
9500                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9501                          plylev,
9502                          (gameMode == TwoMachinesPlay ?
9503                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9504                          ((double) curscore) / 100.0,
9505                          prefixHint ? lastHint : "",
9506                          prefixHint ? " " : "" );
9507
9508                 if( buf1[0] != NULLCHAR ) {
9509                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9510
9511                     if( strlen(pv) > max_len ) {
9512                         if( appData.debugMode) {
9513                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9514                         }
9515                         pv[max_len+1] = '\0';
9516                     }
9517
9518                     strcat( thinkOutput, pv);
9519                 }
9520
9521                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9522                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9523                     DisplayMove(currentMove - 1);
9524                 }
9525                 return;
9526
9527             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9528                 /* crafty (9.25+) says "(only move) <move>"
9529                  * if there is only 1 legal move
9530                  */
9531                 sscanf(p, "(only move) %s", buf1);
9532                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9533                 sprintf(programStats.movelist, "%s (only move)", buf1);
9534                 programStats.depth = 1;
9535                 programStats.nr_moves = 1;
9536                 programStats.moves_left = 1;
9537                 programStats.nodes = 1;
9538                 programStats.time = 1;
9539                 programStats.got_only_move = 1;
9540
9541                 /* Not really, but we also use this member to
9542                    mean "line isn't going to change" (Crafty
9543                    isn't searching, so stats won't change) */
9544                 programStats.line_is_book = 1;
9545
9546                 SendProgramStatsToFrontend( cps, &programStats );
9547
9548                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9549                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9550                     DisplayMove(currentMove - 1);
9551                 }
9552                 return;
9553             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9554                               &time, &nodes, &plylev, &mvleft,
9555                               &mvtot, mvname) >= 5) {
9556                 /* The stat01: line is from Crafty (9.29+) in response
9557                    to the "." command */
9558                 programStats.seen_stat = 1;
9559                 cps->maybeThinking = TRUE;
9560
9561                 if (programStats.got_only_move || !appData.periodicUpdates)
9562                   return;
9563
9564                 programStats.depth = plylev;
9565                 programStats.time = time;
9566                 programStats.nodes = nodes;
9567                 programStats.moves_left = mvleft;
9568                 programStats.nr_moves = mvtot;
9569                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9570                 programStats.ok_to_send = 1;
9571                 programStats.movelist[0] = '\0';
9572
9573                 SendProgramStatsToFrontend( cps, &programStats );
9574
9575                 return;
9576
9577             } else if (strncmp(message,"++",2) == 0) {
9578                 /* Crafty 9.29+ outputs this */
9579                 programStats.got_fail = 2;
9580                 return;
9581
9582             } else if (strncmp(message,"--",2) == 0) {
9583                 /* Crafty 9.29+ outputs this */
9584                 programStats.got_fail = 1;
9585                 return;
9586
9587             } else if (thinkOutput[0] != NULLCHAR &&
9588                        strncmp(message, "    ", 4) == 0) {
9589                 unsigned message_len;
9590
9591                 p = message;
9592                 while (*p && *p == ' ') p++;
9593
9594                 message_len = strlen( p );
9595
9596                 /* [AS] Avoid buffer overflow */
9597                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9598                     strcat(thinkOutput, " ");
9599                     strcat(thinkOutput, p);
9600                 }
9601
9602                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9603                     strcat(programStats.movelist, " ");
9604                     strcat(programStats.movelist, p);
9605                 }
9606
9607                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9608                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9609                     DisplayMove(currentMove - 1);
9610                 }
9611                 return;
9612             }
9613         }
9614         else {
9615             buf1[0] = NULLCHAR;
9616
9617             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9618                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9619             {
9620                 ChessProgramStats cpstats;
9621
9622                 if (plyext != ' ' && plyext != '\t') {
9623                     time *= 100;
9624                 }
9625
9626                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9627                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9628                     curscore = -curscore;
9629                 }
9630
9631                 cpstats.depth = plylev;
9632                 cpstats.nodes = nodes;
9633                 cpstats.time = time;
9634                 cpstats.score = curscore;
9635                 cpstats.got_only_move = 0;
9636                 cpstats.movelist[0] = '\0';
9637
9638                 if (buf1[0] != NULLCHAR) {
9639                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9640                 }
9641
9642                 cpstats.ok_to_send = 0;
9643                 cpstats.line_is_book = 0;
9644                 cpstats.nr_moves = 0;
9645                 cpstats.moves_left = 0;
9646
9647                 SendProgramStatsToFrontend( cps, &cpstats );
9648             }
9649         }
9650     }
9651 }
9652
9653
9654 /* Parse a game score from the character string "game", and
9655    record it as the history of the current game.  The game
9656    score is NOT assumed to start from the standard position.
9657    The display is not updated in any way.
9658    */
9659 void
9660 ParseGameHistory (char *game)
9661 {
9662     ChessMove moveType;
9663     int fromX, fromY, toX, toY, boardIndex;
9664     char promoChar;
9665     char *p, *q;
9666     char buf[MSG_SIZ];
9667
9668     if (appData.debugMode)
9669       fprintf(debugFP, "Parsing game history: %s\n", game);
9670
9671     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9672     gameInfo.site = StrSave(appData.icsHost);
9673     gameInfo.date = PGNDate();
9674     gameInfo.round = StrSave("-");
9675
9676     /* Parse out names of players */
9677     while (*game == ' ') game++;
9678     p = buf;
9679     while (*game != ' ') *p++ = *game++;
9680     *p = NULLCHAR;
9681     gameInfo.white = StrSave(buf);
9682     while (*game == ' ') game++;
9683     p = buf;
9684     while (*game != ' ' && *game != '\n') *p++ = *game++;
9685     *p = NULLCHAR;
9686     gameInfo.black = StrSave(buf);
9687
9688     /* Parse moves */
9689     boardIndex = blackPlaysFirst ? 1 : 0;
9690     yynewstr(game);
9691     for (;;) {
9692         yyboardindex = boardIndex;
9693         moveType = (ChessMove) Myylex();
9694         switch (moveType) {
9695           case IllegalMove:             /* maybe suicide chess, etc. */
9696   if (appData.debugMode) {
9697     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9698     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9699     setbuf(debugFP, NULL);
9700   }
9701           case WhitePromotion:
9702           case BlackPromotion:
9703           case WhiteNonPromotion:
9704           case BlackNonPromotion:
9705           case NormalMove:
9706           case FirstLeg:
9707           case WhiteCapturesEnPassant:
9708           case BlackCapturesEnPassant:
9709           case WhiteKingSideCastle:
9710           case WhiteQueenSideCastle:
9711           case BlackKingSideCastle:
9712           case BlackQueenSideCastle:
9713           case WhiteKingSideCastleWild:
9714           case WhiteQueenSideCastleWild:
9715           case BlackKingSideCastleWild:
9716           case BlackQueenSideCastleWild:
9717           /* PUSH Fabien */
9718           case WhiteHSideCastleFR:
9719           case WhiteASideCastleFR:
9720           case BlackHSideCastleFR:
9721           case BlackASideCastleFR:
9722           /* POP Fabien */
9723             fromX = currentMoveString[0] - AAA;
9724             fromY = currentMoveString[1] - ONE;
9725             toX = currentMoveString[2] - AAA;
9726             toY = currentMoveString[3] - ONE;
9727             promoChar = currentMoveString[4];
9728             break;
9729           case WhiteDrop:
9730           case BlackDrop:
9731             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9732             fromX = moveType == WhiteDrop ?
9733               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9734             (int) CharToPiece(ToLower(currentMoveString[0]));
9735             fromY = DROP_RANK;
9736             toX = currentMoveString[2] - AAA;
9737             toY = currentMoveString[3] - ONE;
9738             promoChar = NULLCHAR;
9739             break;
9740           case AmbiguousMove:
9741             /* bug? */
9742             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9743   if (appData.debugMode) {
9744     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9745     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9746     setbuf(debugFP, NULL);
9747   }
9748             DisplayError(buf, 0);
9749             return;
9750           case ImpossibleMove:
9751             /* bug? */
9752             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9753   if (appData.debugMode) {
9754     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9755     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9756     setbuf(debugFP, NULL);
9757   }
9758             DisplayError(buf, 0);
9759             return;
9760           case EndOfFile:
9761             if (boardIndex < backwardMostMove) {
9762                 /* Oops, gap.  How did that happen? */
9763                 DisplayError(_("Gap in move list"), 0);
9764                 return;
9765             }
9766             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9767             if (boardIndex > forwardMostMove) {
9768                 forwardMostMove = boardIndex;
9769             }
9770             return;
9771           case ElapsedTime:
9772             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9773                 strcat(parseList[boardIndex-1], " ");
9774                 strcat(parseList[boardIndex-1], yy_text);
9775             }
9776             continue;
9777           case Comment:
9778           case PGNTag:
9779           case NAG:
9780           default:
9781             /* ignore */
9782             continue;
9783           case WhiteWins:
9784           case BlackWins:
9785           case GameIsDrawn:
9786           case GameUnfinished:
9787             if (gameMode == IcsExamining) {
9788                 if (boardIndex < backwardMostMove) {
9789                     /* Oops, gap.  How did that happen? */
9790                     return;
9791                 }
9792                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9793                 return;
9794             }
9795             gameInfo.result = moveType;
9796             p = strchr(yy_text, '{');
9797             if (p == NULL) p = strchr(yy_text, '(');
9798             if (p == NULL) {
9799                 p = yy_text;
9800                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9801             } else {
9802                 q = strchr(p, *p == '{' ? '}' : ')');
9803                 if (q != NULL) *q = NULLCHAR;
9804                 p++;
9805             }
9806             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9807             gameInfo.resultDetails = StrSave(p);
9808             continue;
9809         }
9810         if (boardIndex >= forwardMostMove &&
9811             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9812             backwardMostMove = blackPlaysFirst ? 1 : 0;
9813             return;
9814         }
9815         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9816                                  fromY, fromX, toY, toX, promoChar,
9817                                  parseList[boardIndex]);
9818         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9819         /* currentMoveString is set as a side-effect of yylex */
9820         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9821         strcat(moveList[boardIndex], "\n");
9822         boardIndex++;
9823         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9824         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9825           case MT_NONE:
9826           case MT_STALEMATE:
9827           default:
9828             break;
9829           case MT_CHECK:
9830             if(!IS_SHOGI(gameInfo.variant))
9831                 strcat(parseList[boardIndex - 1], "+");
9832             break;
9833           case MT_CHECKMATE:
9834           case MT_STAINMATE:
9835             strcat(parseList[boardIndex - 1], "#");
9836             break;
9837         }
9838     }
9839 }
9840
9841
9842 /* Apply a move to the given board  */
9843 void
9844 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9845 {
9846   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9847   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9848
9849     /* [HGM] compute & store e.p. status and castling rights for new position */
9850     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9851
9852       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9853       oldEP = (signed char)board[EP_STATUS];
9854       board[EP_STATUS] = EP_NONE;
9855
9856   if (fromY == DROP_RANK) {
9857         /* must be first */
9858         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9859             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9860             return;
9861         }
9862         piece = board[toY][toX] = (ChessSquare) fromX;
9863   } else {
9864 //      ChessSquare victim;
9865       int i;
9866
9867       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9868 //           victim = board[killY][killX],
9869            board[killY][killX] = EmptySquare,
9870            board[EP_STATUS] = EP_CAPTURE;
9871
9872       if( board[toY][toX] != EmptySquare ) {
9873            board[EP_STATUS] = EP_CAPTURE;
9874            if( (fromX != toX || fromY != toY) && // not igui!
9875                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9876                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9877                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9878            }
9879       }
9880
9881       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9882            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9883                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9884       } else
9885       if( board[fromY][fromX] == WhitePawn ) {
9886            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9887                board[EP_STATUS] = EP_PAWN_MOVE;
9888            if( toY-fromY==2) {
9889                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9890                         gameInfo.variant != VariantBerolina || toX < fromX)
9891                       board[EP_STATUS] = toX | berolina;
9892                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9893                         gameInfo.variant != VariantBerolina || toX > fromX)
9894                       board[EP_STATUS] = toX;
9895            }
9896       } else
9897       if( board[fromY][fromX] == BlackPawn ) {
9898            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9899                board[EP_STATUS] = EP_PAWN_MOVE;
9900            if( toY-fromY== -2) {
9901                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9902                         gameInfo.variant != VariantBerolina || toX < fromX)
9903                       board[EP_STATUS] = toX | berolina;
9904                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9905                         gameInfo.variant != VariantBerolina || toX > fromX)
9906                       board[EP_STATUS] = toX;
9907            }
9908        }
9909
9910        for(i=0; i<nrCastlingRights; i++) {
9911            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9912               board[CASTLING][i] == toX   && castlingRank[i] == toY
9913              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9914        }
9915
9916        if(gameInfo.variant == VariantSChess) { // update virginity
9917            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9918            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9919            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9920            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9921        }
9922
9923      if (fromX == toX && fromY == toY) return;
9924
9925      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9926      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9927      if(gameInfo.variant == VariantKnightmate)
9928          king += (int) WhiteUnicorn - (int) WhiteKing;
9929
9930     /* Code added by Tord: */
9931     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9932     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9933         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9934       board[fromY][fromX] = EmptySquare;
9935       board[toY][toX] = EmptySquare;
9936       if((toX > fromX) != (piece == WhiteRook)) {
9937         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9938       } else {
9939         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9940       }
9941     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9942                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9943       board[fromY][fromX] = EmptySquare;
9944       board[toY][toX] = EmptySquare;
9945       if((toX > fromX) != (piece == BlackRook)) {
9946         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9947       } else {
9948         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9949       }
9950     /* End of code added by Tord */
9951
9952     } else if (board[fromY][fromX] == king
9953         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9954         && toY == fromY && toX > fromX+1) {
9955         board[fromY][fromX] = EmptySquare;
9956         board[toY][toX] = king;
9957         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9958         board[fromY][BOARD_RGHT-1] = EmptySquare;
9959     } else if (board[fromY][fromX] == king
9960         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9961                && toY == fromY && toX < fromX-1) {
9962         board[fromY][fromX] = EmptySquare;
9963         board[toY][toX] = king;
9964         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9965         board[fromY][BOARD_LEFT] = EmptySquare;
9966     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9967                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9968                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9969                ) {
9970         /* white pawn promotion */
9971         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9972         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9973             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9974         board[fromY][fromX] = EmptySquare;
9975     } else if ((fromY >= BOARD_HEIGHT>>1)
9976                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9977                && (toX != fromX)
9978                && gameInfo.variant != VariantXiangqi
9979                && gameInfo.variant != VariantBerolina
9980                && (board[fromY][fromX] == WhitePawn)
9981                && (board[toY][toX] == EmptySquare)) {
9982         board[fromY][fromX] = EmptySquare;
9983         board[toY][toX] = WhitePawn;
9984         captured = board[toY - 1][toX];
9985         board[toY - 1][toX] = EmptySquare;
9986     } else if ((fromY == BOARD_HEIGHT-4)
9987                && (toX == fromX)
9988                && gameInfo.variant == VariantBerolina
9989                && (board[fromY][fromX] == WhitePawn)
9990                && (board[toY][toX] == EmptySquare)) {
9991         board[fromY][fromX] = EmptySquare;
9992         board[toY][toX] = WhitePawn;
9993         if(oldEP & EP_BEROLIN_A) {
9994                 captured = board[fromY][fromX-1];
9995                 board[fromY][fromX-1] = EmptySquare;
9996         }else{  captured = board[fromY][fromX+1];
9997                 board[fromY][fromX+1] = EmptySquare;
9998         }
9999     } else if (board[fromY][fromX] == king
10000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10001                && toY == fromY && toX > fromX+1) {
10002         board[fromY][fromX] = EmptySquare;
10003         board[toY][toX] = king;
10004         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10005         board[fromY][BOARD_RGHT-1] = EmptySquare;
10006     } else if (board[fromY][fromX] == king
10007         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10008                && toY == fromY && toX < fromX-1) {
10009         board[fromY][fromX] = EmptySquare;
10010         board[toY][toX] = king;
10011         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10012         board[fromY][BOARD_LEFT] = EmptySquare;
10013     } else if (fromY == 7 && fromX == 3
10014                && board[fromY][fromX] == BlackKing
10015                && toY == 7 && toX == 5) {
10016         board[fromY][fromX] = EmptySquare;
10017         board[toY][toX] = BlackKing;
10018         board[fromY][7] = EmptySquare;
10019         board[toY][4] = BlackRook;
10020     } else if (fromY == 7 && fromX == 3
10021                && board[fromY][fromX] == BlackKing
10022                && toY == 7 && toX == 1) {
10023         board[fromY][fromX] = EmptySquare;
10024         board[toY][toX] = BlackKing;
10025         board[fromY][0] = EmptySquare;
10026         board[toY][2] = BlackRook;
10027     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10028                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10029                && toY < promoRank && promoChar
10030                ) {
10031         /* black pawn promotion */
10032         board[toY][toX] = CharToPiece(ToLower(promoChar));
10033         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10034             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10035         board[fromY][fromX] = EmptySquare;
10036     } else if ((fromY < BOARD_HEIGHT>>1)
10037                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10038                && (toX != fromX)
10039                && gameInfo.variant != VariantXiangqi
10040                && gameInfo.variant != VariantBerolina
10041                && (board[fromY][fromX] == BlackPawn)
10042                && (board[toY][toX] == EmptySquare)) {
10043         board[fromY][fromX] = EmptySquare;
10044         board[toY][toX] = BlackPawn;
10045         captured = board[toY + 1][toX];
10046         board[toY + 1][toX] = EmptySquare;
10047     } else if ((fromY == 3)
10048                && (toX == fromX)
10049                && gameInfo.variant == VariantBerolina
10050                && (board[fromY][fromX] == BlackPawn)
10051                && (board[toY][toX] == EmptySquare)) {
10052         board[fromY][fromX] = EmptySquare;
10053         board[toY][toX] = BlackPawn;
10054         if(oldEP & EP_BEROLIN_A) {
10055                 captured = board[fromY][fromX-1];
10056                 board[fromY][fromX-1] = EmptySquare;
10057         }else{  captured = board[fromY][fromX+1];
10058                 board[fromY][fromX+1] = EmptySquare;
10059         }
10060     } else {
10061         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10062         board[fromY][fromX] = EmptySquare;
10063         board[toY][toX] = piece;
10064     }
10065   }
10066
10067     if (gameInfo.holdingsWidth != 0) {
10068
10069       /* !!A lot more code needs to be written to support holdings  */
10070       /* [HGM] OK, so I have written it. Holdings are stored in the */
10071       /* penultimate board files, so they are automaticlly stored   */
10072       /* in the game history.                                       */
10073       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10074                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10075         /* Delete from holdings, by decreasing count */
10076         /* and erasing image if necessary            */
10077         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10078         if(p < (int) BlackPawn) { /* white drop */
10079              p -= (int)WhitePawn;
10080                  p = PieceToNumber((ChessSquare)p);
10081              if(p >= gameInfo.holdingsSize) p = 0;
10082              if(--board[p][BOARD_WIDTH-2] <= 0)
10083                   board[p][BOARD_WIDTH-1] = EmptySquare;
10084              if((int)board[p][BOARD_WIDTH-2] < 0)
10085                         board[p][BOARD_WIDTH-2] = 0;
10086         } else {                  /* black drop */
10087              p -= (int)BlackPawn;
10088                  p = PieceToNumber((ChessSquare)p);
10089              if(p >= gameInfo.holdingsSize) p = 0;
10090              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10091                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10092              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10093                         board[BOARD_HEIGHT-1-p][1] = 0;
10094         }
10095       }
10096       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10097           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10098         /* [HGM] holdings: Add to holdings, if holdings exist */
10099         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10100                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10101                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10102         }
10103         p = (int) captured;
10104         if (p >= (int) BlackPawn) {
10105           p -= (int)BlackPawn;
10106           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10107                   /* in Shogi restore piece to its original  first */
10108                   captured = (ChessSquare) (DEMOTED captured);
10109                   p = DEMOTED p;
10110           }
10111           p = PieceToNumber((ChessSquare)p);
10112           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10113           board[p][BOARD_WIDTH-2]++;
10114           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10115         } else {
10116           p -= (int)WhitePawn;
10117           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10118                   captured = (ChessSquare) (DEMOTED captured);
10119                   p = DEMOTED p;
10120           }
10121           p = PieceToNumber((ChessSquare)p);
10122           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10123           board[BOARD_HEIGHT-1-p][1]++;
10124           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10125         }
10126       }
10127     } else if (gameInfo.variant == VariantAtomic) {
10128       if (captured != EmptySquare) {
10129         int y, x;
10130         for (y = toY-1; y <= toY+1; y++) {
10131           for (x = toX-1; x <= toX+1; x++) {
10132             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10133                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10134               board[y][x] = EmptySquare;
10135             }
10136           }
10137         }
10138         board[toY][toX] = EmptySquare;
10139       }
10140     }
10141
10142     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10143         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10144     } else
10145     if(promoChar == '+') {
10146         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10147         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10148         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10149           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10150     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10151         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10152         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10153            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10154         board[toY][toX] = newPiece;
10155     }
10156     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10157                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10158         // [HGM] superchess: take promotion piece out of holdings
10159         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10160         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10161             if(!--board[k][BOARD_WIDTH-2])
10162                 board[k][BOARD_WIDTH-1] = EmptySquare;
10163         } else {
10164             if(!--board[BOARD_HEIGHT-1-k][1])
10165                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10166         }
10167     }
10168 }
10169
10170 /* Updates forwardMostMove */
10171 void
10172 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10173 {
10174     int x = toX, y = toY;
10175     char *s = parseList[forwardMostMove];
10176     ChessSquare p = boards[forwardMostMove][toY][toX];
10177 //    forwardMostMove++; // [HGM] bare: moved downstream
10178
10179     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10180     (void) CoordsToAlgebraic(boards[forwardMostMove],
10181                              PosFlags(forwardMostMove),
10182                              fromY, fromX, y, x, promoChar,
10183                              s);
10184     if(killX >= 0 && killY >= 0)
10185         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10186
10187     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10188         int timeLeft; static int lastLoadFlag=0; int king, piece;
10189         piece = boards[forwardMostMove][fromY][fromX];
10190         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10191         if(gameInfo.variant == VariantKnightmate)
10192             king += (int) WhiteUnicorn - (int) WhiteKing;
10193         if(forwardMostMove == 0) {
10194             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10195                 fprintf(serverMoves, "%s;", UserName());
10196             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10197                 fprintf(serverMoves, "%s;", second.tidy);
10198             fprintf(serverMoves, "%s;", first.tidy);
10199             if(gameMode == MachinePlaysWhite)
10200                 fprintf(serverMoves, "%s;", UserName());
10201             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10202                 fprintf(serverMoves, "%s;", second.tidy);
10203         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10204         lastLoadFlag = loadFlag;
10205         // print base move
10206         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10207         // print castling suffix
10208         if( toY == fromY && piece == king ) {
10209             if(toX-fromX > 1)
10210                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10211             if(fromX-toX >1)
10212                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10213         }
10214         // e.p. suffix
10215         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10216              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10217              boards[forwardMostMove][toY][toX] == EmptySquare
10218              && fromX != toX && fromY != toY)
10219                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10220         // promotion suffix
10221         if(promoChar != NULLCHAR) {
10222             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10223                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10224                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10225             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10226         }
10227         if(!loadFlag) {
10228                 char buf[MOVE_LEN*2], *p; int len;
10229             fprintf(serverMoves, "/%d/%d",
10230                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10231             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10232             else                      timeLeft = blackTimeRemaining/1000;
10233             fprintf(serverMoves, "/%d", timeLeft);
10234                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10235                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10236                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10237                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10238             fprintf(serverMoves, "/%s", buf);
10239         }
10240         fflush(serverMoves);
10241     }
10242
10243     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10244         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10245       return;
10246     }
10247     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10248     if (commentList[forwardMostMove+1] != NULL) {
10249         free(commentList[forwardMostMove+1]);
10250         commentList[forwardMostMove+1] = NULL;
10251     }
10252     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10253     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10254     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10255     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10256     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10257     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10258     adjustedClock = FALSE;
10259     gameInfo.result = GameUnfinished;
10260     if (gameInfo.resultDetails != NULL) {
10261         free(gameInfo.resultDetails);
10262         gameInfo.resultDetails = NULL;
10263     }
10264     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10265                               moveList[forwardMostMove - 1]);
10266     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10267       case MT_NONE:
10268       case MT_STALEMATE:
10269       default:
10270         break;
10271       case MT_CHECK:
10272         if(!IS_SHOGI(gameInfo.variant))
10273             strcat(parseList[forwardMostMove - 1], "+");
10274         break;
10275       case MT_CHECKMATE:
10276       case MT_STAINMATE:
10277         strcat(parseList[forwardMostMove - 1], "#");
10278         break;
10279     }
10280 }
10281
10282 /* Updates currentMove if not pausing */
10283 void
10284 ShowMove (int fromX, int fromY, int toX, int toY)
10285 {
10286     int instant = (gameMode == PlayFromGameFile) ?
10287         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10288     if(appData.noGUI) return;
10289     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10290         if (!instant) {
10291             if (forwardMostMove == currentMove + 1) {
10292                 AnimateMove(boards[forwardMostMove - 1],
10293                             fromX, fromY, toX, toY);
10294             }
10295         }
10296         currentMove = forwardMostMove;
10297     }
10298
10299     killX = killY = -1; // [HGM] lion: used up
10300
10301     if (instant) return;
10302
10303     DisplayMove(currentMove - 1);
10304     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10305             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10306                 SetHighlights(fromX, fromY, toX, toY);
10307             }
10308     }
10309     DrawPosition(FALSE, boards[currentMove]);
10310     DisplayBothClocks();
10311     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10312 }
10313
10314 void
10315 SendEgtPath (ChessProgramState *cps)
10316 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10317         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10318
10319         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10320
10321         while(*p) {
10322             char c, *q = name+1, *r, *s;
10323
10324             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10325             while(*p && *p != ',') *q++ = *p++;
10326             *q++ = ':'; *q = 0;
10327             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10328                 strcmp(name, ",nalimov:") == 0 ) {
10329                 // take nalimov path from the menu-changeable option first, if it is defined
10330               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10331                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10332             } else
10333             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10334                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10335                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10336                 s = r = StrStr(s, ":") + 1; // beginning of path info
10337                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10338                 c = *r; *r = 0;             // temporarily null-terminate path info
10339                     *--q = 0;               // strip of trailig ':' from name
10340                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10341                 *r = c;
10342                 SendToProgram(buf,cps);     // send egtbpath command for this format
10343             }
10344             if(*p == ',') p++; // read away comma to position for next format name
10345         }
10346 }
10347
10348 static int
10349 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10350 {
10351       int width = 8, height = 8, holdings = 0;             // most common sizes
10352       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10353       // correct the deviations default for each variant
10354       if( v == VariantXiangqi ) width = 9,  height = 10;
10355       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10356       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10357       if( v == VariantCapablanca || v == VariantCapaRandom ||
10358           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10359                                 width = 10;
10360       if( v == VariantCourier ) width = 12;
10361       if( v == VariantSuper )                            holdings = 8;
10362       if( v == VariantGreat )   width = 10,              holdings = 8;
10363       if( v == VariantSChess )                           holdings = 7;
10364       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10365       if( v == VariantChuChess) width = 10, height = 10;
10366       if( v == VariantChu )     width = 12, height = 12;
10367       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10368              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10369              holdingsSize >= 0 && holdingsSize != holdings;
10370 }
10371
10372 char variantError[MSG_SIZ];
10373
10374 char *
10375 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10376 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10377       char *p, *variant = VariantName(v);
10378       static char b[MSG_SIZ];
10379       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10380            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10381                                                holdingsSize, variant); // cook up sized variant name
10382            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10383            if(StrStr(list, b) == NULL) {
10384                // specific sized variant not known, check if general sizing allowed
10385                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10386                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10387                             boardWidth, boardHeight, holdingsSize, engine);
10388                    return NULL;
10389                }
10390                /* [HGM] here we really should compare with the maximum supported board size */
10391            }
10392       } else snprintf(b, MSG_SIZ,"%s", variant);
10393       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10394       p = StrStr(list, b);
10395       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10396       if(p == NULL) {
10397           // occurs not at all in list, or only as sub-string
10398           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10399           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10400               int l = strlen(variantError);
10401               char *q;
10402               while(p != list && p[-1] != ',') p--;
10403               q = strchr(p, ',');
10404               if(q) *q = NULLCHAR;
10405               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10406               if(q) *q= ',';
10407           }
10408           return NULL;
10409       }
10410       return b;
10411 }
10412
10413 void
10414 InitChessProgram (ChessProgramState *cps, int setup)
10415 /* setup needed to setup FRC opening position */
10416 {
10417     char buf[MSG_SIZ], *b;
10418     if (appData.noChessProgram) return;
10419     hintRequested = FALSE;
10420     bookRequested = FALSE;
10421
10422     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10423     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10424     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10425     if(cps->memSize) { /* [HGM] memory */
10426       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10427         SendToProgram(buf, cps);
10428     }
10429     SendEgtPath(cps); /* [HGM] EGT */
10430     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10431       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10432         SendToProgram(buf, cps);
10433     }
10434
10435     setboardSpoiledMachineBlack = FALSE;
10436     SendToProgram(cps->initString, cps);
10437     if (gameInfo.variant != VariantNormal &&
10438         gameInfo.variant != VariantLoadable
10439         /* [HGM] also send variant if board size non-standard */
10440         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10441
10442       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10443                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10444       if (b == NULL) {
10445         DisplayFatalError(variantError, 0, 1);
10446         return;
10447       }
10448
10449       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10450       SendToProgram(buf, cps);
10451     }
10452     currentlyInitializedVariant = gameInfo.variant;
10453
10454     /* [HGM] send opening position in FRC to first engine */
10455     if(setup) {
10456           SendToProgram("force\n", cps);
10457           SendBoard(cps, 0);
10458           /* engine is now in force mode! Set flag to wake it up after first move. */
10459           setboardSpoiledMachineBlack = 1;
10460     }
10461
10462     if (cps->sendICS) {
10463       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10464       SendToProgram(buf, cps);
10465     }
10466     cps->maybeThinking = FALSE;
10467     cps->offeredDraw = 0;
10468     if (!appData.icsActive) {
10469         SendTimeControl(cps, movesPerSession, timeControl,
10470                         timeIncrement, appData.searchDepth,
10471                         searchTime);
10472     }
10473     if (appData.showThinking
10474         // [HGM] thinking: four options require thinking output to be sent
10475         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10476                                 ) {
10477         SendToProgram("post\n", cps);
10478     }
10479     SendToProgram("hard\n", cps);
10480     if (!appData.ponderNextMove) {
10481         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10482            it without being sure what state we are in first.  "hard"
10483            is not a toggle, so that one is OK.
10484          */
10485         SendToProgram("easy\n", cps);
10486     }
10487     if (cps->usePing) {
10488       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10489       SendToProgram(buf, cps);
10490     }
10491     cps->initDone = TRUE;
10492     ClearEngineOutputPane(cps == &second);
10493 }
10494
10495
10496 void
10497 ResendOptions (ChessProgramState *cps)
10498 { // send the stored value of the options
10499   int i;
10500   char buf[MSG_SIZ];
10501   Option *opt = cps->option;
10502   for(i=0; i<cps->nrOptions; i++, opt++) {
10503       switch(opt->type) {
10504         case Spin:
10505         case Slider:
10506         case CheckBox:
10507             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10508           break;
10509         case ComboBox:
10510           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10511           break;
10512         default:
10513             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10514           break;
10515         case Button:
10516         case SaveButton:
10517           continue;
10518       }
10519       SendToProgram(buf, cps);
10520   }
10521 }
10522
10523 void
10524 StartChessProgram (ChessProgramState *cps)
10525 {
10526     char buf[MSG_SIZ];
10527     int err;
10528
10529     if (appData.noChessProgram) return;
10530     cps->initDone = FALSE;
10531
10532     if (strcmp(cps->host, "localhost") == 0) {
10533         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10534     } else if (*appData.remoteShell == NULLCHAR) {
10535         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10536     } else {
10537         if (*appData.remoteUser == NULLCHAR) {
10538           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10539                     cps->program);
10540         } else {
10541           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10542                     cps->host, appData.remoteUser, cps->program);
10543         }
10544         err = StartChildProcess(buf, "", &cps->pr);
10545     }
10546
10547     if (err != 0) {
10548       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10549         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10550         if(cps != &first) return;
10551         appData.noChessProgram = TRUE;
10552         ThawUI();
10553         SetNCPMode();
10554 //      DisplayFatalError(buf, err, 1);
10555 //      cps->pr = NoProc;
10556 //      cps->isr = NULL;
10557         return;
10558     }
10559
10560     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10561     if (cps->protocolVersion > 1) {
10562       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10563       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10564         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10565         cps->comboCnt = 0;  //                and values of combo boxes
10566       }
10567       SendToProgram(buf, cps);
10568       if(cps->reload) ResendOptions(cps);
10569     } else {
10570       SendToProgram("xboard\n", cps);
10571     }
10572 }
10573
10574 void
10575 TwoMachinesEventIfReady P((void))
10576 {
10577   static int curMess = 0;
10578   if (first.lastPing != first.lastPong) {
10579     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10580     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10581     return;
10582   }
10583   if (second.lastPing != second.lastPong) {
10584     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10585     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10586     return;
10587   }
10588   DisplayMessage("", ""); curMess = 0;
10589   TwoMachinesEvent();
10590 }
10591
10592 char *
10593 MakeName (char *template)
10594 {
10595     time_t clock;
10596     struct tm *tm;
10597     static char buf[MSG_SIZ];
10598     char *p = buf;
10599     int i;
10600
10601     clock = time((time_t *)NULL);
10602     tm = localtime(&clock);
10603
10604     while(*p++ = *template++) if(p[-1] == '%') {
10605         switch(*template++) {
10606           case 0:   *p = 0; return buf;
10607           case 'Y': i = tm->tm_year+1900; break;
10608           case 'y': i = tm->tm_year-100; break;
10609           case 'M': i = tm->tm_mon+1; break;
10610           case 'd': i = tm->tm_mday; break;
10611           case 'h': i = tm->tm_hour; break;
10612           case 'm': i = tm->tm_min; break;
10613           case 's': i = tm->tm_sec; break;
10614           default:  i = 0;
10615         }
10616         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10617     }
10618     return buf;
10619 }
10620
10621 int
10622 CountPlayers (char *p)
10623 {
10624     int n = 0;
10625     while(p = strchr(p, '\n')) p++, n++; // count participants
10626     return n;
10627 }
10628
10629 FILE *
10630 WriteTourneyFile (char *results, FILE *f)
10631 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10632     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10633     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10634         // create a file with tournament description
10635         fprintf(f, "-participants {%s}\n", appData.participants);
10636         fprintf(f, "-seedBase %d\n", appData.seedBase);
10637         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10638         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10639         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10640         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10641         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10642         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10643         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10644         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10645         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10646         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10647         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10648         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10649         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10650         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10651         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10652         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10653         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10654         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10655         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10656         fprintf(f, "-smpCores %d\n", appData.smpCores);
10657         if(searchTime > 0)
10658                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10659         else {
10660                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10661                 fprintf(f, "-tc %s\n", appData.timeControl);
10662                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10663         }
10664         fprintf(f, "-results \"%s\"\n", results);
10665     }
10666     return f;
10667 }
10668
10669 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10670
10671 void
10672 Substitute (char *participants, int expunge)
10673 {
10674     int i, changed, changes=0, nPlayers=0;
10675     char *p, *q, *r, buf[MSG_SIZ];
10676     if(participants == NULL) return;
10677     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10678     r = p = participants; q = appData.participants;
10679     while(*p && *p == *q) {
10680         if(*p == '\n') r = p+1, nPlayers++;
10681         p++; q++;
10682     }
10683     if(*p) { // difference
10684         while(*p && *p++ != '\n');
10685         while(*q && *q++ != '\n');
10686       changed = nPlayers;
10687         changes = 1 + (strcmp(p, q) != 0);
10688     }
10689     if(changes == 1) { // a single engine mnemonic was changed
10690         q = r; while(*q) nPlayers += (*q++ == '\n');
10691         p = buf; while(*r && (*p = *r++) != '\n') p++;
10692         *p = NULLCHAR;
10693         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10694         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10695         if(mnemonic[i]) { // The substitute is valid
10696             FILE *f;
10697             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10698                 flock(fileno(f), LOCK_EX);
10699                 ParseArgsFromFile(f);
10700                 fseek(f, 0, SEEK_SET);
10701                 FREE(appData.participants); appData.participants = participants;
10702                 if(expunge) { // erase results of replaced engine
10703                     int len = strlen(appData.results), w, b, dummy;
10704                     for(i=0; i<len; i++) {
10705                         Pairing(i, nPlayers, &w, &b, &dummy);
10706                         if((w == changed || b == changed) && appData.results[i] == '*') {
10707                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10708                             fclose(f);
10709                             return;
10710                         }
10711                     }
10712                     for(i=0; i<len; i++) {
10713                         Pairing(i, nPlayers, &w, &b, &dummy);
10714                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10715                     }
10716                 }
10717                 WriteTourneyFile(appData.results, f);
10718                 fclose(f); // release lock
10719                 return;
10720             }
10721         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10722     }
10723     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10724     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10725     free(participants);
10726     return;
10727 }
10728
10729 int
10730 CheckPlayers (char *participants)
10731 {
10732         int i;
10733         char buf[MSG_SIZ], *p;
10734         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10735         while(p = strchr(participants, '\n')) {
10736             *p = NULLCHAR;
10737             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10738             if(!mnemonic[i]) {
10739                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10740                 *p = '\n';
10741                 DisplayError(buf, 0);
10742                 return 1;
10743             }
10744             *p = '\n';
10745             participants = p + 1;
10746         }
10747         return 0;
10748 }
10749
10750 int
10751 CreateTourney (char *name)
10752 {
10753         FILE *f;
10754         if(matchMode && strcmp(name, appData.tourneyFile)) {
10755              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10756         }
10757         if(name[0] == NULLCHAR) {
10758             if(appData.participants[0])
10759                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10760             return 0;
10761         }
10762         f = fopen(name, "r");
10763         if(f) { // file exists
10764             ASSIGN(appData.tourneyFile, name);
10765             ParseArgsFromFile(f); // parse it
10766         } else {
10767             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10768             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10769                 DisplayError(_("Not enough participants"), 0);
10770                 return 0;
10771             }
10772             if(CheckPlayers(appData.participants)) return 0;
10773             ASSIGN(appData.tourneyFile, name);
10774             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10775             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10776         }
10777         fclose(f);
10778         appData.noChessProgram = FALSE;
10779         appData.clockMode = TRUE;
10780         SetGNUMode();
10781         return 1;
10782 }
10783
10784 int
10785 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10786 {
10787     char buf[MSG_SIZ], *p, *q;
10788     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10789     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10790     skip = !all && group[0]; // if group requested, we start in skip mode
10791     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10792         p = names; q = buf; header = 0;
10793         while(*p && *p != '\n') *q++ = *p++;
10794         *q = 0;
10795         if(*p == '\n') p++;
10796         if(buf[0] == '#') {
10797             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10798             depth++; // we must be entering a new group
10799             if(all) continue; // suppress printing group headers when complete list requested
10800             header = 1;
10801             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10802         }
10803         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10804         if(engineList[i]) free(engineList[i]);
10805         engineList[i] = strdup(buf);
10806         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10807         if(engineMnemonic[i]) free(engineMnemonic[i]);
10808         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10809             strcat(buf, " (");
10810             sscanf(q + 8, "%s", buf + strlen(buf));
10811             strcat(buf, ")");
10812         }
10813         engineMnemonic[i] = strdup(buf);
10814         i++;
10815     }
10816     engineList[i] = engineMnemonic[i] = NULL;
10817     return i;
10818 }
10819
10820 // following implemented as macro to avoid type limitations
10821 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10822
10823 void
10824 SwapEngines (int n)
10825 {   // swap settings for first engine and other engine (so far only some selected options)
10826     int h;
10827     char *p;
10828     if(n == 0) return;
10829     SWAP(directory, p)
10830     SWAP(chessProgram, p)
10831     SWAP(isUCI, h)
10832     SWAP(hasOwnBookUCI, h)
10833     SWAP(protocolVersion, h)
10834     SWAP(reuse, h)
10835     SWAP(scoreIsAbsolute, h)
10836     SWAP(timeOdds, h)
10837     SWAP(logo, p)
10838     SWAP(pgnName, p)
10839     SWAP(pvSAN, h)
10840     SWAP(engOptions, p)
10841     SWAP(engInitString, p)
10842     SWAP(computerString, p)
10843     SWAP(features, p)
10844     SWAP(fenOverride, p)
10845     SWAP(NPS, h)
10846     SWAP(accumulateTC, h)
10847     SWAP(drawDepth, h)
10848     SWAP(host, p)
10849     SWAP(pseudo, h)
10850 }
10851
10852 int
10853 GetEngineLine (char *s, int n)
10854 {
10855     int i;
10856     char buf[MSG_SIZ];
10857     extern char *icsNames;
10858     if(!s || !*s) return 0;
10859     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10860     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10861     if(!mnemonic[i]) return 0;
10862     if(n == 11) return 1; // just testing if there was a match
10863     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10864     if(n == 1) SwapEngines(n);
10865     ParseArgsFromString(buf);
10866     if(n == 1) SwapEngines(n);
10867     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10868         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10869         ParseArgsFromString(buf);
10870     }
10871     return 1;
10872 }
10873
10874 int
10875 SetPlayer (int player, char *p)
10876 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10877     int i;
10878     char buf[MSG_SIZ], *engineName;
10879     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10880     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10881     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10882     if(mnemonic[i]) {
10883         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10884         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10885         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10886         ParseArgsFromString(buf);
10887     } else { // no engine with this nickname is installed!
10888         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10889         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10890         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10891         ModeHighlight();
10892         DisplayError(buf, 0);
10893         return 0;
10894     }
10895     free(engineName);
10896     return i;
10897 }
10898
10899 char *recentEngines;
10900
10901 void
10902 RecentEngineEvent (int nr)
10903 {
10904     int n;
10905 //    SwapEngines(1); // bump first to second
10906 //    ReplaceEngine(&second, 1); // and load it there
10907     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10908     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10909     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10910         ReplaceEngine(&first, 0);
10911         FloatToFront(&appData.recentEngineList, command[n]);
10912     }
10913 }
10914
10915 int
10916 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10917 {   // determine players from game number
10918     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10919
10920     if(appData.tourneyType == 0) {
10921         roundsPerCycle = (nPlayers - 1) | 1;
10922         pairingsPerRound = nPlayers / 2;
10923     } else if(appData.tourneyType > 0) {
10924         roundsPerCycle = nPlayers - appData.tourneyType;
10925         pairingsPerRound = appData.tourneyType;
10926     }
10927     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10928     gamesPerCycle = gamesPerRound * roundsPerCycle;
10929     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10930     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10931     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10932     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10933     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10934     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10935
10936     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10937     if(appData.roundSync) *syncInterval = gamesPerRound;
10938
10939     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10940
10941     if(appData.tourneyType == 0) {
10942         if(curPairing == (nPlayers-1)/2 ) {
10943             *whitePlayer = curRound;
10944             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10945         } else {
10946             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10947             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10948             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10949             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10950         }
10951     } else if(appData.tourneyType > 1) {
10952         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10953         *whitePlayer = curRound + appData.tourneyType;
10954     } else if(appData.tourneyType > 0) {
10955         *whitePlayer = curPairing;
10956         *blackPlayer = curRound + appData.tourneyType;
10957     }
10958
10959     // take care of white/black alternation per round.
10960     // For cycles and games this is already taken care of by default, derived from matchGame!
10961     return curRound & 1;
10962 }
10963
10964 int
10965 NextTourneyGame (int nr, int *swapColors)
10966 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10967     char *p, *q;
10968     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10969     FILE *tf;
10970     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10971     tf = fopen(appData.tourneyFile, "r");
10972     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10973     ParseArgsFromFile(tf); fclose(tf);
10974     InitTimeControls(); // TC might be altered from tourney file
10975
10976     nPlayers = CountPlayers(appData.participants); // count participants
10977     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10978     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10979
10980     if(syncInterval) {
10981         p = q = appData.results;
10982         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10983         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10984             DisplayMessage(_("Waiting for other game(s)"),"");
10985             waitingForGame = TRUE;
10986             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10987             return 0;
10988         }
10989         waitingForGame = FALSE;
10990     }
10991
10992     if(appData.tourneyType < 0) {
10993         if(nr>=0 && !pairingReceived) {
10994             char buf[1<<16];
10995             if(pairing.pr == NoProc) {
10996                 if(!appData.pairingEngine[0]) {
10997                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10998                     return 0;
10999                 }
11000                 StartChessProgram(&pairing); // starts the pairing engine
11001             }
11002             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11003             SendToProgram(buf, &pairing);
11004             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11005             SendToProgram(buf, &pairing);
11006             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11007         }
11008         pairingReceived = 0;                              // ... so we continue here
11009         *swapColors = 0;
11010         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11011         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11012         matchGame = 1; roundNr = nr / syncInterval + 1;
11013     }
11014
11015     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11016
11017     // redefine engines, engine dir, etc.
11018     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11019     if(first.pr == NoProc) {
11020       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11021       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11022     }
11023     if(second.pr == NoProc) {
11024       SwapEngines(1);
11025       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11026       SwapEngines(1);         // and make that valid for second engine by swapping
11027       InitEngine(&second, 1);
11028     }
11029     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11030     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11031     return OK;
11032 }
11033
11034 void
11035 NextMatchGame ()
11036 {   // performs game initialization that does not invoke engines, and then tries to start the game
11037     int res, firstWhite, swapColors = 0;
11038     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11039     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
11040         char buf[MSG_SIZ];
11041         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11042         if(strcmp(buf, currentDebugFile)) { // name has changed
11043             FILE *f = fopen(buf, "w");
11044             if(f) { // if opening the new file failed, just keep using the old one
11045                 ASSIGN(currentDebugFile, buf);
11046                 fclose(debugFP);
11047                 debugFP = f;
11048             }
11049             if(appData.serverFileName) {
11050                 if(serverFP) fclose(serverFP);
11051                 serverFP = fopen(appData.serverFileName, "w");
11052                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11053                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11054             }
11055         }
11056     }
11057     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11058     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11059     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11060     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11061     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11062     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11063     Reset(FALSE, first.pr != NoProc);
11064     res = LoadGameOrPosition(matchGame); // setup game
11065     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11066     if(!res) return; // abort when bad game/pos file
11067     TwoMachinesEvent();
11068 }
11069
11070 void
11071 UserAdjudicationEvent (int result)
11072 {
11073     ChessMove gameResult = GameIsDrawn;
11074
11075     if( result > 0 ) {
11076         gameResult = WhiteWins;
11077     }
11078     else if( result < 0 ) {
11079         gameResult = BlackWins;
11080     }
11081
11082     if( gameMode == TwoMachinesPlay ) {
11083         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11084     }
11085 }
11086
11087
11088 // [HGM] save: calculate checksum of game to make games easily identifiable
11089 int
11090 StringCheckSum (char *s)
11091 {
11092         int i = 0;
11093         if(s==NULL) return 0;
11094         while(*s) i = i*259 + *s++;
11095         return i;
11096 }
11097
11098 int
11099 GameCheckSum ()
11100 {
11101         int i, sum=0;
11102         for(i=backwardMostMove; i<forwardMostMove; i++) {
11103                 sum += pvInfoList[i].depth;
11104                 sum += StringCheckSum(parseList[i]);
11105                 sum += StringCheckSum(commentList[i]);
11106                 sum *= 261;
11107         }
11108         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11109         return sum + StringCheckSum(commentList[i]);
11110 } // end of save patch
11111
11112 void
11113 GameEnds (ChessMove result, char *resultDetails, int whosays)
11114 {
11115     GameMode nextGameMode;
11116     int isIcsGame;
11117     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11118
11119     if(endingGame) return; /* [HGM] crash: forbid recursion */
11120     endingGame = 1;
11121     if(twoBoards) { // [HGM] dual: switch back to one board
11122         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11123         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11124     }
11125     if (appData.debugMode) {
11126       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11127               result, resultDetails ? resultDetails : "(null)", whosays);
11128     }
11129
11130     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11131
11132     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11133
11134     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11135         /* If we are playing on ICS, the server decides when the
11136            game is over, but the engine can offer to draw, claim
11137            a draw, or resign.
11138          */
11139 #if ZIPPY
11140         if (appData.zippyPlay && first.initDone) {
11141             if (result == GameIsDrawn) {
11142                 /* In case draw still needs to be claimed */
11143                 SendToICS(ics_prefix);
11144                 SendToICS("draw\n");
11145             } else if (StrCaseStr(resultDetails, "resign")) {
11146                 SendToICS(ics_prefix);
11147                 SendToICS("resign\n");
11148             }
11149         }
11150 #endif
11151         endingGame = 0; /* [HGM] crash */
11152         return;
11153     }
11154
11155     /* If we're loading the game from a file, stop */
11156     if (whosays == GE_FILE) {
11157       (void) StopLoadGameTimer();
11158       gameFileFP = NULL;
11159     }
11160
11161     /* Cancel draw offers */
11162     first.offeredDraw = second.offeredDraw = 0;
11163
11164     /* If this is an ICS game, only ICS can really say it's done;
11165        if not, anyone can. */
11166     isIcsGame = (gameMode == IcsPlayingWhite ||
11167                  gameMode == IcsPlayingBlack ||
11168                  gameMode == IcsObserving    ||
11169                  gameMode == IcsExamining);
11170
11171     if (!isIcsGame || whosays == GE_ICS) {
11172         /* OK -- not an ICS game, or ICS said it was done */
11173         StopClocks();
11174         if (!isIcsGame && !appData.noChessProgram)
11175           SetUserThinkingEnables();
11176
11177         /* [HGM] if a machine claims the game end we verify this claim */
11178         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11179             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11180                 char claimer;
11181                 ChessMove trueResult = (ChessMove) -1;
11182
11183                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11184                                             first.twoMachinesColor[0] :
11185                                             second.twoMachinesColor[0] ;
11186
11187                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11188                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11189                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11190                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11191                 } else
11192                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11193                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11194                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11195                 } else
11196                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11197                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11198                 }
11199
11200                 // now verify win claims, but not in drop games, as we don't understand those yet
11201                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11202                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11203                     (result == WhiteWins && claimer == 'w' ||
11204                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11205                       if (appData.debugMode) {
11206                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11207                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11208                       }
11209                       if(result != trueResult) {
11210                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11211                               result = claimer == 'w' ? BlackWins : WhiteWins;
11212                               resultDetails = buf;
11213                       }
11214                 } else
11215                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11216                     && (forwardMostMove <= backwardMostMove ||
11217                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11218                         (claimer=='b')==(forwardMostMove&1))
11219                                                                                   ) {
11220                       /* [HGM] verify: draws that were not flagged are false claims */
11221                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11222                       result = claimer == 'w' ? BlackWins : WhiteWins;
11223                       resultDetails = buf;
11224                 }
11225                 /* (Claiming a loss is accepted no questions asked!) */
11226             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11227                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11228                 result = GameUnfinished;
11229                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11230             }
11231             /* [HGM] bare: don't allow bare King to win */
11232             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11233                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11234                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11235                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11236                && result != GameIsDrawn)
11237             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11238                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11239                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11240                         if(p >= 0 && p <= (int)WhiteKing) k++;
11241                 }
11242                 if (appData.debugMode) {
11243                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11244                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11245                 }
11246                 if(k <= 1) {
11247                         result = GameIsDrawn;
11248                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11249                         resultDetails = buf;
11250                 }
11251             }
11252         }
11253
11254
11255         if(serverMoves != NULL && !loadFlag) { char c = '=';
11256             if(result==WhiteWins) c = '+';
11257             if(result==BlackWins) c = '-';
11258             if(resultDetails != NULL)
11259                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11260         }
11261         if (resultDetails != NULL) {
11262             gameInfo.result = result;
11263             gameInfo.resultDetails = StrSave(resultDetails);
11264
11265             /* display last move only if game was not loaded from file */
11266             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11267                 DisplayMove(currentMove - 1);
11268
11269             if (forwardMostMove != 0) {
11270                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11271                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11272                                                                 ) {
11273                     if (*appData.saveGameFile != NULLCHAR) {
11274                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11275                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11276                         else
11277                         SaveGameToFile(appData.saveGameFile, TRUE);
11278                     } else if (appData.autoSaveGames) {
11279                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11280                     }
11281                     if (*appData.savePositionFile != NULLCHAR) {
11282                         SavePositionToFile(appData.savePositionFile);
11283                     }
11284                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11285                 }
11286             }
11287
11288             /* Tell program how game ended in case it is learning */
11289             /* [HGM] Moved this to after saving the PGN, just in case */
11290             /* engine died and we got here through time loss. In that */
11291             /* case we will get a fatal error writing the pipe, which */
11292             /* would otherwise lose us the PGN.                       */
11293             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11294             /* output during GameEnds should never be fatal anymore   */
11295             if (gameMode == MachinePlaysWhite ||
11296                 gameMode == MachinePlaysBlack ||
11297                 gameMode == TwoMachinesPlay ||
11298                 gameMode == IcsPlayingWhite ||
11299                 gameMode == IcsPlayingBlack ||
11300                 gameMode == BeginningOfGame) {
11301                 char buf[MSG_SIZ];
11302                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11303                         resultDetails);
11304                 if (first.pr != NoProc) {
11305                     SendToProgram(buf, &first);
11306                 }
11307                 if (second.pr != NoProc &&
11308                     gameMode == TwoMachinesPlay) {
11309                     SendToProgram(buf, &second);
11310                 }
11311             }
11312         }
11313
11314         if (appData.icsActive) {
11315             if (appData.quietPlay &&
11316                 (gameMode == IcsPlayingWhite ||
11317                  gameMode == IcsPlayingBlack)) {
11318                 SendToICS(ics_prefix);
11319                 SendToICS("set shout 1\n");
11320             }
11321             nextGameMode = IcsIdle;
11322             ics_user_moved = FALSE;
11323             /* clean up premove.  It's ugly when the game has ended and the
11324              * premove highlights are still on the board.
11325              */
11326             if (gotPremove) {
11327               gotPremove = FALSE;
11328               ClearPremoveHighlights();
11329               DrawPosition(FALSE, boards[currentMove]);
11330             }
11331             if (whosays == GE_ICS) {
11332                 switch (result) {
11333                 case WhiteWins:
11334                     if (gameMode == IcsPlayingWhite)
11335                         PlayIcsWinSound();
11336                     else if(gameMode == IcsPlayingBlack)
11337                         PlayIcsLossSound();
11338                     break;
11339                 case BlackWins:
11340                     if (gameMode == IcsPlayingBlack)
11341                         PlayIcsWinSound();
11342                     else if(gameMode == IcsPlayingWhite)
11343                         PlayIcsLossSound();
11344                     break;
11345                 case GameIsDrawn:
11346                     PlayIcsDrawSound();
11347                     break;
11348                 default:
11349                     PlayIcsUnfinishedSound();
11350                 }
11351             }
11352             if(appData.quitNext) { ExitEvent(0); return; }
11353         } else if (gameMode == EditGame ||
11354                    gameMode == PlayFromGameFile ||
11355                    gameMode == AnalyzeMode ||
11356                    gameMode == AnalyzeFile) {
11357             nextGameMode = gameMode;
11358         } else {
11359             nextGameMode = EndOfGame;
11360         }
11361         pausing = FALSE;
11362         ModeHighlight();
11363     } else {
11364         nextGameMode = gameMode;
11365     }
11366
11367     if (appData.noChessProgram) {
11368         gameMode = nextGameMode;
11369         ModeHighlight();
11370         endingGame = 0; /* [HGM] crash */
11371         return;
11372     }
11373
11374     if (first.reuse) {
11375         /* Put first chess program into idle state */
11376         if (first.pr != NoProc &&
11377             (gameMode == MachinePlaysWhite ||
11378              gameMode == MachinePlaysBlack ||
11379              gameMode == TwoMachinesPlay ||
11380              gameMode == IcsPlayingWhite ||
11381              gameMode == IcsPlayingBlack ||
11382              gameMode == BeginningOfGame)) {
11383             SendToProgram("force\n", &first);
11384             if (first.usePing) {
11385               char buf[MSG_SIZ];
11386               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11387               SendToProgram(buf, &first);
11388             }
11389         }
11390     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11391         /* Kill off first chess program */
11392         if (first.isr != NULL)
11393           RemoveInputSource(first.isr);
11394         first.isr = NULL;
11395
11396         if (first.pr != NoProc) {
11397             ExitAnalyzeMode();
11398             DoSleep( appData.delayBeforeQuit );
11399             SendToProgram("quit\n", &first);
11400             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11401             first.reload = TRUE;
11402         }
11403         first.pr = NoProc;
11404     }
11405     if (second.reuse) {
11406         /* Put second chess program into idle state */
11407         if (second.pr != NoProc &&
11408             gameMode == TwoMachinesPlay) {
11409             SendToProgram("force\n", &second);
11410             if (second.usePing) {
11411               char buf[MSG_SIZ];
11412               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11413               SendToProgram(buf, &second);
11414             }
11415         }
11416     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11417         /* Kill off second chess program */
11418         if (second.isr != NULL)
11419           RemoveInputSource(second.isr);
11420         second.isr = NULL;
11421
11422         if (second.pr != NoProc) {
11423             DoSleep( appData.delayBeforeQuit );
11424             SendToProgram("quit\n", &second);
11425             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11426             second.reload = TRUE;
11427         }
11428         second.pr = NoProc;
11429     }
11430
11431     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11432         char resChar = '=';
11433         switch (result) {
11434         case WhiteWins:
11435           resChar = '+';
11436           if (first.twoMachinesColor[0] == 'w') {
11437             first.matchWins++;
11438           } else {
11439             second.matchWins++;
11440           }
11441           break;
11442         case BlackWins:
11443           resChar = '-';
11444           if (first.twoMachinesColor[0] == 'b') {
11445             first.matchWins++;
11446           } else {
11447             second.matchWins++;
11448           }
11449           break;
11450         case GameUnfinished:
11451           resChar = ' ';
11452         default:
11453           break;
11454         }
11455
11456         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11457         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11458             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11459             ReserveGame(nextGame, resChar); // sets nextGame
11460             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11461             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11462         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11463
11464         if (nextGame <= appData.matchGames && !abortMatch) {
11465             gameMode = nextGameMode;
11466             matchGame = nextGame; // this will be overruled in tourney mode!
11467             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11468             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11469             endingGame = 0; /* [HGM] crash */
11470             return;
11471         } else {
11472             gameMode = nextGameMode;
11473             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11474                      first.tidy, second.tidy,
11475                      first.matchWins, second.matchWins,
11476                      appData.matchGames - (first.matchWins + second.matchWins));
11477             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11478             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11479             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11480             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11481                 first.twoMachinesColor = "black\n";
11482                 second.twoMachinesColor = "white\n";
11483             } else {
11484                 first.twoMachinesColor = "white\n";
11485                 second.twoMachinesColor = "black\n";
11486             }
11487         }
11488     }
11489     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11490         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11491       ExitAnalyzeMode();
11492     gameMode = nextGameMode;
11493     ModeHighlight();
11494     endingGame = 0;  /* [HGM] crash */
11495     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11496         if(matchMode == TRUE) { // match through command line: exit with or without popup
11497             if(ranking) {
11498                 ToNrEvent(forwardMostMove);
11499                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11500                 else ExitEvent(0);
11501             } else DisplayFatalError(buf, 0, 0);
11502         } else { // match through menu; just stop, with or without popup
11503             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11504             ModeHighlight();
11505             if(ranking){
11506                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11507             } else DisplayNote(buf);
11508       }
11509       if(ranking) free(ranking);
11510     }
11511 }
11512
11513 /* Assumes program was just initialized (initString sent).
11514    Leaves program in force mode. */
11515 void
11516 FeedMovesToProgram (ChessProgramState *cps, int upto)
11517 {
11518     int i;
11519
11520     if (appData.debugMode)
11521       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11522               startedFromSetupPosition ? "position and " : "",
11523               backwardMostMove, upto, cps->which);
11524     if(currentlyInitializedVariant != gameInfo.variant) {
11525       char buf[MSG_SIZ];
11526         // [HGM] variantswitch: make engine aware of new variant
11527         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11528                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11529                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11530         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11531         SendToProgram(buf, cps);
11532         currentlyInitializedVariant = gameInfo.variant;
11533     }
11534     SendToProgram("force\n", cps);
11535     if (startedFromSetupPosition) {
11536         SendBoard(cps, backwardMostMove);
11537     if (appData.debugMode) {
11538         fprintf(debugFP, "feedMoves\n");
11539     }
11540     }
11541     for (i = backwardMostMove; i < upto; i++) {
11542         SendMoveToProgram(i, cps);
11543     }
11544 }
11545
11546
11547 int
11548 ResurrectChessProgram ()
11549 {
11550      /* The chess program may have exited.
11551         If so, restart it and feed it all the moves made so far. */
11552     static int doInit = 0;
11553
11554     if (appData.noChessProgram) return 1;
11555
11556     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11557         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11558         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11559         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11560     } else {
11561         if (first.pr != NoProc) return 1;
11562         StartChessProgram(&first);
11563     }
11564     InitChessProgram(&first, FALSE);
11565     FeedMovesToProgram(&first, currentMove);
11566
11567     if (!first.sendTime) {
11568         /* can't tell gnuchess what its clock should read,
11569            so we bow to its notion. */
11570         ResetClocks();
11571         timeRemaining[0][currentMove] = whiteTimeRemaining;
11572         timeRemaining[1][currentMove] = blackTimeRemaining;
11573     }
11574
11575     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11576                 appData.icsEngineAnalyze) && first.analysisSupport) {
11577       SendToProgram("analyze\n", &first);
11578       first.analyzing = TRUE;
11579     }
11580     return 1;
11581 }
11582
11583 /*
11584  * Button procedures
11585  */
11586 void
11587 Reset (int redraw, int init)
11588 {
11589     int i;
11590
11591     if (appData.debugMode) {
11592         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11593                 redraw, init, gameMode);
11594     }
11595     CleanupTail(); // [HGM] vari: delete any stored variations
11596     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11597     pausing = pauseExamInvalid = FALSE;
11598     startedFromSetupPosition = blackPlaysFirst = FALSE;
11599     firstMove = TRUE;
11600     whiteFlag = blackFlag = FALSE;
11601     userOfferedDraw = FALSE;
11602     hintRequested = bookRequested = FALSE;
11603     first.maybeThinking = FALSE;
11604     second.maybeThinking = FALSE;
11605     first.bookSuspend = FALSE; // [HGM] book
11606     second.bookSuspend = FALSE;
11607     thinkOutput[0] = NULLCHAR;
11608     lastHint[0] = NULLCHAR;
11609     ClearGameInfo(&gameInfo);
11610     gameInfo.variant = StringToVariant(appData.variant);
11611     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11612     ics_user_moved = ics_clock_paused = FALSE;
11613     ics_getting_history = H_FALSE;
11614     ics_gamenum = -1;
11615     white_holding[0] = black_holding[0] = NULLCHAR;
11616     ClearProgramStats();
11617     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11618
11619     ResetFrontEnd();
11620     ClearHighlights();
11621     flipView = appData.flipView;
11622     ClearPremoveHighlights();
11623     gotPremove = FALSE;
11624     alarmSounded = FALSE;
11625     killX = killY = -1; // [HGM] lion
11626
11627     GameEnds(EndOfFile, NULL, GE_PLAYER);
11628     if(appData.serverMovesName != NULL) {
11629         /* [HGM] prepare to make moves file for broadcasting */
11630         clock_t t = clock();
11631         if(serverMoves != NULL) fclose(serverMoves);
11632         serverMoves = fopen(appData.serverMovesName, "r");
11633         if(serverMoves != NULL) {
11634             fclose(serverMoves);
11635             /* delay 15 sec before overwriting, so all clients can see end */
11636             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11637         }
11638         serverMoves = fopen(appData.serverMovesName, "w");
11639     }
11640
11641     ExitAnalyzeMode();
11642     gameMode = BeginningOfGame;
11643     ModeHighlight();
11644     if(appData.icsActive) gameInfo.variant = VariantNormal;
11645     currentMove = forwardMostMove = backwardMostMove = 0;
11646     MarkTargetSquares(1);
11647     InitPosition(redraw);
11648     for (i = 0; i < MAX_MOVES; i++) {
11649         if (commentList[i] != NULL) {
11650             free(commentList[i]);
11651             commentList[i] = NULL;
11652         }
11653     }
11654     ResetClocks();
11655     timeRemaining[0][0] = whiteTimeRemaining;
11656     timeRemaining[1][0] = blackTimeRemaining;
11657
11658     if (first.pr == NoProc) {
11659         StartChessProgram(&first);
11660     }
11661     if (init) {
11662             InitChessProgram(&first, startedFromSetupPosition);
11663     }
11664     DisplayTitle("");
11665     DisplayMessage("", "");
11666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11667     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11668     ClearMap();        // [HGM] exclude: invalidate map
11669 }
11670
11671 void
11672 AutoPlayGameLoop ()
11673 {
11674     for (;;) {
11675         if (!AutoPlayOneMove())
11676           return;
11677         if (matchMode || appData.timeDelay == 0)
11678           continue;
11679         if (appData.timeDelay < 0)
11680           return;
11681         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11682         break;
11683     }
11684 }
11685
11686 void
11687 AnalyzeNextGame()
11688 {
11689     ReloadGame(1); // next game
11690 }
11691
11692 int
11693 AutoPlayOneMove ()
11694 {
11695     int fromX, fromY, toX, toY;
11696
11697     if (appData.debugMode) {
11698       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11699     }
11700
11701     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11702       return FALSE;
11703
11704     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11705       pvInfoList[currentMove].depth = programStats.depth;
11706       pvInfoList[currentMove].score = programStats.score;
11707       pvInfoList[currentMove].time  = 0;
11708       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11709       else { // append analysis of final position as comment
11710         char buf[MSG_SIZ];
11711         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11712         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11713       }
11714       programStats.depth = 0;
11715     }
11716
11717     if (currentMove >= forwardMostMove) {
11718       if(gameMode == AnalyzeFile) {
11719           if(appData.loadGameIndex == -1) {
11720             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11721           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11722           } else {
11723           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11724         }
11725       }
11726 //      gameMode = EndOfGame;
11727 //      ModeHighlight();
11728
11729       /* [AS] Clear current move marker at the end of a game */
11730       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11731
11732       return FALSE;
11733     }
11734
11735     toX = moveList[currentMove][2] - AAA;
11736     toY = moveList[currentMove][3] - ONE;
11737
11738     if (moveList[currentMove][1] == '@') {
11739         if (appData.highlightLastMove) {
11740             SetHighlights(-1, -1, toX, toY);
11741         }
11742     } else {
11743         fromX = moveList[currentMove][0] - AAA;
11744         fromY = moveList[currentMove][1] - ONE;
11745
11746         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11747
11748         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11749
11750         if (appData.highlightLastMove) {
11751             SetHighlights(fromX, fromY, toX, toY);
11752         }
11753     }
11754     DisplayMove(currentMove);
11755     SendMoveToProgram(currentMove++, &first);
11756     DisplayBothClocks();
11757     DrawPosition(FALSE, boards[currentMove]);
11758     // [HGM] PV info: always display, routine tests if empty
11759     DisplayComment(currentMove - 1, commentList[currentMove]);
11760     return TRUE;
11761 }
11762
11763
11764 int
11765 LoadGameOneMove (ChessMove readAhead)
11766 {
11767     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11768     char promoChar = NULLCHAR;
11769     ChessMove moveType;
11770     char move[MSG_SIZ];
11771     char *p, *q;
11772
11773     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11774         gameMode != AnalyzeMode && gameMode != Training) {
11775         gameFileFP = NULL;
11776         return FALSE;
11777     }
11778
11779     yyboardindex = forwardMostMove;
11780     if (readAhead != EndOfFile) {
11781       moveType = readAhead;
11782     } else {
11783       if (gameFileFP == NULL)
11784           return FALSE;
11785       moveType = (ChessMove) Myylex();
11786     }
11787
11788     done = FALSE;
11789     switch (moveType) {
11790       case Comment:
11791         if (appData.debugMode)
11792           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11793         p = yy_text;
11794
11795         /* append the comment but don't display it */
11796         AppendComment(currentMove, p, FALSE);
11797         return TRUE;
11798
11799       case WhiteCapturesEnPassant:
11800       case BlackCapturesEnPassant:
11801       case WhitePromotion:
11802       case BlackPromotion:
11803       case WhiteNonPromotion:
11804       case BlackNonPromotion:
11805       case NormalMove:
11806       case FirstLeg:
11807       case WhiteKingSideCastle:
11808       case WhiteQueenSideCastle:
11809       case BlackKingSideCastle:
11810       case BlackQueenSideCastle:
11811       case WhiteKingSideCastleWild:
11812       case WhiteQueenSideCastleWild:
11813       case BlackKingSideCastleWild:
11814       case BlackQueenSideCastleWild:
11815       /* PUSH Fabien */
11816       case WhiteHSideCastleFR:
11817       case WhiteASideCastleFR:
11818       case BlackHSideCastleFR:
11819       case BlackASideCastleFR:
11820       /* POP Fabien */
11821         if (appData.debugMode)
11822           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11823         fromX = currentMoveString[0] - AAA;
11824         fromY = currentMoveString[1] - ONE;
11825         toX = currentMoveString[2] - AAA;
11826         toY = currentMoveString[3] - ONE;
11827         promoChar = currentMoveString[4];
11828         if(promoChar == ';') promoChar = NULLCHAR;
11829         break;
11830
11831       case WhiteDrop:
11832       case BlackDrop:
11833         if (appData.debugMode)
11834           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11835         fromX = moveType == WhiteDrop ?
11836           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11837         (int) CharToPiece(ToLower(currentMoveString[0]));
11838         fromY = DROP_RANK;
11839         toX = currentMoveString[2] - AAA;
11840         toY = currentMoveString[3] - ONE;
11841         break;
11842
11843       case WhiteWins:
11844       case BlackWins:
11845       case GameIsDrawn:
11846       case GameUnfinished:
11847         if (appData.debugMode)
11848           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11849         p = strchr(yy_text, '{');
11850         if (p == NULL) p = strchr(yy_text, '(');
11851         if (p == NULL) {
11852             p = yy_text;
11853             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11854         } else {
11855             q = strchr(p, *p == '{' ? '}' : ')');
11856             if (q != NULL) *q = NULLCHAR;
11857             p++;
11858         }
11859         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11860         GameEnds(moveType, p, GE_FILE);
11861         done = TRUE;
11862         if (cmailMsgLoaded) {
11863             ClearHighlights();
11864             flipView = WhiteOnMove(currentMove);
11865             if (moveType == GameUnfinished) flipView = !flipView;
11866             if (appData.debugMode)
11867               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11868         }
11869         break;
11870
11871       case EndOfFile:
11872         if (appData.debugMode)
11873           fprintf(debugFP, "Parser hit end of file\n");
11874         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11875           case MT_NONE:
11876           case MT_CHECK:
11877             break;
11878           case MT_CHECKMATE:
11879           case MT_STAINMATE:
11880             if (WhiteOnMove(currentMove)) {
11881                 GameEnds(BlackWins, "Black mates", GE_FILE);
11882             } else {
11883                 GameEnds(WhiteWins, "White mates", GE_FILE);
11884             }
11885             break;
11886           case MT_STALEMATE:
11887             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11888             break;
11889         }
11890         done = TRUE;
11891         break;
11892
11893       case MoveNumberOne:
11894         if (lastLoadGameStart == GNUChessGame) {
11895             /* GNUChessGames have numbers, but they aren't move numbers */
11896             if (appData.debugMode)
11897               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11898                       yy_text, (int) moveType);
11899             return LoadGameOneMove(EndOfFile); /* tail recursion */
11900         }
11901         /* else fall thru */
11902
11903       case XBoardGame:
11904       case GNUChessGame:
11905       case PGNTag:
11906         /* Reached start of next game in file */
11907         if (appData.debugMode)
11908           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11909         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11910           case MT_NONE:
11911           case MT_CHECK:
11912             break;
11913           case MT_CHECKMATE:
11914           case MT_STAINMATE:
11915             if (WhiteOnMove(currentMove)) {
11916                 GameEnds(BlackWins, "Black mates", GE_FILE);
11917             } else {
11918                 GameEnds(WhiteWins, "White mates", GE_FILE);
11919             }
11920             break;
11921           case MT_STALEMATE:
11922             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11923             break;
11924         }
11925         done = TRUE;
11926         break;
11927
11928       case PositionDiagram:     /* should not happen; ignore */
11929       case ElapsedTime:         /* ignore */
11930       case NAG:                 /* ignore */
11931         if (appData.debugMode)
11932           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11933                   yy_text, (int) moveType);
11934         return LoadGameOneMove(EndOfFile); /* tail recursion */
11935
11936       case IllegalMove:
11937         if (appData.testLegality) {
11938             if (appData.debugMode)
11939               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11940             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11941                     (forwardMostMove / 2) + 1,
11942                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11943             DisplayError(move, 0);
11944             done = TRUE;
11945         } else {
11946             if (appData.debugMode)
11947               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11948                       yy_text, currentMoveString);
11949             fromX = currentMoveString[0] - AAA;
11950             fromY = currentMoveString[1] - ONE;
11951             toX = currentMoveString[2] - AAA;
11952             toY = currentMoveString[3] - ONE;
11953             promoChar = currentMoveString[4];
11954         }
11955         break;
11956
11957       case AmbiguousMove:
11958         if (appData.debugMode)
11959           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11960         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11961                 (forwardMostMove / 2) + 1,
11962                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11963         DisplayError(move, 0);
11964         done = TRUE;
11965         break;
11966
11967       default:
11968       case ImpossibleMove:
11969         if (appData.debugMode)
11970           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11971         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11972                 (forwardMostMove / 2) + 1,
11973                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11974         DisplayError(move, 0);
11975         done = TRUE;
11976         break;
11977     }
11978
11979     if (done) {
11980         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11981             DrawPosition(FALSE, boards[currentMove]);
11982             DisplayBothClocks();
11983             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11984               DisplayComment(currentMove - 1, commentList[currentMove]);
11985         }
11986         (void) StopLoadGameTimer();
11987         gameFileFP = NULL;
11988         cmailOldMove = forwardMostMove;
11989         return FALSE;
11990     } else {
11991         /* currentMoveString is set as a side-effect of yylex */
11992
11993         thinkOutput[0] = NULLCHAR;
11994         MakeMove(fromX, fromY, toX, toY, promoChar);
11995         killX = killY = -1; // [HGM] lion: used up
11996         currentMove = forwardMostMove;
11997         return TRUE;
11998     }
11999 }
12000
12001 /* Load the nth game from the given file */
12002 int
12003 LoadGameFromFile (char *filename, int n, char *title, int useList)
12004 {
12005     FILE *f;
12006     char buf[MSG_SIZ];
12007
12008     if (strcmp(filename, "-") == 0) {
12009         f = stdin;
12010         title = "stdin";
12011     } else {
12012         f = fopen(filename, "rb");
12013         if (f == NULL) {
12014           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12015             DisplayError(buf, errno);
12016             return FALSE;
12017         }
12018     }
12019     if (fseek(f, 0, 0) == -1) {
12020         /* f is not seekable; probably a pipe */
12021         useList = FALSE;
12022     }
12023     if (useList && n == 0) {
12024         int error = GameListBuild(f);
12025         if (error) {
12026             DisplayError(_("Cannot build game list"), error);
12027         } else if (!ListEmpty(&gameList) &&
12028                    ((ListGame *) gameList.tailPred)->number > 1) {
12029             GameListPopUp(f, title);
12030             return TRUE;
12031         }
12032         GameListDestroy();
12033         n = 1;
12034     }
12035     if (n == 0) n = 1;
12036     return LoadGame(f, n, title, FALSE);
12037 }
12038
12039
12040 void
12041 MakeRegisteredMove ()
12042 {
12043     int fromX, fromY, toX, toY;
12044     char promoChar;
12045     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12046         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12047           case CMAIL_MOVE:
12048           case CMAIL_DRAW:
12049             if (appData.debugMode)
12050               fprintf(debugFP, "Restoring %s for game %d\n",
12051                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12052
12053             thinkOutput[0] = NULLCHAR;
12054             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12055             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12056             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12057             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12058             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12059             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12060             MakeMove(fromX, fromY, toX, toY, promoChar);
12061             ShowMove(fromX, fromY, toX, toY);
12062
12063             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12064               case MT_NONE:
12065               case MT_CHECK:
12066                 break;
12067
12068               case MT_CHECKMATE:
12069               case MT_STAINMATE:
12070                 if (WhiteOnMove(currentMove)) {
12071                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12072                 } else {
12073                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12074                 }
12075                 break;
12076
12077               case MT_STALEMATE:
12078                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12079                 break;
12080             }
12081
12082             break;
12083
12084           case CMAIL_RESIGN:
12085             if (WhiteOnMove(currentMove)) {
12086                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12087             } else {
12088                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12089             }
12090             break;
12091
12092           case CMAIL_ACCEPT:
12093             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12094             break;
12095
12096           default:
12097             break;
12098         }
12099     }
12100
12101     return;
12102 }
12103
12104 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12105 int
12106 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12107 {
12108     int retVal;
12109
12110     if (gameNumber > nCmailGames) {
12111         DisplayError(_("No more games in this message"), 0);
12112         return FALSE;
12113     }
12114     if (f == lastLoadGameFP) {
12115         int offset = gameNumber - lastLoadGameNumber;
12116         if (offset == 0) {
12117             cmailMsg[0] = NULLCHAR;
12118             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12119                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12120                 nCmailMovesRegistered--;
12121             }
12122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12123             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12124                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12125             }
12126         } else {
12127             if (! RegisterMove()) return FALSE;
12128         }
12129     }
12130
12131     retVal = LoadGame(f, gameNumber, title, useList);
12132
12133     /* Make move registered during previous look at this game, if any */
12134     MakeRegisteredMove();
12135
12136     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12137         commentList[currentMove]
12138           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12139         DisplayComment(currentMove - 1, commentList[currentMove]);
12140     }
12141
12142     return retVal;
12143 }
12144
12145 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12146 int
12147 ReloadGame (int offset)
12148 {
12149     int gameNumber = lastLoadGameNumber + offset;
12150     if (lastLoadGameFP == NULL) {
12151         DisplayError(_("No game has been loaded yet"), 0);
12152         return FALSE;
12153     }
12154     if (gameNumber <= 0) {
12155         DisplayError(_("Can't back up any further"), 0);
12156         return FALSE;
12157     }
12158     if (cmailMsgLoaded) {
12159         return CmailLoadGame(lastLoadGameFP, gameNumber,
12160                              lastLoadGameTitle, lastLoadGameUseList);
12161     } else {
12162         return LoadGame(lastLoadGameFP, gameNumber,
12163                         lastLoadGameTitle, lastLoadGameUseList);
12164     }
12165 }
12166
12167 int keys[EmptySquare+1];
12168
12169 int
12170 PositionMatches (Board b1, Board b2)
12171 {
12172     int r, f, sum=0;
12173     switch(appData.searchMode) {
12174         case 1: return CompareWithRights(b1, b2);
12175         case 2:
12176             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12177                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12178             }
12179             return TRUE;
12180         case 3:
12181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12183                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12184             }
12185             return sum==0;
12186         case 4:
12187             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12188                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12189             }
12190             return sum==0;
12191     }
12192     return TRUE;
12193 }
12194
12195 #define Q_PROMO  4
12196 #define Q_EP     3
12197 #define Q_BCASTL 2
12198 #define Q_WCASTL 1
12199
12200 int pieceList[256], quickBoard[256];
12201 ChessSquare pieceType[256] = { EmptySquare };
12202 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12203 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12204 int soughtTotal, turn;
12205 Boolean epOK, flipSearch;
12206
12207 typedef struct {
12208     unsigned char piece, to;
12209 } Move;
12210
12211 #define DSIZE (250000)
12212
12213 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12214 Move *moveDatabase = initialSpace;
12215 unsigned int movePtr, dataSize = DSIZE;
12216
12217 int
12218 MakePieceList (Board board, int *counts)
12219 {
12220     int r, f, n=Q_PROMO, total=0;
12221     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12222     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12223         int sq = f + (r<<4);
12224         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12225             quickBoard[sq] = ++n;
12226             pieceList[n] = sq;
12227             pieceType[n] = board[r][f];
12228             counts[board[r][f]]++;
12229             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12230             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12231             total++;
12232         }
12233     }
12234     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12235     return total;
12236 }
12237
12238 void
12239 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12240 {
12241     int sq = fromX + (fromY<<4);
12242     int piece = quickBoard[sq], rook;
12243     quickBoard[sq] = 0;
12244     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12245     if(piece == pieceList[1] && fromY == toY) {
12246       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12247         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12248         moveDatabase[movePtr++].piece = Q_WCASTL;
12249         quickBoard[sq] = piece;
12250         piece = quickBoard[from]; quickBoard[from] = 0;
12251         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12252       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12253         quickBoard[sq] = 0; // remove Rook
12254         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12255         moveDatabase[movePtr++].piece = Q_WCASTL;
12256         quickBoard[sq] = pieceList[1]; // put King
12257         piece = rook;
12258         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12259       }
12260     } else
12261     if(piece == pieceList[2] && fromY == toY) {
12262       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12263         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12264         moveDatabase[movePtr++].piece = Q_BCASTL;
12265         quickBoard[sq] = piece;
12266         piece = quickBoard[from]; quickBoard[from] = 0;
12267         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12268       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12269         quickBoard[sq] = 0; // remove Rook
12270         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12271         moveDatabase[movePtr++].piece = Q_BCASTL;
12272         quickBoard[sq] = pieceList[2]; // put King
12273         piece = rook;
12274         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12275       }
12276     } else
12277     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12278         quickBoard[(fromY<<4)+toX] = 0;
12279         moveDatabase[movePtr].piece = Q_EP;
12280         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12281         moveDatabase[movePtr].to = sq;
12282     } else
12283     if(promoPiece != pieceType[piece]) {
12284         moveDatabase[movePtr++].piece = Q_PROMO;
12285         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12286     }
12287     moveDatabase[movePtr].piece = piece;
12288     quickBoard[sq] = piece;
12289     movePtr++;
12290 }
12291
12292 int
12293 PackGame (Board board)
12294 {
12295     Move *newSpace = NULL;
12296     moveDatabase[movePtr].piece = 0; // terminate previous game
12297     if(movePtr > dataSize) {
12298         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12299         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12300         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12301         if(newSpace) {
12302             int i;
12303             Move *p = moveDatabase, *q = newSpace;
12304             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12305             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12306             moveDatabase = newSpace;
12307         } else { // calloc failed, we must be out of memory. Too bad...
12308             dataSize = 0; // prevent calloc events for all subsequent games
12309             return 0;     // and signal this one isn't cached
12310         }
12311     }
12312     movePtr++;
12313     MakePieceList(board, counts);
12314     return movePtr;
12315 }
12316
12317 int
12318 QuickCompare (Board board, int *minCounts, int *maxCounts)
12319 {   // compare according to search mode
12320     int r, f;
12321     switch(appData.searchMode)
12322     {
12323       case 1: // exact position match
12324         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12325         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12326             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12327         }
12328         break;
12329       case 2: // can have extra material on empty squares
12330         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12331             if(board[r][f] == EmptySquare) continue;
12332             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12333         }
12334         break;
12335       case 3: // material with exact Pawn structure
12336         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12337             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12338             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12339         } // fall through to material comparison
12340       case 4: // exact material
12341         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12342         break;
12343       case 6: // material range with given imbalance
12344         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12345         // fall through to range comparison
12346       case 5: // material range
12347         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12348     }
12349     return TRUE;
12350 }
12351
12352 int
12353 QuickScan (Board board, Move *move)
12354 {   // reconstruct game,and compare all positions in it
12355     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12356     do {
12357         int piece = move->piece;
12358         int to = move->to, from = pieceList[piece];
12359         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12360           if(!piece) return -1;
12361           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12362             piece = (++move)->piece;
12363             from = pieceList[piece];
12364             counts[pieceType[piece]]--;
12365             pieceType[piece] = (ChessSquare) move->to;
12366             counts[move->to]++;
12367           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12368             counts[pieceType[quickBoard[to]]]--;
12369             quickBoard[to] = 0; total--;
12370             move++;
12371             continue;
12372           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12373             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12374             from  = pieceList[piece]; // so this must be King
12375             quickBoard[from] = 0;
12376             pieceList[piece] = to;
12377             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12378             quickBoard[from] = 0; // rook
12379             quickBoard[to] = piece;
12380             to = move->to; piece = move->piece;
12381             goto aftercastle;
12382           }
12383         }
12384         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12385         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12386         quickBoard[from] = 0;
12387       aftercastle:
12388         quickBoard[to] = piece;
12389         pieceList[piece] = to;
12390         cnt++; turn ^= 3;
12391         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12392            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12393            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12394                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12395           ) {
12396             static int lastCounts[EmptySquare+1];
12397             int i;
12398             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12399             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12400         } else stretch = 0;
12401         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12402         move++;
12403     } while(1);
12404 }
12405
12406 void
12407 InitSearch ()
12408 {
12409     int r, f;
12410     flipSearch = FALSE;
12411     CopyBoard(soughtBoard, boards[currentMove]);
12412     soughtTotal = MakePieceList(soughtBoard, maxSought);
12413     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12414     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12415     CopyBoard(reverseBoard, boards[currentMove]);
12416     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12417         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12418         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12419         reverseBoard[r][f] = piece;
12420     }
12421     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12422     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12423     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12424                  || (boards[currentMove][CASTLING][2] == NoRights ||
12425                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12426                  && (boards[currentMove][CASTLING][5] == NoRights ||
12427                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12428       ) {
12429         flipSearch = TRUE;
12430         CopyBoard(flipBoard, soughtBoard);
12431         CopyBoard(rotateBoard, reverseBoard);
12432         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12433             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12434             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12435         }
12436     }
12437     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12438     if(appData.searchMode >= 5) {
12439         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12440         MakePieceList(soughtBoard, minSought);
12441         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12442     }
12443     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12444         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12445 }
12446
12447 GameInfo dummyInfo;
12448 static int creatingBook;
12449
12450 int
12451 GameContainsPosition (FILE *f, ListGame *lg)
12452 {
12453     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12454     int fromX, fromY, toX, toY;
12455     char promoChar;
12456     static int initDone=FALSE;
12457
12458     // weed out games based on numerical tag comparison
12459     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12460     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12461     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12462     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12463     if(!initDone) {
12464         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12465         initDone = TRUE;
12466     }
12467     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12468     else CopyBoard(boards[scratch], initialPosition); // default start position
12469     if(lg->moves) {
12470         turn = btm + 1;
12471         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12472         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12473     }
12474     if(btm) plyNr++;
12475     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12476     fseek(f, lg->offset, 0);
12477     yynewfile(f);
12478     while(1) {
12479         yyboardindex = scratch;
12480         quickFlag = plyNr+1;
12481         next = Myylex();
12482         quickFlag = 0;
12483         switch(next) {
12484             case PGNTag:
12485                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12486             default:
12487                 continue;
12488
12489             case XBoardGame:
12490             case GNUChessGame:
12491                 if(plyNr) return -1; // after we have seen moves, this is for new game
12492               continue;
12493
12494             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12495             case ImpossibleMove:
12496             case WhiteWins: // game ends here with these four
12497             case BlackWins:
12498             case GameIsDrawn:
12499             case GameUnfinished:
12500                 return -1;
12501
12502             case IllegalMove:
12503                 if(appData.testLegality) return -1;
12504             case WhiteCapturesEnPassant:
12505             case BlackCapturesEnPassant:
12506             case WhitePromotion:
12507             case BlackPromotion:
12508             case WhiteNonPromotion:
12509             case BlackNonPromotion:
12510             case NormalMove:
12511             case FirstLeg:
12512             case WhiteKingSideCastle:
12513             case WhiteQueenSideCastle:
12514             case BlackKingSideCastle:
12515             case BlackQueenSideCastle:
12516             case WhiteKingSideCastleWild:
12517             case WhiteQueenSideCastleWild:
12518             case BlackKingSideCastleWild:
12519             case BlackQueenSideCastleWild:
12520             case WhiteHSideCastleFR:
12521             case WhiteASideCastleFR:
12522             case BlackHSideCastleFR:
12523             case BlackASideCastleFR:
12524                 fromX = currentMoveString[0] - AAA;
12525                 fromY = currentMoveString[1] - ONE;
12526                 toX = currentMoveString[2] - AAA;
12527                 toY = currentMoveString[3] - ONE;
12528                 promoChar = currentMoveString[4];
12529                 break;
12530             case WhiteDrop:
12531             case BlackDrop:
12532                 fromX = next == WhiteDrop ?
12533                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12534                   (int) CharToPiece(ToLower(currentMoveString[0]));
12535                 fromY = DROP_RANK;
12536                 toX = currentMoveString[2] - AAA;
12537                 toY = currentMoveString[3] - ONE;
12538                 promoChar = 0;
12539                 break;
12540         }
12541         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12542         plyNr++;
12543         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12544         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12545         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12546         if(appData.findMirror) {
12547             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12548             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12549         }
12550     }
12551 }
12552
12553 /* Load the nth game from open file f */
12554 int
12555 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12556 {
12557     ChessMove cm;
12558     char buf[MSG_SIZ];
12559     int gn = gameNumber;
12560     ListGame *lg = NULL;
12561     int numPGNTags = 0;
12562     int err, pos = -1;
12563     GameMode oldGameMode;
12564     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12565
12566     if (appData.debugMode)
12567         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12568
12569     if (gameMode == Training )
12570         SetTrainingModeOff();
12571
12572     oldGameMode = gameMode;
12573     if (gameMode != BeginningOfGame) {
12574       Reset(FALSE, TRUE);
12575     }
12576     killX = killY = -1; // [HGM] lion: in case we did not Reset
12577
12578     gameFileFP = f;
12579     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12580         fclose(lastLoadGameFP);
12581     }
12582
12583     if (useList) {
12584         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12585
12586         if (lg) {
12587             fseek(f, lg->offset, 0);
12588             GameListHighlight(gameNumber);
12589             pos = lg->position;
12590             gn = 1;
12591         }
12592         else {
12593             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12594               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12595             else
12596             DisplayError(_("Game number out of range"), 0);
12597             return FALSE;
12598         }
12599     } else {
12600         GameListDestroy();
12601         if (fseek(f, 0, 0) == -1) {
12602             if (f == lastLoadGameFP ?
12603                 gameNumber == lastLoadGameNumber + 1 :
12604                 gameNumber == 1) {
12605                 gn = 1;
12606             } else {
12607                 DisplayError(_("Can't seek on game file"), 0);
12608                 return FALSE;
12609             }
12610         }
12611     }
12612     lastLoadGameFP = f;
12613     lastLoadGameNumber = gameNumber;
12614     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12615     lastLoadGameUseList = useList;
12616
12617     yynewfile(f);
12618
12619     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12620       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12621                 lg->gameInfo.black);
12622             DisplayTitle(buf);
12623     } else if (*title != NULLCHAR) {
12624         if (gameNumber > 1) {
12625           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12626             DisplayTitle(buf);
12627         } else {
12628             DisplayTitle(title);
12629         }
12630     }
12631
12632     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12633         gameMode = PlayFromGameFile;
12634         ModeHighlight();
12635     }
12636
12637     currentMove = forwardMostMove = backwardMostMove = 0;
12638     CopyBoard(boards[0], initialPosition);
12639     StopClocks();
12640
12641     /*
12642      * Skip the first gn-1 games in the file.
12643      * Also skip over anything that precedes an identifiable
12644      * start of game marker, to avoid being confused by
12645      * garbage at the start of the file.  Currently
12646      * recognized start of game markers are the move number "1",
12647      * the pattern "gnuchess .* game", the pattern
12648      * "^[#;%] [^ ]* game file", and a PGN tag block.
12649      * A game that starts with one of the latter two patterns
12650      * will also have a move number 1, possibly
12651      * following a position diagram.
12652      * 5-4-02: Let's try being more lenient and allowing a game to
12653      * start with an unnumbered move.  Does that break anything?
12654      */
12655     cm = lastLoadGameStart = EndOfFile;
12656     while (gn > 0) {
12657         yyboardindex = forwardMostMove;
12658         cm = (ChessMove) Myylex();
12659         switch (cm) {
12660           case EndOfFile:
12661             if (cmailMsgLoaded) {
12662                 nCmailGames = CMAIL_MAX_GAMES - gn;
12663             } else {
12664                 Reset(TRUE, TRUE);
12665                 DisplayError(_("Game not found in file"), 0);
12666             }
12667             return FALSE;
12668
12669           case GNUChessGame:
12670           case XBoardGame:
12671             gn--;
12672             lastLoadGameStart = cm;
12673             break;
12674
12675           case MoveNumberOne:
12676             switch (lastLoadGameStart) {
12677               case GNUChessGame:
12678               case XBoardGame:
12679               case PGNTag:
12680                 break;
12681               case MoveNumberOne:
12682               case EndOfFile:
12683                 gn--;           /* count this game */
12684                 lastLoadGameStart = cm;
12685                 break;
12686               default:
12687                 /* impossible */
12688                 break;
12689             }
12690             break;
12691
12692           case PGNTag:
12693             switch (lastLoadGameStart) {
12694               case GNUChessGame:
12695               case PGNTag:
12696               case MoveNumberOne:
12697               case EndOfFile:
12698                 gn--;           /* count this game */
12699                 lastLoadGameStart = cm;
12700                 break;
12701               case XBoardGame:
12702                 lastLoadGameStart = cm; /* game counted already */
12703                 break;
12704               default:
12705                 /* impossible */
12706                 break;
12707             }
12708             if (gn > 0) {
12709                 do {
12710                     yyboardindex = forwardMostMove;
12711                     cm = (ChessMove) Myylex();
12712                 } while (cm == PGNTag || cm == Comment);
12713             }
12714             break;
12715
12716           case WhiteWins:
12717           case BlackWins:
12718           case GameIsDrawn:
12719             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12720                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12721                     != CMAIL_OLD_RESULT) {
12722                     nCmailResults ++ ;
12723                     cmailResult[  CMAIL_MAX_GAMES
12724                                 - gn - 1] = CMAIL_OLD_RESULT;
12725                 }
12726             }
12727             break;
12728
12729           case NormalMove:
12730           case FirstLeg:
12731             /* Only a NormalMove can be at the start of a game
12732              * without a position diagram. */
12733             if (lastLoadGameStart == EndOfFile ) {
12734               gn--;
12735               lastLoadGameStart = MoveNumberOne;
12736             }
12737             break;
12738
12739           default:
12740             break;
12741         }
12742     }
12743
12744     if (appData.debugMode)
12745       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12746
12747     if (cm == XBoardGame) {
12748         /* Skip any header junk before position diagram and/or move 1 */
12749         for (;;) {
12750             yyboardindex = forwardMostMove;
12751             cm = (ChessMove) Myylex();
12752
12753             if (cm == EndOfFile ||
12754                 cm == GNUChessGame || cm == XBoardGame) {
12755                 /* Empty game; pretend end-of-file and handle later */
12756                 cm = EndOfFile;
12757                 break;
12758             }
12759
12760             if (cm == MoveNumberOne || cm == PositionDiagram ||
12761                 cm == PGNTag || cm == Comment)
12762               break;
12763         }
12764     } else if (cm == GNUChessGame) {
12765         if (gameInfo.event != NULL) {
12766             free(gameInfo.event);
12767         }
12768         gameInfo.event = StrSave(yy_text);
12769     }
12770
12771     startedFromSetupPosition = FALSE;
12772     while (cm == PGNTag) {
12773         if (appData.debugMode)
12774           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12775         err = ParsePGNTag(yy_text, &gameInfo);
12776         if (!err) numPGNTags++;
12777
12778         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12779         if(gameInfo.variant != oldVariant) {
12780             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12781             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12782             InitPosition(TRUE);
12783             oldVariant = gameInfo.variant;
12784             if (appData.debugMode)
12785               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12786         }
12787
12788
12789         if (gameInfo.fen != NULL) {
12790           Board initial_position;
12791           startedFromSetupPosition = TRUE;
12792           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12793             Reset(TRUE, TRUE);
12794             DisplayError(_("Bad FEN position in file"), 0);
12795             return FALSE;
12796           }
12797           CopyBoard(boards[0], initial_position);
12798           if (blackPlaysFirst) {
12799             currentMove = forwardMostMove = backwardMostMove = 1;
12800             CopyBoard(boards[1], initial_position);
12801             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12802             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12803             timeRemaining[0][1] = whiteTimeRemaining;
12804             timeRemaining[1][1] = blackTimeRemaining;
12805             if (commentList[0] != NULL) {
12806               commentList[1] = commentList[0];
12807               commentList[0] = NULL;
12808             }
12809           } else {
12810             currentMove = forwardMostMove = backwardMostMove = 0;
12811           }
12812           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12813           {   int i;
12814               initialRulePlies = FENrulePlies;
12815               for( i=0; i< nrCastlingRights; i++ )
12816                   initialRights[i] = initial_position[CASTLING][i];
12817           }
12818           yyboardindex = forwardMostMove;
12819           free(gameInfo.fen);
12820           gameInfo.fen = NULL;
12821         }
12822
12823         yyboardindex = forwardMostMove;
12824         cm = (ChessMove) Myylex();
12825
12826         /* Handle comments interspersed among the tags */
12827         while (cm == Comment) {
12828             char *p;
12829             if (appData.debugMode)
12830               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12831             p = yy_text;
12832             AppendComment(currentMove, p, FALSE);
12833             yyboardindex = forwardMostMove;
12834             cm = (ChessMove) Myylex();
12835         }
12836     }
12837
12838     /* don't rely on existence of Event tag since if game was
12839      * pasted from clipboard the Event tag may not exist
12840      */
12841     if (numPGNTags > 0){
12842         char *tags;
12843         if (gameInfo.variant == VariantNormal) {
12844           VariantClass v = StringToVariant(gameInfo.event);
12845           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12846           if(v < VariantShogi) gameInfo.variant = v;
12847         }
12848         if (!matchMode) {
12849           if( appData.autoDisplayTags ) {
12850             tags = PGNTags(&gameInfo);
12851             TagsPopUp(tags, CmailMsg());
12852             free(tags);
12853           }
12854         }
12855     } else {
12856         /* Make something up, but don't display it now */
12857         SetGameInfo();
12858         TagsPopDown();
12859     }
12860
12861     if (cm == PositionDiagram) {
12862         int i, j;
12863         char *p;
12864         Board initial_position;
12865
12866         if (appData.debugMode)
12867           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12868
12869         if (!startedFromSetupPosition) {
12870             p = yy_text;
12871             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12872               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12873                 switch (*p) {
12874                   case '{':
12875                   case '[':
12876                   case '-':
12877                   case ' ':
12878                   case '\t':
12879                   case '\n':
12880                   case '\r':
12881                     break;
12882                   default:
12883                     initial_position[i][j++] = CharToPiece(*p);
12884                     break;
12885                 }
12886             while (*p == ' ' || *p == '\t' ||
12887                    *p == '\n' || *p == '\r') p++;
12888
12889             if (strncmp(p, "black", strlen("black"))==0)
12890               blackPlaysFirst = TRUE;
12891             else
12892               blackPlaysFirst = FALSE;
12893             startedFromSetupPosition = TRUE;
12894
12895             CopyBoard(boards[0], initial_position);
12896             if (blackPlaysFirst) {
12897                 currentMove = forwardMostMove = backwardMostMove = 1;
12898                 CopyBoard(boards[1], initial_position);
12899                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12900                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12901                 timeRemaining[0][1] = whiteTimeRemaining;
12902                 timeRemaining[1][1] = blackTimeRemaining;
12903                 if (commentList[0] != NULL) {
12904                     commentList[1] = commentList[0];
12905                     commentList[0] = NULL;
12906                 }
12907             } else {
12908                 currentMove = forwardMostMove = backwardMostMove = 0;
12909             }
12910         }
12911         yyboardindex = forwardMostMove;
12912         cm = (ChessMove) Myylex();
12913     }
12914
12915   if(!creatingBook) {
12916     if (first.pr == NoProc) {
12917         StartChessProgram(&first);
12918     }
12919     InitChessProgram(&first, FALSE);
12920     SendToProgram("force\n", &first);
12921     if (startedFromSetupPosition) {
12922         SendBoard(&first, forwardMostMove);
12923     if (appData.debugMode) {
12924         fprintf(debugFP, "Load Game\n");
12925     }
12926         DisplayBothClocks();
12927     }
12928   }
12929
12930     /* [HGM] server: flag to write setup moves in broadcast file as one */
12931     loadFlag = appData.suppressLoadMoves;
12932
12933     while (cm == Comment) {
12934         char *p;
12935         if (appData.debugMode)
12936           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12937         p = yy_text;
12938         AppendComment(currentMove, p, FALSE);
12939         yyboardindex = forwardMostMove;
12940         cm = (ChessMove) Myylex();
12941     }
12942
12943     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12944         cm == WhiteWins || cm == BlackWins ||
12945         cm == GameIsDrawn || cm == GameUnfinished) {
12946         DisplayMessage("", _("No moves in game"));
12947         if (cmailMsgLoaded) {
12948             if (appData.debugMode)
12949               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12950             ClearHighlights();
12951             flipView = FALSE;
12952         }
12953         DrawPosition(FALSE, boards[currentMove]);
12954         DisplayBothClocks();
12955         gameMode = EditGame;
12956         ModeHighlight();
12957         gameFileFP = NULL;
12958         cmailOldMove = 0;
12959         return TRUE;
12960     }
12961
12962     // [HGM] PV info: routine tests if comment empty
12963     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12964         DisplayComment(currentMove - 1, commentList[currentMove]);
12965     }
12966     if (!matchMode && appData.timeDelay != 0)
12967       DrawPosition(FALSE, boards[currentMove]);
12968
12969     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12970       programStats.ok_to_send = 1;
12971     }
12972
12973     /* if the first token after the PGN tags is a move
12974      * and not move number 1, retrieve it from the parser
12975      */
12976     if (cm != MoveNumberOne)
12977         LoadGameOneMove(cm);
12978
12979     /* load the remaining moves from the file */
12980     while (LoadGameOneMove(EndOfFile)) {
12981       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12982       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12983     }
12984
12985     /* rewind to the start of the game */
12986     currentMove = backwardMostMove;
12987
12988     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12989
12990     if (oldGameMode == AnalyzeFile) {
12991       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12992       AnalyzeFileEvent();
12993     } else
12994     if (oldGameMode == AnalyzeMode) {
12995       AnalyzeFileEvent();
12996     }
12997
12998     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12999         long int w, b; // [HGM] adjourn: restore saved clock times
13000         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13001         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13002             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13003             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13004         }
13005     }
13006
13007     if(creatingBook) return TRUE;
13008     if (!matchMode && pos > 0) {
13009         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13010     } else
13011     if (matchMode || appData.timeDelay == 0) {
13012       ToEndEvent();
13013     } else if (appData.timeDelay > 0) {
13014       AutoPlayGameLoop();
13015     }
13016
13017     if (appData.debugMode)
13018         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13019
13020     loadFlag = 0; /* [HGM] true game starts */
13021     return TRUE;
13022 }
13023
13024 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13025 int
13026 ReloadPosition (int offset)
13027 {
13028     int positionNumber = lastLoadPositionNumber + offset;
13029     if (lastLoadPositionFP == NULL) {
13030         DisplayError(_("No position has been loaded yet"), 0);
13031         return FALSE;
13032     }
13033     if (positionNumber <= 0) {
13034         DisplayError(_("Can't back up any further"), 0);
13035         return FALSE;
13036     }
13037     return LoadPosition(lastLoadPositionFP, positionNumber,
13038                         lastLoadPositionTitle);
13039 }
13040
13041 /* Load the nth position from the given file */
13042 int
13043 LoadPositionFromFile (char *filename, int n, char *title)
13044 {
13045     FILE *f;
13046     char buf[MSG_SIZ];
13047
13048     if (strcmp(filename, "-") == 0) {
13049         return LoadPosition(stdin, n, "stdin");
13050     } else {
13051         f = fopen(filename, "rb");
13052         if (f == NULL) {
13053             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13054             DisplayError(buf, errno);
13055             return FALSE;
13056         } else {
13057             return LoadPosition(f, n, title);
13058         }
13059     }
13060 }
13061
13062 /* Load the nth position from the given open file, and close it */
13063 int
13064 LoadPosition (FILE *f, int positionNumber, char *title)
13065 {
13066     char *p, line[MSG_SIZ];
13067     Board initial_position;
13068     int i, j, fenMode, pn;
13069
13070     if (gameMode == Training )
13071         SetTrainingModeOff();
13072
13073     if (gameMode != BeginningOfGame) {
13074         Reset(FALSE, TRUE);
13075     }
13076     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13077         fclose(lastLoadPositionFP);
13078     }
13079     if (positionNumber == 0) positionNumber = 1;
13080     lastLoadPositionFP = f;
13081     lastLoadPositionNumber = positionNumber;
13082     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13083     if (first.pr == NoProc && !appData.noChessProgram) {
13084       StartChessProgram(&first);
13085       InitChessProgram(&first, FALSE);
13086     }
13087     pn = positionNumber;
13088     if (positionNumber < 0) {
13089         /* Negative position number means to seek to that byte offset */
13090         if (fseek(f, -positionNumber, 0) == -1) {
13091             DisplayError(_("Can't seek on position file"), 0);
13092             return FALSE;
13093         };
13094         pn = 1;
13095     } else {
13096         if (fseek(f, 0, 0) == -1) {
13097             if (f == lastLoadPositionFP ?
13098                 positionNumber == lastLoadPositionNumber + 1 :
13099                 positionNumber == 1) {
13100                 pn = 1;
13101             } else {
13102                 DisplayError(_("Can't seek on position file"), 0);
13103                 return FALSE;
13104             }
13105         }
13106     }
13107     /* See if this file is FEN or old-style xboard */
13108     if (fgets(line, MSG_SIZ, f) == NULL) {
13109         DisplayError(_("Position not found in file"), 0);
13110         return FALSE;
13111     }
13112     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13113     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13114
13115     if (pn >= 2) {
13116         if (fenMode || line[0] == '#') pn--;
13117         while (pn > 0) {
13118             /* skip positions before number pn */
13119             if (fgets(line, MSG_SIZ, f) == NULL) {
13120                 Reset(TRUE, TRUE);
13121                 DisplayError(_("Position not found in file"), 0);
13122                 return FALSE;
13123             }
13124             if (fenMode || line[0] == '#') pn--;
13125         }
13126     }
13127
13128     if (fenMode) {
13129         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13130             DisplayError(_("Bad FEN position in file"), 0);
13131             return FALSE;
13132         }
13133     } else {
13134         (void) fgets(line, MSG_SIZ, f);
13135         (void) fgets(line, MSG_SIZ, f);
13136
13137         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13138             (void) fgets(line, MSG_SIZ, f);
13139             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13140                 if (*p == ' ')
13141                   continue;
13142                 initial_position[i][j++] = CharToPiece(*p);
13143             }
13144         }
13145
13146         blackPlaysFirst = FALSE;
13147         if (!feof(f)) {
13148             (void) fgets(line, MSG_SIZ, f);
13149             if (strncmp(line, "black", strlen("black"))==0)
13150               blackPlaysFirst = TRUE;
13151         }
13152     }
13153     startedFromSetupPosition = TRUE;
13154
13155     CopyBoard(boards[0], initial_position);
13156     if (blackPlaysFirst) {
13157         currentMove = forwardMostMove = backwardMostMove = 1;
13158         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13159         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13160         CopyBoard(boards[1], initial_position);
13161         DisplayMessage("", _("Black to play"));
13162     } else {
13163         currentMove = forwardMostMove = backwardMostMove = 0;
13164         DisplayMessage("", _("White to play"));
13165     }
13166     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13167     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13168         SendToProgram("force\n", &first);
13169         SendBoard(&first, forwardMostMove);
13170     }
13171     if (appData.debugMode) {
13172 int i, j;
13173   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13174   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13175         fprintf(debugFP, "Load Position\n");
13176     }
13177
13178     if (positionNumber > 1) {
13179       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13180         DisplayTitle(line);
13181     } else {
13182         DisplayTitle(title);
13183     }
13184     gameMode = EditGame;
13185     ModeHighlight();
13186     ResetClocks();
13187     timeRemaining[0][1] = whiteTimeRemaining;
13188     timeRemaining[1][1] = blackTimeRemaining;
13189     DrawPosition(FALSE, boards[currentMove]);
13190
13191     return TRUE;
13192 }
13193
13194
13195 void
13196 CopyPlayerNameIntoFileName (char **dest, char *src)
13197 {
13198     while (*src != NULLCHAR && *src != ',') {
13199         if (*src == ' ') {
13200             *(*dest)++ = '_';
13201             src++;
13202         } else {
13203             *(*dest)++ = *src++;
13204         }
13205     }
13206 }
13207
13208 char *
13209 DefaultFileName (char *ext)
13210 {
13211     static char def[MSG_SIZ];
13212     char *p;
13213
13214     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13215         p = def;
13216         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13217         *p++ = '-';
13218         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13219         *p++ = '.';
13220         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13221     } else {
13222         def[0] = NULLCHAR;
13223     }
13224     return def;
13225 }
13226
13227 /* Save the current game to the given file */
13228 int
13229 SaveGameToFile (char *filename, int append)
13230 {
13231     FILE *f;
13232     char buf[MSG_SIZ];
13233     int result, i, t,tot=0;
13234
13235     if (strcmp(filename, "-") == 0) {
13236         return SaveGame(stdout, 0, NULL);
13237     } else {
13238         for(i=0; i<10; i++) { // upto 10 tries
13239              f = fopen(filename, append ? "a" : "w");
13240              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13241              if(f || errno != 13) break;
13242              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13243              tot += t;
13244         }
13245         if (f == NULL) {
13246             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13247             DisplayError(buf, errno);
13248             return FALSE;
13249         } else {
13250             safeStrCpy(buf, lastMsg, MSG_SIZ);
13251             DisplayMessage(_("Waiting for access to save file"), "");
13252             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13253             DisplayMessage(_("Saving game"), "");
13254             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13255             result = SaveGame(f, 0, NULL);
13256             DisplayMessage(buf, "");
13257             return result;
13258         }
13259     }
13260 }
13261
13262 char *
13263 SavePart (char *str)
13264 {
13265     static char buf[MSG_SIZ];
13266     char *p;
13267
13268     p = strchr(str, ' ');
13269     if (p == NULL) return str;
13270     strncpy(buf, str, p - str);
13271     buf[p - str] = NULLCHAR;
13272     return buf;
13273 }
13274
13275 #define PGN_MAX_LINE 75
13276
13277 #define PGN_SIDE_WHITE  0
13278 #define PGN_SIDE_BLACK  1
13279
13280 static int
13281 FindFirstMoveOutOfBook (int side)
13282 {
13283     int result = -1;
13284
13285     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13286         int index = backwardMostMove;
13287         int has_book_hit = 0;
13288
13289         if( (index % 2) != side ) {
13290             index++;
13291         }
13292
13293         while( index < forwardMostMove ) {
13294             /* Check to see if engine is in book */
13295             int depth = pvInfoList[index].depth;
13296             int score = pvInfoList[index].score;
13297             int in_book = 0;
13298
13299             if( depth <= 2 ) {
13300                 in_book = 1;
13301             }
13302             else if( score == 0 && depth == 63 ) {
13303                 in_book = 1; /* Zappa */
13304             }
13305             else if( score == 2 && depth == 99 ) {
13306                 in_book = 1; /* Abrok */
13307             }
13308
13309             has_book_hit += in_book;
13310
13311             if( ! in_book ) {
13312                 result = index;
13313
13314                 break;
13315             }
13316
13317             index += 2;
13318         }
13319     }
13320
13321     return result;
13322 }
13323
13324 void
13325 GetOutOfBookInfo (char * buf)
13326 {
13327     int oob[2];
13328     int i;
13329     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13330
13331     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13332     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13333
13334     *buf = '\0';
13335
13336     if( oob[0] >= 0 || oob[1] >= 0 ) {
13337         for( i=0; i<2; i++ ) {
13338             int idx = oob[i];
13339
13340             if( idx >= 0 ) {
13341                 if( i > 0 && oob[0] >= 0 ) {
13342                     strcat( buf, "   " );
13343                 }
13344
13345                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13346                 sprintf( buf+strlen(buf), "%s%.2f",
13347                     pvInfoList[idx].score >= 0 ? "+" : "",
13348                     pvInfoList[idx].score / 100.0 );
13349             }
13350         }
13351     }
13352 }
13353
13354 /* Save game in PGN style and close the file */
13355 int
13356 SaveGamePGN (FILE *f)
13357 {
13358     int i, offset, linelen, newblock;
13359 //    char *movetext;
13360     char numtext[32];
13361     int movelen, numlen, blank;
13362     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13363
13364     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13365
13366     PrintPGNTags(f, &gameInfo);
13367
13368     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13369
13370     if (backwardMostMove > 0 || startedFromSetupPosition) {
13371         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13372         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13373         fprintf(f, "\n{--------------\n");
13374         PrintPosition(f, backwardMostMove);
13375         fprintf(f, "--------------}\n");
13376         free(fen);
13377     }
13378     else {
13379         /* [AS] Out of book annotation */
13380         if( appData.saveOutOfBookInfo ) {
13381             char buf[64];
13382
13383             GetOutOfBookInfo( buf );
13384
13385             if( buf[0] != '\0' ) {
13386                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13387             }
13388         }
13389
13390         fprintf(f, "\n");
13391     }
13392
13393     i = backwardMostMove;
13394     linelen = 0;
13395     newblock = TRUE;
13396
13397     while (i < forwardMostMove) {
13398         /* Print comments preceding this move */
13399         if (commentList[i] != NULL) {
13400             if (linelen > 0) fprintf(f, "\n");
13401             fprintf(f, "%s", commentList[i]);
13402             linelen = 0;
13403             newblock = TRUE;
13404         }
13405
13406         /* Format move number */
13407         if ((i % 2) == 0)
13408           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13409         else
13410           if (newblock)
13411             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13412           else
13413             numtext[0] = NULLCHAR;
13414
13415         numlen = strlen(numtext);
13416         newblock = FALSE;
13417
13418         /* Print move number */
13419         blank = linelen > 0 && numlen > 0;
13420         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13421             fprintf(f, "\n");
13422             linelen = 0;
13423             blank = 0;
13424         }
13425         if (blank) {
13426             fprintf(f, " ");
13427             linelen++;
13428         }
13429         fprintf(f, "%s", numtext);
13430         linelen += numlen;
13431
13432         /* Get move */
13433         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13434         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13435
13436         /* Print move */
13437         blank = linelen > 0 && movelen > 0;
13438         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13439             fprintf(f, "\n");
13440             linelen = 0;
13441             blank = 0;
13442         }
13443         if (blank) {
13444             fprintf(f, " ");
13445             linelen++;
13446         }
13447         fprintf(f, "%s", move_buffer);
13448         linelen += movelen;
13449
13450         /* [AS] Add PV info if present */
13451         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13452             /* [HGM] add time */
13453             char buf[MSG_SIZ]; int seconds;
13454
13455             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13456
13457             if( seconds <= 0)
13458               buf[0] = 0;
13459             else
13460               if( seconds < 30 )
13461                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13462               else
13463                 {
13464                   seconds = (seconds + 4)/10; // round to full seconds
13465                   if( seconds < 60 )
13466                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13467                   else
13468                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13469                 }
13470
13471             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13472                       pvInfoList[i].score >= 0 ? "+" : "",
13473                       pvInfoList[i].score / 100.0,
13474                       pvInfoList[i].depth,
13475                       buf );
13476
13477             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13478
13479             /* Print score/depth */
13480             blank = linelen > 0 && movelen > 0;
13481             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13482                 fprintf(f, "\n");
13483                 linelen = 0;
13484                 blank = 0;
13485             }
13486             if (blank) {
13487                 fprintf(f, " ");
13488                 linelen++;
13489             }
13490             fprintf(f, "%s", move_buffer);
13491             linelen += movelen;
13492         }
13493
13494         i++;
13495     }
13496
13497     /* Start a new line */
13498     if (linelen > 0) fprintf(f, "\n");
13499
13500     /* Print comments after last move */
13501     if (commentList[i] != NULL) {
13502         fprintf(f, "%s\n", commentList[i]);
13503     }
13504
13505     /* Print result */
13506     if (gameInfo.resultDetails != NULL &&
13507         gameInfo.resultDetails[0] != NULLCHAR) {
13508         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13509         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13510            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13511             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13512         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13513     } else {
13514         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13515     }
13516
13517     fclose(f);
13518     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13519     return TRUE;
13520 }
13521
13522 /* Save game in old style and close the file */
13523 int
13524 SaveGameOldStyle (FILE *f)
13525 {
13526     int i, offset;
13527     time_t tm;
13528
13529     tm = time((time_t *) NULL);
13530
13531     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13532     PrintOpponents(f);
13533
13534     if (backwardMostMove > 0 || startedFromSetupPosition) {
13535         fprintf(f, "\n[--------------\n");
13536         PrintPosition(f, backwardMostMove);
13537         fprintf(f, "--------------]\n");
13538     } else {
13539         fprintf(f, "\n");
13540     }
13541
13542     i = backwardMostMove;
13543     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13544
13545     while (i < forwardMostMove) {
13546         if (commentList[i] != NULL) {
13547             fprintf(f, "[%s]\n", commentList[i]);
13548         }
13549
13550         if ((i % 2) == 1) {
13551             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13552             i++;
13553         } else {
13554             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13555             i++;
13556             if (commentList[i] != NULL) {
13557                 fprintf(f, "\n");
13558                 continue;
13559             }
13560             if (i >= forwardMostMove) {
13561                 fprintf(f, "\n");
13562                 break;
13563             }
13564             fprintf(f, "%s\n", parseList[i]);
13565             i++;
13566         }
13567     }
13568
13569     if (commentList[i] != NULL) {
13570         fprintf(f, "[%s]\n", commentList[i]);
13571     }
13572
13573     /* This isn't really the old style, but it's close enough */
13574     if (gameInfo.resultDetails != NULL &&
13575         gameInfo.resultDetails[0] != NULLCHAR) {
13576         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13577                 gameInfo.resultDetails);
13578     } else {
13579         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13580     }
13581
13582     fclose(f);
13583     return TRUE;
13584 }
13585
13586 /* Save the current game to open file f and close the file */
13587 int
13588 SaveGame (FILE *f, int dummy, char *dummy2)
13589 {
13590     if (gameMode == EditPosition) EditPositionDone(TRUE);
13591     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13592     if (appData.oldSaveStyle)
13593       return SaveGameOldStyle(f);
13594     else
13595       return SaveGamePGN(f);
13596 }
13597
13598 /* Save the current position to the given file */
13599 int
13600 SavePositionToFile (char *filename)
13601 {
13602     FILE *f;
13603     char buf[MSG_SIZ];
13604
13605     if (strcmp(filename, "-") == 0) {
13606         return SavePosition(stdout, 0, NULL);
13607     } else {
13608         f = fopen(filename, "a");
13609         if (f == NULL) {
13610             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13611             DisplayError(buf, errno);
13612             return FALSE;
13613         } else {
13614             safeStrCpy(buf, lastMsg, MSG_SIZ);
13615             DisplayMessage(_("Waiting for access to save file"), "");
13616             flock(fileno(f), LOCK_EX); // [HGM] lock
13617             DisplayMessage(_("Saving position"), "");
13618             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13619             SavePosition(f, 0, NULL);
13620             DisplayMessage(buf, "");
13621             return TRUE;
13622         }
13623     }
13624 }
13625
13626 /* Save the current position to the given open file and close the file */
13627 int
13628 SavePosition (FILE *f, int dummy, char *dummy2)
13629 {
13630     time_t tm;
13631     char *fen;
13632
13633     if (gameMode == EditPosition) EditPositionDone(TRUE);
13634     if (appData.oldSaveStyle) {
13635         tm = time((time_t *) NULL);
13636
13637         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13638         PrintOpponents(f);
13639         fprintf(f, "[--------------\n");
13640         PrintPosition(f, currentMove);
13641         fprintf(f, "--------------]\n");
13642     } else {
13643         fen = PositionToFEN(currentMove, NULL, 1);
13644         fprintf(f, "%s\n", fen);
13645         free(fen);
13646     }
13647     fclose(f);
13648     return TRUE;
13649 }
13650
13651 void
13652 ReloadCmailMsgEvent (int unregister)
13653 {
13654 #if !WIN32
13655     static char *inFilename = NULL;
13656     static char *outFilename;
13657     int i;
13658     struct stat inbuf, outbuf;
13659     int status;
13660
13661     /* Any registered moves are unregistered if unregister is set, */
13662     /* i.e. invoked by the signal handler */
13663     if (unregister) {
13664         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13665             cmailMoveRegistered[i] = FALSE;
13666             if (cmailCommentList[i] != NULL) {
13667                 free(cmailCommentList[i]);
13668                 cmailCommentList[i] = NULL;
13669             }
13670         }
13671         nCmailMovesRegistered = 0;
13672     }
13673
13674     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13675         cmailResult[i] = CMAIL_NOT_RESULT;
13676     }
13677     nCmailResults = 0;
13678
13679     if (inFilename == NULL) {
13680         /* Because the filenames are static they only get malloced once  */
13681         /* and they never get freed                                      */
13682         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13683         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13684
13685         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13686         sprintf(outFilename, "%s.out", appData.cmailGameName);
13687     }
13688
13689     status = stat(outFilename, &outbuf);
13690     if (status < 0) {
13691         cmailMailedMove = FALSE;
13692     } else {
13693         status = stat(inFilename, &inbuf);
13694         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13695     }
13696
13697     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13698        counts the games, notes how each one terminated, etc.
13699
13700        It would be nice to remove this kludge and instead gather all
13701        the information while building the game list.  (And to keep it
13702        in the game list nodes instead of having a bunch of fixed-size
13703        parallel arrays.)  Note this will require getting each game's
13704        termination from the PGN tags, as the game list builder does
13705        not process the game moves.  --mann
13706        */
13707     cmailMsgLoaded = TRUE;
13708     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13709
13710     /* Load first game in the file or popup game menu */
13711     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13712
13713 #endif /* !WIN32 */
13714     return;
13715 }
13716
13717 int
13718 RegisterMove ()
13719 {
13720     FILE *f;
13721     char string[MSG_SIZ];
13722
13723     if (   cmailMailedMove
13724         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13725         return TRUE;            /* Allow free viewing  */
13726     }
13727
13728     /* Unregister move to ensure that we don't leave RegisterMove        */
13729     /* with the move registered when the conditions for registering no   */
13730     /* longer hold                                                       */
13731     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13732         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13733         nCmailMovesRegistered --;
13734
13735         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13736           {
13737               free(cmailCommentList[lastLoadGameNumber - 1]);
13738               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13739           }
13740     }
13741
13742     if (cmailOldMove == -1) {
13743         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13744         return FALSE;
13745     }
13746
13747     if (currentMove > cmailOldMove + 1) {
13748         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13749         return FALSE;
13750     }
13751
13752     if (currentMove < cmailOldMove) {
13753         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13754         return FALSE;
13755     }
13756
13757     if (forwardMostMove > currentMove) {
13758         /* Silently truncate extra moves */
13759         TruncateGame();
13760     }
13761
13762     if (   (currentMove == cmailOldMove + 1)
13763         || (   (currentMove == cmailOldMove)
13764             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13765                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13766         if (gameInfo.result != GameUnfinished) {
13767             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13768         }
13769
13770         if (commentList[currentMove] != NULL) {
13771             cmailCommentList[lastLoadGameNumber - 1]
13772               = StrSave(commentList[currentMove]);
13773         }
13774         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13775
13776         if (appData.debugMode)
13777           fprintf(debugFP, "Saving %s for game %d\n",
13778                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13779
13780         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13781
13782         f = fopen(string, "w");
13783         if (appData.oldSaveStyle) {
13784             SaveGameOldStyle(f); /* also closes the file */
13785
13786             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13787             f = fopen(string, "w");
13788             SavePosition(f, 0, NULL); /* also closes the file */
13789         } else {
13790             fprintf(f, "{--------------\n");
13791             PrintPosition(f, currentMove);
13792             fprintf(f, "--------------}\n\n");
13793
13794             SaveGame(f, 0, NULL); /* also closes the file*/
13795         }
13796
13797         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13798         nCmailMovesRegistered ++;
13799     } else if (nCmailGames == 1) {
13800         DisplayError(_("You have not made a move yet"), 0);
13801         return FALSE;
13802     }
13803
13804     return TRUE;
13805 }
13806
13807 void
13808 MailMoveEvent ()
13809 {
13810 #if !WIN32
13811     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13812     FILE *commandOutput;
13813     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13814     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13815     int nBuffers;
13816     int i;
13817     int archived;
13818     char *arcDir;
13819
13820     if (! cmailMsgLoaded) {
13821         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13822         return;
13823     }
13824
13825     if (nCmailGames == nCmailResults) {
13826         DisplayError(_("No unfinished games"), 0);
13827         return;
13828     }
13829
13830 #if CMAIL_PROHIBIT_REMAIL
13831     if (cmailMailedMove) {
13832       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);
13833         DisplayError(msg, 0);
13834         return;
13835     }
13836 #endif
13837
13838     if (! (cmailMailedMove || RegisterMove())) return;
13839
13840     if (   cmailMailedMove
13841         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13842       snprintf(string, MSG_SIZ, partCommandString,
13843                appData.debugMode ? " -v" : "", appData.cmailGameName);
13844         commandOutput = popen(string, "r");
13845
13846         if (commandOutput == NULL) {
13847             DisplayError(_("Failed to invoke cmail"), 0);
13848         } else {
13849             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13850                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13851             }
13852             if (nBuffers > 1) {
13853                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13854                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13855                 nBytes = MSG_SIZ - 1;
13856             } else {
13857                 (void) memcpy(msg, buffer, nBytes);
13858             }
13859             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13860
13861             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13862                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13863
13864                 archived = TRUE;
13865                 for (i = 0; i < nCmailGames; i ++) {
13866                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13867                         archived = FALSE;
13868                     }
13869                 }
13870                 if (   archived
13871                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13872                         != NULL)) {
13873                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13874                            arcDir,
13875                            appData.cmailGameName,
13876                            gameInfo.date);
13877                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13878                     cmailMsgLoaded = FALSE;
13879                 }
13880             }
13881
13882             DisplayInformation(msg);
13883             pclose(commandOutput);
13884         }
13885     } else {
13886         if ((*cmailMsg) != '\0') {
13887             DisplayInformation(cmailMsg);
13888         }
13889     }
13890
13891     return;
13892 #endif /* !WIN32 */
13893 }
13894
13895 char *
13896 CmailMsg ()
13897 {
13898 #if WIN32
13899     return NULL;
13900 #else
13901     int  prependComma = 0;
13902     char number[5];
13903     char string[MSG_SIZ];       /* Space for game-list */
13904     int  i;
13905
13906     if (!cmailMsgLoaded) return "";
13907
13908     if (cmailMailedMove) {
13909       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13910     } else {
13911         /* Create a list of games left */
13912       snprintf(string, MSG_SIZ, "[");
13913         for (i = 0; i < nCmailGames; i ++) {
13914             if (! (   cmailMoveRegistered[i]
13915                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13916                 if (prependComma) {
13917                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13918                 } else {
13919                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13920                     prependComma = 1;
13921                 }
13922
13923                 strcat(string, number);
13924             }
13925         }
13926         strcat(string, "]");
13927
13928         if (nCmailMovesRegistered + nCmailResults == 0) {
13929             switch (nCmailGames) {
13930               case 1:
13931                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13932                 break;
13933
13934               case 2:
13935                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13936                 break;
13937
13938               default:
13939                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13940                          nCmailGames);
13941                 break;
13942             }
13943         } else {
13944             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13945               case 1:
13946                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13947                          string);
13948                 break;
13949
13950               case 0:
13951                 if (nCmailResults == nCmailGames) {
13952                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13953                 } else {
13954                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13955                 }
13956                 break;
13957
13958               default:
13959                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13960                          string);
13961             }
13962         }
13963     }
13964     return cmailMsg;
13965 #endif /* WIN32 */
13966 }
13967
13968 void
13969 ResetGameEvent ()
13970 {
13971     if (gameMode == Training)
13972       SetTrainingModeOff();
13973
13974     Reset(TRUE, TRUE);
13975     cmailMsgLoaded = FALSE;
13976     if (appData.icsActive) {
13977       SendToICS(ics_prefix);
13978       SendToICS("refresh\n");
13979     }
13980 }
13981
13982 void
13983 ExitEvent (int status)
13984 {
13985     exiting++;
13986     if (exiting > 2) {
13987       /* Give up on clean exit */
13988       exit(status);
13989     }
13990     if (exiting > 1) {
13991       /* Keep trying for clean exit */
13992       return;
13993     }
13994
13995     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13996     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13997
13998     if (telnetISR != NULL) {
13999       RemoveInputSource(telnetISR);
14000     }
14001     if (icsPR != NoProc) {
14002       DestroyChildProcess(icsPR, TRUE);
14003     }
14004
14005     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14006     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14007
14008     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14009     /* make sure this other one finishes before killing it!                  */
14010     if(endingGame) { int count = 0;
14011         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14012         while(endingGame && count++ < 10) DoSleep(1);
14013         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14014     }
14015
14016     /* Kill off chess programs */
14017     if (first.pr != NoProc) {
14018         ExitAnalyzeMode();
14019
14020         DoSleep( appData.delayBeforeQuit );
14021         SendToProgram("quit\n", &first);
14022         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14023     }
14024     if (second.pr != NoProc) {
14025         DoSleep( appData.delayBeforeQuit );
14026         SendToProgram("quit\n", &second);
14027         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14028     }
14029     if (first.isr != NULL) {
14030         RemoveInputSource(first.isr);
14031     }
14032     if (second.isr != NULL) {
14033         RemoveInputSource(second.isr);
14034     }
14035
14036     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14037     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14038
14039     ShutDownFrontEnd();
14040     exit(status);
14041 }
14042
14043 void
14044 PauseEngine (ChessProgramState *cps)
14045 {
14046     SendToProgram("pause\n", cps);
14047     cps->pause = 2;
14048 }
14049
14050 void
14051 UnPauseEngine (ChessProgramState *cps)
14052 {
14053     SendToProgram("resume\n", cps);
14054     cps->pause = 1;
14055 }
14056
14057 void
14058 PauseEvent ()
14059 {
14060     if (appData.debugMode)
14061         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14062     if (pausing) {
14063         pausing = FALSE;
14064         ModeHighlight();
14065         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14066             StartClocks();
14067             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14068                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14069                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14070             }
14071             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14072             HandleMachineMove(stashedInputMove, stalledEngine);
14073             stalledEngine = NULL;
14074             return;
14075         }
14076         if (gameMode == MachinePlaysWhite ||
14077             gameMode == TwoMachinesPlay   ||
14078             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14079             if(first.pause)  UnPauseEngine(&first);
14080             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14081             if(second.pause) UnPauseEngine(&second);
14082             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14083             StartClocks();
14084         } else {
14085             DisplayBothClocks();
14086         }
14087         if (gameMode == PlayFromGameFile) {
14088             if (appData.timeDelay >= 0)
14089                 AutoPlayGameLoop();
14090         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14091             Reset(FALSE, TRUE);
14092             SendToICS(ics_prefix);
14093             SendToICS("refresh\n");
14094         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14095             ForwardInner(forwardMostMove);
14096         }
14097         pauseExamInvalid = FALSE;
14098     } else {
14099         switch (gameMode) {
14100           default:
14101             return;
14102           case IcsExamining:
14103             pauseExamForwardMostMove = forwardMostMove;
14104             pauseExamInvalid = FALSE;
14105             /* fall through */
14106           case IcsObserving:
14107           case IcsPlayingWhite:
14108           case IcsPlayingBlack:
14109             pausing = TRUE;
14110             ModeHighlight();
14111             return;
14112           case PlayFromGameFile:
14113             (void) StopLoadGameTimer();
14114             pausing = TRUE;
14115             ModeHighlight();
14116             break;
14117           case BeginningOfGame:
14118             if (appData.icsActive) return;
14119             /* else fall through */
14120           case MachinePlaysWhite:
14121           case MachinePlaysBlack:
14122           case TwoMachinesPlay:
14123             if (forwardMostMove == 0)
14124               return;           /* don't pause if no one has moved */
14125             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14126                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14127                 if(onMove->pause) {           // thinking engine can be paused
14128                     PauseEngine(onMove);      // do it
14129                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14130                         PauseEngine(onMove->other);
14131                     else
14132                         SendToProgram("easy\n", onMove->other);
14133                     StopClocks();
14134                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14135             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14136                 if(first.pause) {
14137                     PauseEngine(&first);
14138                     StopClocks();
14139                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14140             } else { // human on move, pause pondering by either method
14141                 if(first.pause)
14142                     PauseEngine(&first);
14143                 else if(appData.ponderNextMove)
14144                     SendToProgram("easy\n", &first);
14145                 StopClocks();
14146             }
14147             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14148           case AnalyzeMode:
14149             pausing = TRUE;
14150             ModeHighlight();
14151             break;
14152         }
14153     }
14154 }
14155
14156 void
14157 EditCommentEvent ()
14158 {
14159     char title[MSG_SIZ];
14160
14161     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14162       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14163     } else {
14164       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14165                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14166                parseList[currentMove - 1]);
14167     }
14168
14169     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14170 }
14171
14172
14173 void
14174 EditTagsEvent ()
14175 {
14176     char *tags = PGNTags(&gameInfo);
14177     bookUp = FALSE;
14178     EditTagsPopUp(tags, NULL);
14179     free(tags);
14180 }
14181
14182 void
14183 ToggleSecond ()
14184 {
14185   if(second.analyzing) {
14186     SendToProgram("exit\n", &second);
14187     second.analyzing = FALSE;
14188   } else {
14189     if (second.pr == NoProc) StartChessProgram(&second);
14190     InitChessProgram(&second, FALSE);
14191     FeedMovesToProgram(&second, currentMove);
14192
14193     SendToProgram("analyze\n", &second);
14194     second.analyzing = TRUE;
14195   }
14196 }
14197
14198 /* Toggle ShowThinking */
14199 void
14200 ToggleShowThinking()
14201 {
14202   appData.showThinking = !appData.showThinking;
14203   ShowThinkingEvent();
14204 }
14205
14206 int
14207 AnalyzeModeEvent ()
14208 {
14209     char buf[MSG_SIZ];
14210
14211     if (!first.analysisSupport) {
14212       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14213       DisplayError(buf, 0);
14214       return 0;
14215     }
14216     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14217     if (appData.icsActive) {
14218         if (gameMode != IcsObserving) {
14219           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14220             DisplayError(buf, 0);
14221             /* secure check */
14222             if (appData.icsEngineAnalyze) {
14223                 if (appData.debugMode)
14224                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14225                 ExitAnalyzeMode();
14226                 ModeHighlight();
14227             }
14228             return 0;
14229         }
14230         /* if enable, user wants to disable icsEngineAnalyze */
14231         if (appData.icsEngineAnalyze) {
14232                 ExitAnalyzeMode();
14233                 ModeHighlight();
14234                 return 0;
14235         }
14236         appData.icsEngineAnalyze = TRUE;
14237         if (appData.debugMode)
14238             fprintf(debugFP, "ICS engine analyze starting... \n");
14239     }
14240
14241     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14242     if (appData.noChessProgram || gameMode == AnalyzeMode)
14243       return 0;
14244
14245     if (gameMode != AnalyzeFile) {
14246         if (!appData.icsEngineAnalyze) {
14247                EditGameEvent();
14248                if (gameMode != EditGame) return 0;
14249         }
14250         if (!appData.showThinking) ToggleShowThinking();
14251         ResurrectChessProgram();
14252         SendToProgram("analyze\n", &first);
14253         first.analyzing = TRUE;
14254         /*first.maybeThinking = TRUE;*/
14255         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14256         EngineOutputPopUp();
14257     }
14258     if (!appData.icsEngineAnalyze) {
14259         gameMode = AnalyzeMode;
14260         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14261     }
14262     pausing = FALSE;
14263     ModeHighlight();
14264     SetGameInfo();
14265
14266     StartAnalysisClock();
14267     GetTimeMark(&lastNodeCountTime);
14268     lastNodeCount = 0;
14269     return 1;
14270 }
14271
14272 void
14273 AnalyzeFileEvent ()
14274 {
14275     if (appData.noChessProgram || gameMode == AnalyzeFile)
14276       return;
14277
14278     if (!first.analysisSupport) {
14279       char buf[MSG_SIZ];
14280       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14281       DisplayError(buf, 0);
14282       return;
14283     }
14284
14285     if (gameMode != AnalyzeMode) {
14286         keepInfo = 1; // mere annotating should not alter PGN tags
14287         EditGameEvent();
14288         keepInfo = 0;
14289         if (gameMode != EditGame) return;
14290         if (!appData.showThinking) ToggleShowThinking();
14291         ResurrectChessProgram();
14292         SendToProgram("analyze\n", &first);
14293         first.analyzing = TRUE;
14294         /*first.maybeThinking = TRUE;*/
14295         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14296         EngineOutputPopUp();
14297     }
14298     gameMode = AnalyzeFile;
14299     pausing = FALSE;
14300     ModeHighlight();
14301
14302     StartAnalysisClock();
14303     GetTimeMark(&lastNodeCountTime);
14304     lastNodeCount = 0;
14305     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14306     AnalysisPeriodicEvent(1);
14307 }
14308
14309 void
14310 MachineWhiteEvent ()
14311 {
14312     char buf[MSG_SIZ];
14313     char *bookHit = NULL;
14314
14315     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14316       return;
14317
14318
14319     if (gameMode == PlayFromGameFile ||
14320         gameMode == TwoMachinesPlay  ||
14321         gameMode == Training         ||
14322         gameMode == AnalyzeMode      ||
14323         gameMode == EndOfGame)
14324         EditGameEvent();
14325
14326     if (gameMode == EditPosition)
14327         EditPositionDone(TRUE);
14328
14329     if (!WhiteOnMove(currentMove)) {
14330         DisplayError(_("It is not White's turn"), 0);
14331         return;
14332     }
14333
14334     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14335       ExitAnalyzeMode();
14336
14337     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14338         gameMode == AnalyzeFile)
14339         TruncateGame();
14340
14341     ResurrectChessProgram();    /* in case it isn't running */
14342     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14343         gameMode = MachinePlaysWhite;
14344         ResetClocks();
14345     } else
14346     gameMode = MachinePlaysWhite;
14347     pausing = FALSE;
14348     ModeHighlight();
14349     SetGameInfo();
14350     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14351     DisplayTitle(buf);
14352     if (first.sendName) {
14353       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14354       SendToProgram(buf, &first);
14355     }
14356     if (first.sendTime) {
14357       if (first.useColors) {
14358         SendToProgram("black\n", &first); /*gnu kludge*/
14359       }
14360       SendTimeRemaining(&first, TRUE);
14361     }
14362     if (first.useColors) {
14363       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14364     }
14365     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14366     SetMachineThinkingEnables();
14367     first.maybeThinking = TRUE;
14368     StartClocks();
14369     firstMove = FALSE;
14370
14371     if (appData.autoFlipView && !flipView) {
14372       flipView = !flipView;
14373       DrawPosition(FALSE, NULL);
14374       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14375     }
14376
14377     if(bookHit) { // [HGM] book: simulate book reply
14378         static char bookMove[MSG_SIZ]; // a bit generous?
14379
14380         programStats.nodes = programStats.depth = programStats.time =
14381         programStats.score = programStats.got_only_move = 0;
14382         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14383
14384         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14385         strcat(bookMove, bookHit);
14386         HandleMachineMove(bookMove, &first);
14387     }
14388 }
14389
14390 void
14391 MachineBlackEvent ()
14392 {
14393   char buf[MSG_SIZ];
14394   char *bookHit = NULL;
14395
14396     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14397         return;
14398
14399
14400     if (gameMode == PlayFromGameFile ||
14401         gameMode == TwoMachinesPlay  ||
14402         gameMode == Training         ||
14403         gameMode == AnalyzeMode      ||
14404         gameMode == EndOfGame)
14405         EditGameEvent();
14406
14407     if (gameMode == EditPosition)
14408         EditPositionDone(TRUE);
14409
14410     if (WhiteOnMove(currentMove)) {
14411         DisplayError(_("It is not Black's turn"), 0);
14412         return;
14413     }
14414
14415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14416       ExitAnalyzeMode();
14417
14418     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14419         gameMode == AnalyzeFile)
14420         TruncateGame();
14421
14422     ResurrectChessProgram();    /* in case it isn't running */
14423     gameMode = MachinePlaysBlack;
14424     pausing = FALSE;
14425     ModeHighlight();
14426     SetGameInfo();
14427     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14428     DisplayTitle(buf);
14429     if (first.sendName) {
14430       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14431       SendToProgram(buf, &first);
14432     }
14433     if (first.sendTime) {
14434       if (first.useColors) {
14435         SendToProgram("white\n", &first); /*gnu kludge*/
14436       }
14437       SendTimeRemaining(&first, FALSE);
14438     }
14439     if (first.useColors) {
14440       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14441     }
14442     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14443     SetMachineThinkingEnables();
14444     first.maybeThinking = TRUE;
14445     StartClocks();
14446
14447     if (appData.autoFlipView && flipView) {
14448       flipView = !flipView;
14449       DrawPosition(FALSE, NULL);
14450       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14451     }
14452     if(bookHit) { // [HGM] book: simulate book reply
14453         static char bookMove[MSG_SIZ]; // a bit generous?
14454
14455         programStats.nodes = programStats.depth = programStats.time =
14456         programStats.score = programStats.got_only_move = 0;
14457         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14458
14459         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14460         strcat(bookMove, bookHit);
14461         HandleMachineMove(bookMove, &first);
14462     }
14463 }
14464
14465
14466 void
14467 DisplayTwoMachinesTitle ()
14468 {
14469     char buf[MSG_SIZ];
14470     if (appData.matchGames > 0) {
14471         if(appData.tourneyFile[0]) {
14472           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14473                    gameInfo.white, _("vs."), gameInfo.black,
14474                    nextGame+1, appData.matchGames+1,
14475                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14476         } else
14477         if (first.twoMachinesColor[0] == 'w') {
14478           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14479                    gameInfo.white, _("vs."),  gameInfo.black,
14480                    first.matchWins, second.matchWins,
14481                    matchGame - 1 - (first.matchWins + second.matchWins));
14482         } else {
14483           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14484                    gameInfo.white, _("vs."), gameInfo.black,
14485                    second.matchWins, first.matchWins,
14486                    matchGame - 1 - (first.matchWins + second.matchWins));
14487         }
14488     } else {
14489       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14490     }
14491     DisplayTitle(buf);
14492 }
14493
14494 void
14495 SettingsMenuIfReady ()
14496 {
14497   if (second.lastPing != second.lastPong) {
14498     DisplayMessage("", _("Waiting for second chess program"));
14499     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14500     return;
14501   }
14502   ThawUI();
14503   DisplayMessage("", "");
14504   SettingsPopUp(&second);
14505 }
14506
14507 int
14508 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14509 {
14510     char buf[MSG_SIZ];
14511     if (cps->pr == NoProc) {
14512         StartChessProgram(cps);
14513         if (cps->protocolVersion == 1) {
14514           retry();
14515           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14516         } else {
14517           /* kludge: allow timeout for initial "feature" command */
14518           if(retry != TwoMachinesEventIfReady) FreezeUI();
14519           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14520           DisplayMessage("", buf);
14521           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14522         }
14523         return 1;
14524     }
14525     return 0;
14526 }
14527
14528 void
14529 TwoMachinesEvent P((void))
14530 {
14531     int i;
14532     char buf[MSG_SIZ];
14533     ChessProgramState *onmove;
14534     char *bookHit = NULL;
14535     static int stalling = 0;
14536     TimeMark now;
14537     long wait;
14538
14539     if (appData.noChessProgram) return;
14540
14541     switch (gameMode) {
14542       case TwoMachinesPlay:
14543         return;
14544       case MachinePlaysWhite:
14545       case MachinePlaysBlack:
14546         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14547             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14548             return;
14549         }
14550         /* fall through */
14551       case BeginningOfGame:
14552       case PlayFromGameFile:
14553       case EndOfGame:
14554         EditGameEvent();
14555         if (gameMode != EditGame) return;
14556         break;
14557       case EditPosition:
14558         EditPositionDone(TRUE);
14559         break;
14560       case AnalyzeMode:
14561       case AnalyzeFile:
14562         ExitAnalyzeMode();
14563         break;
14564       case EditGame:
14565       default:
14566         break;
14567     }
14568
14569 //    forwardMostMove = currentMove;
14570     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14571     startingEngine = TRUE;
14572
14573     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14574
14575     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14576     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14577       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14578       return;
14579     }
14580     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14581
14582     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14583                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14584         startingEngine = FALSE;
14585         DisplayError("second engine does not play this", 0);
14586         return;
14587     }
14588
14589     if(!stalling) {
14590       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14591       SendToProgram("force\n", &second);
14592       stalling = 1;
14593       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14594       return;
14595     }
14596     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14597     if(appData.matchPause>10000 || appData.matchPause<10)
14598                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14599     wait = SubtractTimeMarks(&now, &pauseStart);
14600     if(wait < appData.matchPause) {
14601         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14602         return;
14603     }
14604     // we are now committed to starting the game
14605     stalling = 0;
14606     DisplayMessage("", "");
14607     if (startedFromSetupPosition) {
14608         SendBoard(&second, backwardMostMove);
14609     if (appData.debugMode) {
14610         fprintf(debugFP, "Two Machines\n");
14611     }
14612     }
14613     for (i = backwardMostMove; i < forwardMostMove; i++) {
14614         SendMoveToProgram(i, &second);
14615     }
14616
14617     gameMode = TwoMachinesPlay;
14618     pausing = startingEngine = FALSE;
14619     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14620     SetGameInfo();
14621     DisplayTwoMachinesTitle();
14622     firstMove = TRUE;
14623     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14624         onmove = &first;
14625     } else {
14626         onmove = &second;
14627     }
14628     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14629     SendToProgram(first.computerString, &first);
14630     if (first.sendName) {
14631       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14632       SendToProgram(buf, &first);
14633     }
14634     SendToProgram(second.computerString, &second);
14635     if (second.sendName) {
14636       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14637       SendToProgram(buf, &second);
14638     }
14639
14640     ResetClocks();
14641     if (!first.sendTime || !second.sendTime) {
14642         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14643         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14644     }
14645     if (onmove->sendTime) {
14646       if (onmove->useColors) {
14647         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14648       }
14649       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14650     }
14651     if (onmove->useColors) {
14652       SendToProgram(onmove->twoMachinesColor, onmove);
14653     }
14654     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14655 //    SendToProgram("go\n", onmove);
14656     onmove->maybeThinking = TRUE;
14657     SetMachineThinkingEnables();
14658
14659     StartClocks();
14660
14661     if(bookHit) { // [HGM] book: simulate book reply
14662         static char bookMove[MSG_SIZ]; // a bit generous?
14663
14664         programStats.nodes = programStats.depth = programStats.time =
14665         programStats.score = programStats.got_only_move = 0;
14666         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14667
14668         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14669         strcat(bookMove, bookHit);
14670         savedMessage = bookMove; // args for deferred call
14671         savedState = onmove;
14672         ScheduleDelayedEvent(DeferredBookMove, 1);
14673     }
14674 }
14675
14676 void
14677 TrainingEvent ()
14678 {
14679     if (gameMode == Training) {
14680       SetTrainingModeOff();
14681       gameMode = PlayFromGameFile;
14682       DisplayMessage("", _("Training mode off"));
14683     } else {
14684       gameMode = Training;
14685       animateTraining = appData.animate;
14686
14687       /* make sure we are not already at the end of the game */
14688       if (currentMove < forwardMostMove) {
14689         SetTrainingModeOn();
14690         DisplayMessage("", _("Training mode on"));
14691       } else {
14692         gameMode = PlayFromGameFile;
14693         DisplayError(_("Already at end of game"), 0);
14694       }
14695     }
14696     ModeHighlight();
14697 }
14698
14699 void
14700 IcsClientEvent ()
14701 {
14702     if (!appData.icsActive) return;
14703     switch (gameMode) {
14704       case IcsPlayingWhite:
14705       case IcsPlayingBlack:
14706       case IcsObserving:
14707       case IcsIdle:
14708       case BeginningOfGame:
14709       case IcsExamining:
14710         return;
14711
14712       case EditGame:
14713         break;
14714
14715       case EditPosition:
14716         EditPositionDone(TRUE);
14717         break;
14718
14719       case AnalyzeMode:
14720       case AnalyzeFile:
14721         ExitAnalyzeMode();
14722         break;
14723
14724       default:
14725         EditGameEvent();
14726         break;
14727     }
14728
14729     gameMode = IcsIdle;
14730     ModeHighlight();
14731     return;
14732 }
14733
14734 void
14735 EditGameEvent ()
14736 {
14737     int i;
14738
14739     switch (gameMode) {
14740       case Training:
14741         SetTrainingModeOff();
14742         break;
14743       case MachinePlaysWhite:
14744       case MachinePlaysBlack:
14745       case BeginningOfGame:
14746         SendToProgram("force\n", &first);
14747         SetUserThinkingEnables();
14748         break;
14749       case PlayFromGameFile:
14750         (void) StopLoadGameTimer();
14751         if (gameFileFP != NULL) {
14752             gameFileFP = NULL;
14753         }
14754         break;
14755       case EditPosition:
14756         EditPositionDone(TRUE);
14757         break;
14758       case AnalyzeMode:
14759       case AnalyzeFile:
14760         ExitAnalyzeMode();
14761         SendToProgram("force\n", &first);
14762         break;
14763       case TwoMachinesPlay:
14764         GameEnds(EndOfFile, NULL, GE_PLAYER);
14765         ResurrectChessProgram();
14766         SetUserThinkingEnables();
14767         break;
14768       case EndOfGame:
14769         ResurrectChessProgram();
14770         break;
14771       case IcsPlayingBlack:
14772       case IcsPlayingWhite:
14773         DisplayError(_("Warning: You are still playing a game"), 0);
14774         break;
14775       case IcsObserving:
14776         DisplayError(_("Warning: You are still observing a game"), 0);
14777         break;
14778       case IcsExamining:
14779         DisplayError(_("Warning: You are still examining a game"), 0);
14780         break;
14781       case IcsIdle:
14782         break;
14783       case EditGame:
14784       default:
14785         return;
14786     }
14787
14788     pausing = FALSE;
14789     StopClocks();
14790     first.offeredDraw = second.offeredDraw = 0;
14791
14792     if (gameMode == PlayFromGameFile) {
14793         whiteTimeRemaining = timeRemaining[0][currentMove];
14794         blackTimeRemaining = timeRemaining[1][currentMove];
14795         DisplayTitle("");
14796     }
14797
14798     if (gameMode == MachinePlaysWhite ||
14799         gameMode == MachinePlaysBlack ||
14800         gameMode == TwoMachinesPlay ||
14801         gameMode == EndOfGame) {
14802         i = forwardMostMove;
14803         while (i > currentMove) {
14804             SendToProgram("undo\n", &first);
14805             i--;
14806         }
14807         if(!adjustedClock) {
14808         whiteTimeRemaining = timeRemaining[0][currentMove];
14809         blackTimeRemaining = timeRemaining[1][currentMove];
14810         DisplayBothClocks();
14811         }
14812         if (whiteFlag || blackFlag) {
14813             whiteFlag = blackFlag = 0;
14814         }
14815         DisplayTitle("");
14816     }
14817
14818     gameMode = EditGame;
14819     ModeHighlight();
14820     SetGameInfo();
14821 }
14822
14823
14824 void
14825 EditPositionEvent ()
14826 {
14827     if (gameMode == EditPosition) {
14828         EditGameEvent();
14829         return;
14830     }
14831
14832     EditGameEvent();
14833     if (gameMode != EditGame) return;
14834
14835     gameMode = EditPosition;
14836     ModeHighlight();
14837     SetGameInfo();
14838     if (currentMove > 0)
14839       CopyBoard(boards[0], boards[currentMove]);
14840
14841     blackPlaysFirst = !WhiteOnMove(currentMove);
14842     ResetClocks();
14843     currentMove = forwardMostMove = backwardMostMove = 0;
14844     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14845     DisplayMove(-1);
14846     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14847 }
14848
14849 void
14850 ExitAnalyzeMode ()
14851 {
14852     /* [DM] icsEngineAnalyze - possible call from other functions */
14853     if (appData.icsEngineAnalyze) {
14854         appData.icsEngineAnalyze = FALSE;
14855
14856         DisplayMessage("",_("Close ICS engine analyze..."));
14857     }
14858     if (first.analysisSupport && first.analyzing) {
14859       SendToBoth("exit\n");
14860       first.analyzing = second.analyzing = FALSE;
14861     }
14862     thinkOutput[0] = NULLCHAR;
14863 }
14864
14865 void
14866 EditPositionDone (Boolean fakeRights)
14867 {
14868     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14869
14870     startedFromSetupPosition = TRUE;
14871     InitChessProgram(&first, FALSE);
14872     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14873       boards[0][EP_STATUS] = EP_NONE;
14874       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14875       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14876         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14877         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14878       } else boards[0][CASTLING][2] = NoRights;
14879       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14880         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14881         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14882       } else boards[0][CASTLING][5] = NoRights;
14883       if(gameInfo.variant == VariantSChess) {
14884         int i;
14885         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14886           boards[0][VIRGIN][i] = 0;
14887           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14888           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14889         }
14890       }
14891     }
14892     SendToProgram("force\n", &first);
14893     if (blackPlaysFirst) {
14894         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14895         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14896         currentMove = forwardMostMove = backwardMostMove = 1;
14897         CopyBoard(boards[1], boards[0]);
14898     } else {
14899         currentMove = forwardMostMove = backwardMostMove = 0;
14900     }
14901     SendBoard(&first, forwardMostMove);
14902     if (appData.debugMode) {
14903         fprintf(debugFP, "EditPosDone\n");
14904     }
14905     DisplayTitle("");
14906     DisplayMessage("", "");
14907     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14908     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14909     gameMode = EditGame;
14910     ModeHighlight();
14911     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14912     ClearHighlights(); /* [AS] */
14913 }
14914
14915 /* Pause for `ms' milliseconds */
14916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14917 void
14918 TimeDelay (long ms)
14919 {
14920     TimeMark m1, m2;
14921
14922     GetTimeMark(&m1);
14923     do {
14924         GetTimeMark(&m2);
14925     } while (SubtractTimeMarks(&m2, &m1) < ms);
14926 }
14927
14928 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14929 void
14930 SendMultiLineToICS (char *buf)
14931 {
14932     char temp[MSG_SIZ+1], *p;
14933     int len;
14934
14935     len = strlen(buf);
14936     if (len > MSG_SIZ)
14937       len = MSG_SIZ;
14938
14939     strncpy(temp, buf, len);
14940     temp[len] = 0;
14941
14942     p = temp;
14943     while (*p) {
14944         if (*p == '\n' || *p == '\r')
14945           *p = ' ';
14946         ++p;
14947     }
14948
14949     strcat(temp, "\n");
14950     SendToICS(temp);
14951     SendToPlayer(temp, strlen(temp));
14952 }
14953
14954 void
14955 SetWhiteToPlayEvent ()
14956 {
14957     if (gameMode == EditPosition) {
14958         blackPlaysFirst = FALSE;
14959         DisplayBothClocks();    /* works because currentMove is 0 */
14960     } else if (gameMode == IcsExamining) {
14961         SendToICS(ics_prefix);
14962         SendToICS("tomove white\n");
14963     }
14964 }
14965
14966 void
14967 SetBlackToPlayEvent ()
14968 {
14969     if (gameMode == EditPosition) {
14970         blackPlaysFirst = TRUE;
14971         currentMove = 1;        /* kludge */
14972         DisplayBothClocks();
14973         currentMove = 0;
14974     } else if (gameMode == IcsExamining) {
14975         SendToICS(ics_prefix);
14976         SendToICS("tomove black\n");
14977     }
14978 }
14979
14980 void
14981 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14982 {
14983     char buf[MSG_SIZ];
14984     ChessSquare piece = boards[0][y][x];
14985     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14986     static int lastVariant;
14987
14988     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14989
14990     switch (selection) {
14991       case ClearBoard:
14992         CopyBoard(currentBoard, boards[0]);
14993         CopyBoard(menuBoard, initialPosition);
14994         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14995             SendToICS(ics_prefix);
14996             SendToICS("bsetup clear\n");
14997         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14998             SendToICS(ics_prefix);
14999             SendToICS("clearboard\n");
15000         } else {
15001             int nonEmpty = 0;
15002             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15003                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15004                 for (y = 0; y < BOARD_HEIGHT; y++) {
15005                     if (gameMode == IcsExamining) {
15006                         if (boards[currentMove][y][x] != EmptySquare) {
15007                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15008                                     AAA + x, ONE + y);
15009                             SendToICS(buf);
15010                         }
15011                     } else {
15012                         if(boards[0][y][x] != p) nonEmpty++;
15013                         boards[0][y][x] = p;
15014                     }
15015                 }
15016                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
15017             }
15018             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15019                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15020                     ChessSquare p = menuBoard[0][x];
15021                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15022                     p = menuBoard[BOARD_HEIGHT-1][x];
15023                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15024                 }
15025                 DisplayMessage("Clicking clock again restores position", "");
15026                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15027                 if(!nonEmpty) { // asked to clear an empty board
15028                     CopyBoard(boards[0], menuBoard);
15029                 } else
15030                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15031                     CopyBoard(boards[0], initialPosition);
15032                 } else
15033                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15034                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15035                     CopyBoard(boards[0], erasedBoard);
15036                 } else
15037                     CopyBoard(erasedBoard, currentBoard);
15038
15039             }
15040         }
15041         if (gameMode == EditPosition) {
15042             DrawPosition(FALSE, boards[0]);
15043         }
15044         break;
15045
15046       case WhitePlay:
15047         SetWhiteToPlayEvent();
15048         break;
15049
15050       case BlackPlay:
15051         SetBlackToPlayEvent();
15052         break;
15053
15054       case EmptySquare:
15055         if (gameMode == IcsExamining) {
15056             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15057             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15058             SendToICS(buf);
15059         } else {
15060             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15061                 if(x == BOARD_LEFT-2) {
15062                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15063                     boards[0][y][1] = 0;
15064                 } else
15065                 if(x == BOARD_RGHT+1) {
15066                     if(y >= gameInfo.holdingsSize) break;
15067                     boards[0][y][BOARD_WIDTH-2] = 0;
15068                 } else break;
15069             }
15070             boards[0][y][x] = EmptySquare;
15071             DrawPosition(FALSE, boards[0]);
15072         }
15073         break;
15074
15075       case PromotePiece:
15076         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15077            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15078             selection = (ChessSquare) (PROMOTED piece);
15079         } else if(piece == EmptySquare) selection = WhiteSilver;
15080         else selection = (ChessSquare)((int)piece - 1);
15081         goto defaultlabel;
15082
15083       case DemotePiece:
15084         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15085            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15086             selection = (ChessSquare) (DEMOTED piece);
15087         } else if(piece == EmptySquare) selection = BlackSilver;
15088         else selection = (ChessSquare)((int)piece + 1);
15089         goto defaultlabel;
15090
15091       case WhiteQueen:
15092       case BlackQueen:
15093         if(gameInfo.variant == VariantShatranj ||
15094            gameInfo.variant == VariantXiangqi  ||
15095            gameInfo.variant == VariantCourier  ||
15096            gameInfo.variant == VariantASEAN    ||
15097            gameInfo.variant == VariantMakruk     )
15098             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15099         goto defaultlabel;
15100
15101       case WhiteKing:
15102       case BlackKing:
15103         if(gameInfo.variant == VariantXiangqi)
15104             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15105         if(gameInfo.variant == VariantKnightmate)
15106             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15107       default:
15108         defaultlabel:
15109         if (gameMode == IcsExamining) {
15110             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15111             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15112                      PieceToChar(selection), AAA + x, ONE + y);
15113             SendToICS(buf);
15114         } else {
15115             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15116                 int n;
15117                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15118                     n = PieceToNumber(selection - BlackPawn);
15119                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15120                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15121                     boards[0][BOARD_HEIGHT-1-n][1]++;
15122                 } else
15123                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15124                     n = PieceToNumber(selection);
15125                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15126                     boards[0][n][BOARD_WIDTH-1] = selection;
15127                     boards[0][n][BOARD_WIDTH-2]++;
15128                 }
15129             } else
15130             boards[0][y][x] = selection;
15131             DrawPosition(TRUE, boards[0]);
15132             ClearHighlights();
15133             fromX = fromY = -1;
15134         }
15135         break;
15136     }
15137 }
15138
15139
15140 void
15141 DropMenuEvent (ChessSquare selection, int x, int y)
15142 {
15143     ChessMove moveType;
15144
15145     switch (gameMode) {
15146       case IcsPlayingWhite:
15147       case MachinePlaysBlack:
15148         if (!WhiteOnMove(currentMove)) {
15149             DisplayMoveError(_("It is Black's turn"));
15150             return;
15151         }
15152         moveType = WhiteDrop;
15153         break;
15154       case IcsPlayingBlack:
15155       case MachinePlaysWhite:
15156         if (WhiteOnMove(currentMove)) {
15157             DisplayMoveError(_("It is White's turn"));
15158             return;
15159         }
15160         moveType = BlackDrop;
15161         break;
15162       case EditGame:
15163         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15164         break;
15165       default:
15166         return;
15167     }
15168
15169     if (moveType == BlackDrop && selection < BlackPawn) {
15170       selection = (ChessSquare) ((int) selection
15171                                  + (int) BlackPawn - (int) WhitePawn);
15172     }
15173     if (boards[currentMove][y][x] != EmptySquare) {
15174         DisplayMoveError(_("That square is occupied"));
15175         return;
15176     }
15177
15178     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15179 }
15180
15181 void
15182 AcceptEvent ()
15183 {
15184     /* Accept a pending offer of any kind from opponent */
15185
15186     if (appData.icsActive) {
15187         SendToICS(ics_prefix);
15188         SendToICS("accept\n");
15189     } else if (cmailMsgLoaded) {
15190         if (currentMove == cmailOldMove &&
15191             commentList[cmailOldMove] != NULL &&
15192             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15193                    "Black offers a draw" : "White offers a draw")) {
15194             TruncateGame();
15195             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15196             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15197         } else {
15198             DisplayError(_("There is no pending offer on this move"), 0);
15199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15200         }
15201     } else {
15202         /* Not used for offers from chess program */
15203     }
15204 }
15205
15206 void
15207 DeclineEvent ()
15208 {
15209     /* Decline a pending offer of any kind from opponent */
15210
15211     if (appData.icsActive) {
15212         SendToICS(ics_prefix);
15213         SendToICS("decline\n");
15214     } else if (cmailMsgLoaded) {
15215         if (currentMove == cmailOldMove &&
15216             commentList[cmailOldMove] != NULL &&
15217             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15218                    "Black offers a draw" : "White offers a draw")) {
15219 #ifdef NOTDEF
15220             AppendComment(cmailOldMove, "Draw declined", TRUE);
15221             DisplayComment(cmailOldMove - 1, "Draw declined");
15222 #endif /*NOTDEF*/
15223         } else {
15224             DisplayError(_("There is no pending offer on this move"), 0);
15225         }
15226     } else {
15227         /* Not used for offers from chess program */
15228     }
15229 }
15230
15231 void
15232 RematchEvent ()
15233 {
15234     /* Issue ICS rematch command */
15235     if (appData.icsActive) {
15236         SendToICS(ics_prefix);
15237         SendToICS("rematch\n");
15238     }
15239 }
15240
15241 void
15242 CallFlagEvent ()
15243 {
15244     /* Call your opponent's flag (claim a win on time) */
15245     if (appData.icsActive) {
15246         SendToICS(ics_prefix);
15247         SendToICS("flag\n");
15248     } else {
15249         switch (gameMode) {
15250           default:
15251             return;
15252           case MachinePlaysWhite:
15253             if (whiteFlag) {
15254                 if (blackFlag)
15255                   GameEnds(GameIsDrawn, "Both players ran out of time",
15256                            GE_PLAYER);
15257                 else
15258                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15259             } else {
15260                 DisplayError(_("Your opponent is not out of time"), 0);
15261             }
15262             break;
15263           case MachinePlaysBlack:
15264             if (blackFlag) {
15265                 if (whiteFlag)
15266                   GameEnds(GameIsDrawn, "Both players ran out of time",
15267                            GE_PLAYER);
15268                 else
15269                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15270             } else {
15271                 DisplayError(_("Your opponent is not out of time"), 0);
15272             }
15273             break;
15274         }
15275     }
15276 }
15277
15278 void
15279 ClockClick (int which)
15280 {       // [HGM] code moved to back-end from winboard.c
15281         if(which) { // black clock
15282           if (gameMode == EditPosition || gameMode == IcsExamining) {
15283             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15284             SetBlackToPlayEvent();
15285           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15286           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15287           } else if (shiftKey) {
15288             AdjustClock(which, -1);
15289           } else if (gameMode == IcsPlayingWhite ||
15290                      gameMode == MachinePlaysBlack) {
15291             CallFlagEvent();
15292           }
15293         } else { // white clock
15294           if (gameMode == EditPosition || gameMode == IcsExamining) {
15295             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15296             SetWhiteToPlayEvent();
15297           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15298           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15299           } else if (shiftKey) {
15300             AdjustClock(which, -1);
15301           } else if (gameMode == IcsPlayingBlack ||
15302                    gameMode == MachinePlaysWhite) {
15303             CallFlagEvent();
15304           }
15305         }
15306 }
15307
15308 void
15309 DrawEvent ()
15310 {
15311     /* Offer draw or accept pending draw offer from opponent */
15312
15313     if (appData.icsActive) {
15314         /* Note: tournament rules require draw offers to be
15315            made after you make your move but before you punch
15316            your clock.  Currently ICS doesn't let you do that;
15317            instead, you immediately punch your clock after making
15318            a move, but you can offer a draw at any time. */
15319
15320         SendToICS(ics_prefix);
15321         SendToICS("draw\n");
15322         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15323     } else if (cmailMsgLoaded) {
15324         if (currentMove == cmailOldMove &&
15325             commentList[cmailOldMove] != NULL &&
15326             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15327                    "Black offers a draw" : "White offers a draw")) {
15328             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15329             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15330         } else if (currentMove == cmailOldMove + 1) {
15331             char *offer = WhiteOnMove(cmailOldMove) ?
15332               "White offers a draw" : "Black offers a draw";
15333             AppendComment(currentMove, offer, TRUE);
15334             DisplayComment(currentMove - 1, offer);
15335             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15336         } else {
15337             DisplayError(_("You must make your move before offering a draw"), 0);
15338             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15339         }
15340     } else if (first.offeredDraw) {
15341         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15342     } else {
15343         if (first.sendDrawOffers) {
15344             SendToProgram("draw\n", &first);
15345             userOfferedDraw = TRUE;
15346         }
15347     }
15348 }
15349
15350 void
15351 AdjournEvent ()
15352 {
15353     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15354
15355     if (appData.icsActive) {
15356         SendToICS(ics_prefix);
15357         SendToICS("adjourn\n");
15358     } else {
15359         /* Currently GNU Chess doesn't offer or accept Adjourns */
15360     }
15361 }
15362
15363
15364 void
15365 AbortEvent ()
15366 {
15367     /* Offer Abort or accept pending Abort offer from opponent */
15368
15369     if (appData.icsActive) {
15370         SendToICS(ics_prefix);
15371         SendToICS("abort\n");
15372     } else {
15373         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15374     }
15375 }
15376
15377 void
15378 ResignEvent ()
15379 {
15380     /* Resign.  You can do this even if it's not your turn. */
15381
15382     if (appData.icsActive) {
15383         SendToICS(ics_prefix);
15384         SendToICS("resign\n");
15385     } else {
15386         switch (gameMode) {
15387           case MachinePlaysWhite:
15388             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15389             break;
15390           case MachinePlaysBlack:
15391             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15392             break;
15393           case EditGame:
15394             if (cmailMsgLoaded) {
15395                 TruncateGame();
15396                 if (WhiteOnMove(cmailOldMove)) {
15397                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15398                 } else {
15399                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15400                 }
15401                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15402             }
15403             break;
15404           default:
15405             break;
15406         }
15407     }
15408 }
15409
15410
15411 void
15412 StopObservingEvent ()
15413 {
15414     /* Stop observing current games */
15415     SendToICS(ics_prefix);
15416     SendToICS("unobserve\n");
15417 }
15418
15419 void
15420 StopExaminingEvent ()
15421 {
15422     /* Stop observing current game */
15423     SendToICS(ics_prefix);
15424     SendToICS("unexamine\n");
15425 }
15426
15427 void
15428 ForwardInner (int target)
15429 {
15430     int limit; int oldSeekGraphUp = seekGraphUp;
15431
15432     if (appData.debugMode)
15433         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15434                 target, currentMove, forwardMostMove);
15435
15436     if (gameMode == EditPosition)
15437       return;
15438
15439     seekGraphUp = FALSE;
15440     MarkTargetSquares(1);
15441
15442     if (gameMode == PlayFromGameFile && !pausing)
15443       PauseEvent();
15444
15445     if (gameMode == IcsExamining && pausing)
15446       limit = pauseExamForwardMostMove;
15447     else
15448       limit = forwardMostMove;
15449
15450     if (target > limit) target = limit;
15451
15452     if (target > 0 && moveList[target - 1][0]) {
15453         int fromX, fromY, toX, toY;
15454         toX = moveList[target - 1][2] - AAA;
15455         toY = moveList[target - 1][3] - ONE;
15456         if (moveList[target - 1][1] == '@') {
15457             if (appData.highlightLastMove) {
15458                 SetHighlights(-1, -1, toX, toY);
15459             }
15460         } else {
15461             fromX = moveList[target - 1][0] - AAA;
15462             fromY = moveList[target - 1][1] - ONE;
15463             if (target == currentMove + 1) {
15464                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15465             }
15466             if (appData.highlightLastMove) {
15467                 SetHighlights(fromX, fromY, toX, toY);
15468             }
15469         }
15470     }
15471     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15472         gameMode == Training || gameMode == PlayFromGameFile ||
15473         gameMode == AnalyzeFile) {
15474         while (currentMove < target) {
15475             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15476             SendMoveToProgram(currentMove++, &first);
15477         }
15478     } else {
15479         currentMove = target;
15480     }
15481
15482     if (gameMode == EditGame || gameMode == EndOfGame) {
15483         whiteTimeRemaining = timeRemaining[0][currentMove];
15484         blackTimeRemaining = timeRemaining[1][currentMove];
15485     }
15486     DisplayBothClocks();
15487     DisplayMove(currentMove - 1);
15488     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15489     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15490     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15491         DisplayComment(currentMove - 1, commentList[currentMove]);
15492     }
15493     ClearMap(); // [HGM] exclude: invalidate map
15494 }
15495
15496
15497 void
15498 ForwardEvent ()
15499 {
15500     if (gameMode == IcsExamining && !pausing) {
15501         SendToICS(ics_prefix);
15502         SendToICS("forward\n");
15503     } else {
15504         ForwardInner(currentMove + 1);
15505     }
15506 }
15507
15508 void
15509 ToEndEvent ()
15510 {
15511     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15512         /* to optimze, we temporarily turn off analysis mode while we feed
15513          * the remaining moves to the engine. Otherwise we get analysis output
15514          * after each move.
15515          */
15516         if (first.analysisSupport) {
15517           SendToProgram("exit\nforce\n", &first);
15518           first.analyzing = FALSE;
15519         }
15520     }
15521
15522     if (gameMode == IcsExamining && !pausing) {
15523         SendToICS(ics_prefix);
15524         SendToICS("forward 999999\n");
15525     } else {
15526         ForwardInner(forwardMostMove);
15527     }
15528
15529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15530         /* we have fed all the moves, so reactivate analysis mode */
15531         SendToProgram("analyze\n", &first);
15532         first.analyzing = TRUE;
15533         /*first.maybeThinking = TRUE;*/
15534         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15535     }
15536 }
15537
15538 void
15539 BackwardInner (int target)
15540 {
15541     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15542
15543     if (appData.debugMode)
15544         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15545                 target, currentMove, forwardMostMove);
15546
15547     if (gameMode == EditPosition) return;
15548     seekGraphUp = FALSE;
15549     MarkTargetSquares(1);
15550     if (currentMove <= backwardMostMove) {
15551         ClearHighlights();
15552         DrawPosition(full_redraw, boards[currentMove]);
15553         return;
15554     }
15555     if (gameMode == PlayFromGameFile && !pausing)
15556       PauseEvent();
15557
15558     if (moveList[target][0]) {
15559         int fromX, fromY, toX, toY;
15560         toX = moveList[target][2] - AAA;
15561         toY = moveList[target][3] - ONE;
15562         if (moveList[target][1] == '@') {
15563             if (appData.highlightLastMove) {
15564                 SetHighlights(-1, -1, toX, toY);
15565             }
15566         } else {
15567             fromX = moveList[target][0] - AAA;
15568             fromY = moveList[target][1] - ONE;
15569             if (target == currentMove - 1) {
15570                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15571             }
15572             if (appData.highlightLastMove) {
15573                 SetHighlights(fromX, fromY, toX, toY);
15574             }
15575         }
15576     }
15577     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15578         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15579         while (currentMove > target) {
15580             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15581                 // null move cannot be undone. Reload program with move history before it.
15582                 int i;
15583                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15584                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15585                 }
15586                 SendBoard(&first, i);
15587               if(second.analyzing) SendBoard(&second, i);
15588                 for(currentMove=i; currentMove<target; currentMove++) {
15589                     SendMoveToProgram(currentMove, &first);
15590                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15591                 }
15592                 break;
15593             }
15594             SendToBoth("undo\n");
15595             currentMove--;
15596         }
15597     } else {
15598         currentMove = target;
15599     }
15600
15601     if (gameMode == EditGame || gameMode == EndOfGame) {
15602         whiteTimeRemaining = timeRemaining[0][currentMove];
15603         blackTimeRemaining = timeRemaining[1][currentMove];
15604     }
15605     DisplayBothClocks();
15606     DisplayMove(currentMove - 1);
15607     DrawPosition(full_redraw, boards[currentMove]);
15608     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15609     // [HGM] PV info: routine tests if comment empty
15610     DisplayComment(currentMove - 1, commentList[currentMove]);
15611     ClearMap(); // [HGM] exclude: invalidate map
15612 }
15613
15614 void
15615 BackwardEvent ()
15616 {
15617     if (gameMode == IcsExamining && !pausing) {
15618         SendToICS(ics_prefix);
15619         SendToICS("backward\n");
15620     } else {
15621         BackwardInner(currentMove - 1);
15622     }
15623 }
15624
15625 void
15626 ToStartEvent ()
15627 {
15628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15629         /* to optimize, we temporarily turn off analysis mode while we undo
15630          * all the moves. Otherwise we get analysis output after each undo.
15631          */
15632         if (first.analysisSupport) {
15633           SendToProgram("exit\nforce\n", &first);
15634           first.analyzing = FALSE;
15635         }
15636     }
15637
15638     if (gameMode == IcsExamining && !pausing) {
15639         SendToICS(ics_prefix);
15640         SendToICS("backward 999999\n");
15641     } else {
15642         BackwardInner(backwardMostMove);
15643     }
15644
15645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15646         /* we have fed all the moves, so reactivate analysis mode */
15647         SendToProgram("analyze\n", &first);
15648         first.analyzing = TRUE;
15649         /*first.maybeThinking = TRUE;*/
15650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15651     }
15652 }
15653
15654 void
15655 ToNrEvent (int to)
15656 {
15657   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15658   if (to >= forwardMostMove) to = forwardMostMove;
15659   if (to <= backwardMostMove) to = backwardMostMove;
15660   if (to < currentMove) {
15661     BackwardInner(to);
15662   } else {
15663     ForwardInner(to);
15664   }
15665 }
15666
15667 void
15668 RevertEvent (Boolean annotate)
15669 {
15670     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15671         return;
15672     }
15673     if (gameMode != IcsExamining) {
15674         DisplayError(_("You are not examining a game"), 0);
15675         return;
15676     }
15677     if (pausing) {
15678         DisplayError(_("You can't revert while pausing"), 0);
15679         return;
15680     }
15681     SendToICS(ics_prefix);
15682     SendToICS("revert\n");
15683 }
15684
15685 void
15686 RetractMoveEvent ()
15687 {
15688     switch (gameMode) {
15689       case MachinePlaysWhite:
15690       case MachinePlaysBlack:
15691         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15692             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15693             return;
15694         }
15695         if (forwardMostMove < 2) return;
15696         currentMove = forwardMostMove = forwardMostMove - 2;
15697         whiteTimeRemaining = timeRemaining[0][currentMove];
15698         blackTimeRemaining = timeRemaining[1][currentMove];
15699         DisplayBothClocks();
15700         DisplayMove(currentMove - 1);
15701         ClearHighlights();/*!! could figure this out*/
15702         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15703         SendToProgram("remove\n", &first);
15704         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15705         break;
15706
15707       case BeginningOfGame:
15708       default:
15709         break;
15710
15711       case IcsPlayingWhite:
15712       case IcsPlayingBlack:
15713         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15714             SendToICS(ics_prefix);
15715             SendToICS("takeback 2\n");
15716         } else {
15717             SendToICS(ics_prefix);
15718             SendToICS("takeback 1\n");
15719         }
15720         break;
15721     }
15722 }
15723
15724 void
15725 MoveNowEvent ()
15726 {
15727     ChessProgramState *cps;
15728
15729     switch (gameMode) {
15730       case MachinePlaysWhite:
15731         if (!WhiteOnMove(forwardMostMove)) {
15732             DisplayError(_("It is your turn"), 0);
15733             return;
15734         }
15735         cps = &first;
15736         break;
15737       case MachinePlaysBlack:
15738         if (WhiteOnMove(forwardMostMove)) {
15739             DisplayError(_("It is your turn"), 0);
15740             return;
15741         }
15742         cps = &first;
15743         break;
15744       case TwoMachinesPlay:
15745         if (WhiteOnMove(forwardMostMove) ==
15746             (first.twoMachinesColor[0] == 'w')) {
15747             cps = &first;
15748         } else {
15749             cps = &second;
15750         }
15751         break;
15752       case BeginningOfGame:
15753       default:
15754         return;
15755     }
15756     SendToProgram("?\n", cps);
15757 }
15758
15759 void
15760 TruncateGameEvent ()
15761 {
15762     EditGameEvent();
15763     if (gameMode != EditGame) return;
15764     TruncateGame();
15765 }
15766
15767 void
15768 TruncateGame ()
15769 {
15770     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15771     if (forwardMostMove > currentMove) {
15772         if (gameInfo.resultDetails != NULL) {
15773             free(gameInfo.resultDetails);
15774             gameInfo.resultDetails = NULL;
15775             gameInfo.result = GameUnfinished;
15776         }
15777         forwardMostMove = currentMove;
15778         HistorySet(parseList, backwardMostMove, forwardMostMove,
15779                    currentMove-1);
15780     }
15781 }
15782
15783 void
15784 HintEvent ()
15785 {
15786     if (appData.noChessProgram) return;
15787     switch (gameMode) {
15788       case MachinePlaysWhite:
15789         if (WhiteOnMove(forwardMostMove)) {
15790             DisplayError(_("Wait until your turn."), 0);
15791             return;
15792         }
15793         break;
15794       case BeginningOfGame:
15795       case MachinePlaysBlack:
15796         if (!WhiteOnMove(forwardMostMove)) {
15797             DisplayError(_("Wait until your turn."), 0);
15798             return;
15799         }
15800         break;
15801       default:
15802         DisplayError(_("No hint available"), 0);
15803         return;
15804     }
15805     SendToProgram("hint\n", &first);
15806     hintRequested = TRUE;
15807 }
15808
15809 void
15810 CreateBookEvent ()
15811 {
15812     ListGame * lg = (ListGame *) gameList.head;
15813     FILE *f, *g;
15814     int nItem;
15815     static int secondTime = FALSE;
15816
15817     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15818         DisplayError(_("Game list not loaded or empty"), 0);
15819         return;
15820     }
15821
15822     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15823         fclose(g);
15824         secondTime++;
15825         DisplayNote(_("Book file exists! Try again for overwrite."));
15826         return;
15827     }
15828
15829     creatingBook = TRUE;
15830     secondTime = FALSE;
15831
15832     /* Get list size */
15833     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15834         LoadGame(f, nItem, "", TRUE);
15835         AddGameToBook(TRUE);
15836         lg = (ListGame *) lg->node.succ;
15837     }
15838
15839     creatingBook = FALSE;
15840     FlushBook();
15841 }
15842
15843 void
15844 BookEvent ()
15845 {
15846     if (appData.noChessProgram) return;
15847     switch (gameMode) {
15848       case MachinePlaysWhite:
15849         if (WhiteOnMove(forwardMostMove)) {
15850             DisplayError(_("Wait until your turn."), 0);
15851             return;
15852         }
15853         break;
15854       case BeginningOfGame:
15855       case MachinePlaysBlack:
15856         if (!WhiteOnMove(forwardMostMove)) {
15857             DisplayError(_("Wait until your turn."), 0);
15858             return;
15859         }
15860         break;
15861       case EditPosition:
15862         EditPositionDone(TRUE);
15863         break;
15864       case TwoMachinesPlay:
15865         return;
15866       default:
15867         break;
15868     }
15869     SendToProgram("bk\n", &first);
15870     bookOutput[0] = NULLCHAR;
15871     bookRequested = TRUE;
15872 }
15873
15874 void
15875 AboutGameEvent ()
15876 {
15877     char *tags = PGNTags(&gameInfo);
15878     TagsPopUp(tags, CmailMsg());
15879     free(tags);
15880 }
15881
15882 /* end button procedures */
15883
15884 void
15885 PrintPosition (FILE *fp, int move)
15886 {
15887     int i, j;
15888
15889     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15890         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15891             char c = PieceToChar(boards[move][i][j]);
15892             fputc(c == 'x' ? '.' : c, fp);
15893             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15894         }
15895     }
15896     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15897       fprintf(fp, "white to play\n");
15898     else
15899       fprintf(fp, "black to play\n");
15900 }
15901
15902 void
15903 PrintOpponents (FILE *fp)
15904 {
15905     if (gameInfo.white != NULL) {
15906         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15907     } else {
15908         fprintf(fp, "\n");
15909     }
15910 }
15911
15912 /* Find last component of program's own name, using some heuristics */
15913 void
15914 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15915 {
15916     char *p, *q, c;
15917     int local = (strcmp(host, "localhost") == 0);
15918     while (!local && (p = strchr(prog, ';')) != NULL) {
15919         p++;
15920         while (*p == ' ') p++;
15921         prog = p;
15922     }
15923     if (*prog == '"' || *prog == '\'') {
15924         q = strchr(prog + 1, *prog);
15925     } else {
15926         q = strchr(prog, ' ');
15927     }
15928     if (q == NULL) q = prog + strlen(prog);
15929     p = q;
15930     while (p >= prog && *p != '/' && *p != '\\') p--;
15931     p++;
15932     if(p == prog && *p == '"') p++;
15933     c = *q; *q = 0;
15934     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15935     memcpy(buf, p, q - p);
15936     buf[q - p] = NULLCHAR;
15937     if (!local) {
15938         strcat(buf, "@");
15939         strcat(buf, host);
15940     }
15941 }
15942
15943 char *
15944 TimeControlTagValue ()
15945 {
15946     char buf[MSG_SIZ];
15947     if (!appData.clockMode) {
15948       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15949     } else if (movesPerSession > 0) {
15950       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15951     } else if (timeIncrement == 0) {
15952       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15953     } else {
15954       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15955     }
15956     return StrSave(buf);
15957 }
15958
15959 void
15960 SetGameInfo ()
15961 {
15962     /* This routine is used only for certain modes */
15963     VariantClass v = gameInfo.variant;
15964     ChessMove r = GameUnfinished;
15965     char *p = NULL;
15966
15967     if(keepInfo) return;
15968
15969     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15970         r = gameInfo.result;
15971         p = gameInfo.resultDetails;
15972         gameInfo.resultDetails = NULL;
15973     }
15974     ClearGameInfo(&gameInfo);
15975     gameInfo.variant = v;
15976
15977     switch (gameMode) {
15978       case MachinePlaysWhite:
15979         gameInfo.event = StrSave( appData.pgnEventHeader );
15980         gameInfo.site = StrSave(HostName());
15981         gameInfo.date = PGNDate();
15982         gameInfo.round = StrSave("-");
15983         gameInfo.white = StrSave(first.tidy);
15984         gameInfo.black = StrSave(UserName());
15985         gameInfo.timeControl = TimeControlTagValue();
15986         break;
15987
15988       case MachinePlaysBlack:
15989         gameInfo.event = StrSave( appData.pgnEventHeader );
15990         gameInfo.site = StrSave(HostName());
15991         gameInfo.date = PGNDate();
15992         gameInfo.round = StrSave("-");
15993         gameInfo.white = StrSave(UserName());
15994         gameInfo.black = StrSave(first.tidy);
15995         gameInfo.timeControl = TimeControlTagValue();
15996         break;
15997
15998       case TwoMachinesPlay:
15999         gameInfo.event = StrSave( appData.pgnEventHeader );
16000         gameInfo.site = StrSave(HostName());
16001         gameInfo.date = PGNDate();
16002         if (roundNr > 0) {
16003             char buf[MSG_SIZ];
16004             snprintf(buf, MSG_SIZ, "%d", roundNr);
16005             gameInfo.round = StrSave(buf);
16006         } else {
16007             gameInfo.round = StrSave("-");
16008         }
16009         if (first.twoMachinesColor[0] == 'w') {
16010             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16011             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16012         } else {
16013             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16014             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16015         }
16016         gameInfo.timeControl = TimeControlTagValue();
16017         break;
16018
16019       case EditGame:
16020         gameInfo.event = StrSave("Edited game");
16021         gameInfo.site = StrSave(HostName());
16022         gameInfo.date = PGNDate();
16023         gameInfo.round = StrSave("-");
16024         gameInfo.white = StrSave("-");
16025         gameInfo.black = StrSave("-");
16026         gameInfo.result = r;
16027         gameInfo.resultDetails = p;
16028         break;
16029
16030       case EditPosition:
16031         gameInfo.event = StrSave("Edited position");
16032         gameInfo.site = StrSave(HostName());
16033         gameInfo.date = PGNDate();
16034         gameInfo.round = StrSave("-");
16035         gameInfo.white = StrSave("-");
16036         gameInfo.black = StrSave("-");
16037         break;
16038
16039       case IcsPlayingWhite:
16040       case IcsPlayingBlack:
16041       case IcsObserving:
16042       case IcsExamining:
16043         break;
16044
16045       case PlayFromGameFile:
16046         gameInfo.event = StrSave("Game from non-PGN file");
16047         gameInfo.site = StrSave(HostName());
16048         gameInfo.date = PGNDate();
16049         gameInfo.round = StrSave("-");
16050         gameInfo.white = StrSave("?");
16051         gameInfo.black = StrSave("?");
16052         break;
16053
16054       default:
16055         break;
16056     }
16057 }
16058
16059 void
16060 ReplaceComment (int index, char *text)
16061 {
16062     int len;
16063     char *p;
16064     float score;
16065
16066     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16067        pvInfoList[index-1].depth == len &&
16068        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16069        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16070     while (*text == '\n') text++;
16071     len = strlen(text);
16072     while (len > 0 && text[len - 1] == '\n') len--;
16073
16074     if (commentList[index] != NULL)
16075       free(commentList[index]);
16076
16077     if (len == 0) {
16078         commentList[index] = NULL;
16079         return;
16080     }
16081   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16082       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16083       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16084     commentList[index] = (char *) malloc(len + 2);
16085     strncpy(commentList[index], text, len);
16086     commentList[index][len] = '\n';
16087     commentList[index][len + 1] = NULLCHAR;
16088   } else {
16089     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16090     char *p;
16091     commentList[index] = (char *) malloc(len + 7);
16092     safeStrCpy(commentList[index], "{\n", 3);
16093     safeStrCpy(commentList[index]+2, text, len+1);
16094     commentList[index][len+2] = NULLCHAR;
16095     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16096     strcat(commentList[index], "\n}\n");
16097   }
16098 }
16099
16100 void
16101 CrushCRs (char *text)
16102 {
16103   char *p = text;
16104   char *q = text;
16105   char ch;
16106
16107   do {
16108     ch = *p++;
16109     if (ch == '\r') continue;
16110     *q++ = ch;
16111   } while (ch != '\0');
16112 }
16113
16114 void
16115 AppendComment (int index, char *text, Boolean addBraces)
16116 /* addBraces  tells if we should add {} */
16117 {
16118     int oldlen, len;
16119     char *old;
16120
16121 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16122     if(addBraces == 3) addBraces = 0; else // force appending literally
16123     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16124
16125     CrushCRs(text);
16126     while (*text == '\n') text++;
16127     len = strlen(text);
16128     while (len > 0 && text[len - 1] == '\n') len--;
16129     text[len] = NULLCHAR;
16130
16131     if (len == 0) return;
16132
16133     if (commentList[index] != NULL) {
16134       Boolean addClosingBrace = addBraces;
16135         old = commentList[index];
16136         oldlen = strlen(old);
16137         while(commentList[index][oldlen-1] ==  '\n')
16138           commentList[index][--oldlen] = NULLCHAR;
16139         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16140         safeStrCpy(commentList[index], old, oldlen + len + 6);
16141         free(old);
16142         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16143         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16144           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16145           while (*text == '\n') { text++; len--; }
16146           commentList[index][--oldlen] = NULLCHAR;
16147       }
16148         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16149         else          strcat(commentList[index], "\n");
16150         strcat(commentList[index], text);
16151         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16152         else          strcat(commentList[index], "\n");
16153     } else {
16154         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16155         if(addBraces)
16156           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16157         else commentList[index][0] = NULLCHAR;
16158         strcat(commentList[index], text);
16159         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16160         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16161     }
16162 }
16163
16164 static char *
16165 FindStr (char * text, char * sub_text)
16166 {
16167     char * result = strstr( text, sub_text );
16168
16169     if( result != NULL ) {
16170         result += strlen( sub_text );
16171     }
16172
16173     return result;
16174 }
16175
16176 /* [AS] Try to extract PV info from PGN comment */
16177 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16178 char *
16179 GetInfoFromComment (int index, char * text)
16180 {
16181     char * sep = text, *p;
16182
16183     if( text != NULL && index > 0 ) {
16184         int score = 0;
16185         int depth = 0;
16186         int time = -1, sec = 0, deci;
16187         char * s_eval = FindStr( text, "[%eval " );
16188         char * s_emt = FindStr( text, "[%emt " );
16189 #if 0
16190         if( s_eval != NULL || s_emt != NULL ) {
16191 #else
16192         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16193 #endif
16194             /* New style */
16195             char delim;
16196
16197             if( s_eval != NULL ) {
16198                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16199                     return text;
16200                 }
16201
16202                 if( delim != ']' ) {
16203                     return text;
16204                 }
16205             }
16206
16207             if( s_emt != NULL ) {
16208             }
16209                 return text;
16210         }
16211         else {
16212             /* We expect something like: [+|-]nnn.nn/dd */
16213             int score_lo = 0;
16214
16215             if(*text != '{') return text; // [HGM] braces: must be normal comment
16216
16217             sep = strchr( text, '/' );
16218             if( sep == NULL || sep < (text+4) ) {
16219                 return text;
16220             }
16221
16222             p = text;
16223             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16224             if(p[1] == '(') { // comment starts with PV
16225                p = strchr(p, ')'); // locate end of PV
16226                if(p == NULL || sep < p+5) return text;
16227                // at this point we have something like "{(.*) +0.23/6 ..."
16228                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16229                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16230                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16231             }
16232             time = -1; sec = -1; deci = -1;
16233             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16234                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16235                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16236                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16237                 return text;
16238             }
16239
16240             if( score_lo < 0 || score_lo >= 100 ) {
16241                 return text;
16242             }
16243
16244             if(sec >= 0) time = 600*time + 10*sec; else
16245             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16246
16247             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16248
16249             /* [HGM] PV time: now locate end of PV info */
16250             while( *++sep >= '0' && *sep <= '9'); // strip depth
16251             if(time >= 0)
16252             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16253             if(sec >= 0)
16254             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16255             if(deci >= 0)
16256             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16257             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16258         }
16259
16260         if( depth <= 0 ) {
16261             return text;
16262         }
16263
16264         if( time < 0 ) {
16265             time = -1;
16266         }
16267
16268         pvInfoList[index-1].depth = depth;
16269         pvInfoList[index-1].score = score;
16270         pvInfoList[index-1].time  = 10*time; // centi-sec
16271         if(*sep == '}') *sep = 0; else *--sep = '{';
16272         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16273     }
16274     return sep;
16275 }
16276
16277 void
16278 SendToProgram (char *message, ChessProgramState *cps)
16279 {
16280     int count, outCount, error;
16281     char buf[MSG_SIZ];
16282
16283     if (cps->pr == NoProc) return;
16284     Attention(cps);
16285
16286     if (appData.debugMode) {
16287         TimeMark now;
16288         GetTimeMark(&now);
16289         fprintf(debugFP, "%ld >%-6s: %s",
16290                 SubtractTimeMarks(&now, &programStartTime),
16291                 cps->which, message);
16292         if(serverFP)
16293             fprintf(serverFP, "%ld >%-6s: %s",
16294                 SubtractTimeMarks(&now, &programStartTime),
16295                 cps->which, message), fflush(serverFP);
16296     }
16297
16298     count = strlen(message);
16299     outCount = OutputToProcess(cps->pr, message, count, &error);
16300     if (outCount < count && !exiting
16301                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16302       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16303       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16304         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16305             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16306                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16307                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16308                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16309             } else {
16310                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16311                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16312                 gameInfo.result = res;
16313             }
16314             gameInfo.resultDetails = StrSave(buf);
16315         }
16316         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16317         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16318     }
16319 }
16320
16321 void
16322 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16323 {
16324     char *end_str;
16325     char buf[MSG_SIZ];
16326     ChessProgramState *cps = (ChessProgramState *)closure;
16327
16328     if (isr != cps->isr) return; /* Killed intentionally */
16329     if (count <= 0) {
16330         if (count == 0) {
16331             RemoveInputSource(cps->isr);
16332             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16333                     _(cps->which), cps->program);
16334             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16335             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16336                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16337                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16338                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16339                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16340                 } else {
16341                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16342                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16343                     gameInfo.result = res;
16344                 }
16345                 gameInfo.resultDetails = StrSave(buf);
16346             }
16347             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16348             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16349         } else {
16350             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16351                     _(cps->which), cps->program);
16352             RemoveInputSource(cps->isr);
16353
16354             /* [AS] Program is misbehaving badly... kill it */
16355             if( count == -2 ) {
16356                 DestroyChildProcess( cps->pr, 9 );
16357                 cps->pr = NoProc;
16358             }
16359
16360             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16361         }
16362         return;
16363     }
16364
16365     if ((end_str = strchr(message, '\r')) != NULL)
16366       *end_str = NULLCHAR;
16367     if ((end_str = strchr(message, '\n')) != NULL)
16368       *end_str = NULLCHAR;
16369
16370     if (appData.debugMode) {
16371         TimeMark now; int print = 1;
16372         char *quote = ""; char c; int i;
16373
16374         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16375                 char start = message[0];
16376                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16377                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16378                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16379                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16380                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16381                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16382                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16383                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16384                    sscanf(message, "hint: %c", &c)!=1 &&
16385                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16386                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16387                     print = (appData.engineComments >= 2);
16388                 }
16389                 message[0] = start; // restore original message
16390         }
16391         if(print) {
16392                 GetTimeMark(&now);
16393                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16394                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16395                         quote,
16396                         message);
16397                 if(serverFP)
16398                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16399                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16400                         quote,
16401                         message), fflush(serverFP);
16402         }
16403     }
16404
16405     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16406     if (appData.icsEngineAnalyze) {
16407         if (strstr(message, "whisper") != NULL ||
16408              strstr(message, "kibitz") != NULL ||
16409             strstr(message, "tellics") != NULL) return;
16410     }
16411
16412     HandleMachineMove(message, cps);
16413 }
16414
16415
16416 void
16417 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16418 {
16419     char buf[MSG_SIZ];
16420     int seconds;
16421
16422     if( timeControl_2 > 0 ) {
16423         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16424             tc = timeControl_2;
16425         }
16426     }
16427     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16428     inc /= cps->timeOdds;
16429     st  /= cps->timeOdds;
16430
16431     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16432
16433     if (st > 0) {
16434       /* Set exact time per move, normally using st command */
16435       if (cps->stKludge) {
16436         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16437         seconds = st % 60;
16438         if (seconds == 0) {
16439           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16440         } else {
16441           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16442         }
16443       } else {
16444         snprintf(buf, MSG_SIZ, "st %d\n", st);
16445       }
16446     } else {
16447       /* Set conventional or incremental time control, using level command */
16448       if (seconds == 0) {
16449         /* Note old gnuchess bug -- minutes:seconds used to not work.
16450            Fixed in later versions, but still avoid :seconds
16451            when seconds is 0. */
16452         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16453       } else {
16454         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16455                  seconds, inc/1000.);
16456       }
16457     }
16458     SendToProgram(buf, cps);
16459
16460     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16461     /* Orthogonally, limit search to given depth */
16462     if (sd > 0) {
16463       if (cps->sdKludge) {
16464         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16465       } else {
16466         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16467       }
16468       SendToProgram(buf, cps);
16469     }
16470
16471     if(cps->nps >= 0) { /* [HGM] nps */
16472         if(cps->supportsNPS == FALSE)
16473           cps->nps = -1; // don't use if engine explicitly says not supported!
16474         else {
16475           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16476           SendToProgram(buf, cps);
16477         }
16478     }
16479 }
16480
16481 ChessProgramState *
16482 WhitePlayer ()
16483 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16484 {
16485     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16486        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16487         return &second;
16488     return &first;
16489 }
16490
16491 void
16492 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16493 {
16494     char message[MSG_SIZ];
16495     long time, otime;
16496
16497     /* Note: this routine must be called when the clocks are stopped
16498        or when they have *just* been set or switched; otherwise
16499        it will be off by the time since the current tick started.
16500     */
16501     if (machineWhite) {
16502         time = whiteTimeRemaining / 10;
16503         otime = blackTimeRemaining / 10;
16504     } else {
16505         time = blackTimeRemaining / 10;
16506         otime = whiteTimeRemaining / 10;
16507     }
16508     /* [HGM] translate opponent's time by time-odds factor */
16509     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16510
16511     if (time <= 0) time = 1;
16512     if (otime <= 0) otime = 1;
16513
16514     snprintf(message, MSG_SIZ, "time %ld\n", time);
16515     SendToProgram(message, cps);
16516
16517     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16518     SendToProgram(message, cps);
16519 }
16520
16521 char *
16522 EngineDefinedVariant (ChessProgramState *cps, int n)
16523 {   // return name of n-th unknown variant that engine supports
16524     static char buf[MSG_SIZ];
16525     char *p, *s = cps->variants;
16526     if(!s) return NULL;
16527     do { // parse string from variants feature
16528       VariantClass v;
16529         p = strchr(s, ',');
16530         if(p) *p = NULLCHAR;
16531       v = StringToVariant(s);
16532       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16533         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16534             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16535         }
16536         if(p) *p++ = ',';
16537         if(n < 0) return buf;
16538     } while(s = p);
16539     return NULL;
16540 }
16541
16542 int
16543 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16544 {
16545   char buf[MSG_SIZ];
16546   int len = strlen(name);
16547   int val;
16548
16549   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16550     (*p) += len + 1;
16551     sscanf(*p, "%d", &val);
16552     *loc = (val != 0);
16553     while (**p && **p != ' ')
16554       (*p)++;
16555     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16556     SendToProgram(buf, cps);
16557     return TRUE;
16558   }
16559   return FALSE;
16560 }
16561
16562 int
16563 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16564 {
16565   char buf[MSG_SIZ];
16566   int len = strlen(name);
16567   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16568     (*p) += len + 1;
16569     sscanf(*p, "%d", loc);
16570     while (**p && **p != ' ') (*p)++;
16571     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16572     SendToProgram(buf, cps);
16573     return TRUE;
16574   }
16575   return FALSE;
16576 }
16577
16578 int
16579 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16580 {
16581   char buf[MSG_SIZ];
16582   int len = strlen(name);
16583   if (strncmp((*p), name, len) == 0
16584       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16585     (*p) += len + 2;
16586     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16587     sscanf(*p, "%[^\"]", *loc);
16588     while (**p && **p != '\"') (*p)++;
16589     if (**p == '\"') (*p)++;
16590     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16591     SendToProgram(buf, cps);
16592     return TRUE;
16593   }
16594   return FALSE;
16595 }
16596
16597 int
16598 ParseOption (Option *opt, ChessProgramState *cps)
16599 // [HGM] options: process the string that defines an engine option, and determine
16600 // name, type, default value, and allowed value range
16601 {
16602         char *p, *q, buf[MSG_SIZ];
16603         int n, min = (-1)<<31, max = 1<<31, def;
16604
16605         if(p = strstr(opt->name, " -spin ")) {
16606             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16607             if(max < min) max = min; // enforce consistency
16608             if(def < min) def = min;
16609             if(def > max) def = max;
16610             opt->value = def;
16611             opt->min = min;
16612             opt->max = max;
16613             opt->type = Spin;
16614         } else if((p = strstr(opt->name, " -slider "))) {
16615             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16616             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16617             if(max < min) max = min; // enforce consistency
16618             if(def < min) def = min;
16619             if(def > max) def = max;
16620             opt->value = def;
16621             opt->min = min;
16622             opt->max = max;
16623             opt->type = Spin; // Slider;
16624         } else if((p = strstr(opt->name, " -string "))) {
16625             opt->textValue = p+9;
16626             opt->type = TextBox;
16627         } else if((p = strstr(opt->name, " -file "))) {
16628             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16629             opt->textValue = p+7;
16630             opt->type = FileName; // FileName;
16631         } else if((p = strstr(opt->name, " -path "))) {
16632             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16633             opt->textValue = p+7;
16634             opt->type = PathName; // PathName;
16635         } else if(p = strstr(opt->name, " -check ")) {
16636             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16637             opt->value = (def != 0);
16638             opt->type = CheckBox;
16639         } else if(p = strstr(opt->name, " -combo ")) {
16640             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16641             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16642             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16643             opt->value = n = 0;
16644             while(q = StrStr(q, " /// ")) {
16645                 n++; *q = 0;    // count choices, and null-terminate each of them
16646                 q += 5;
16647                 if(*q == '*') { // remember default, which is marked with * prefix
16648                     q++;
16649                     opt->value = n;
16650                 }
16651                 cps->comboList[cps->comboCnt++] = q;
16652             }
16653             cps->comboList[cps->comboCnt++] = NULL;
16654             opt->max = n + 1;
16655             opt->type = ComboBox;
16656         } else if(p = strstr(opt->name, " -button")) {
16657             opt->type = Button;
16658         } else if(p = strstr(opt->name, " -save")) {
16659             opt->type = SaveButton;
16660         } else return FALSE;
16661         *p = 0; // terminate option name
16662         // now look if the command-line options define a setting for this engine option.
16663         if(cps->optionSettings && cps->optionSettings[0])
16664             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16665         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16666           snprintf(buf, MSG_SIZ, "option %s", p);
16667                 if(p = strstr(buf, ",")) *p = 0;
16668                 if(q = strchr(buf, '=')) switch(opt->type) {
16669                     case ComboBox:
16670                         for(n=0; n<opt->max; n++)
16671                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16672                         break;
16673                     case TextBox:
16674                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16675                         break;
16676                     case Spin:
16677                     case CheckBox:
16678                         opt->value = atoi(q+1);
16679                     default:
16680                         break;
16681                 }
16682                 strcat(buf, "\n");
16683                 SendToProgram(buf, cps);
16684         }
16685         return TRUE;
16686 }
16687
16688 void
16689 FeatureDone (ChessProgramState *cps, int val)
16690 {
16691   DelayedEventCallback cb = GetDelayedEvent();
16692   if ((cb == InitBackEnd3 && cps == &first) ||
16693       (cb == SettingsMenuIfReady && cps == &second) ||
16694       (cb == LoadEngine) ||
16695       (cb == TwoMachinesEventIfReady)) {
16696     CancelDelayedEvent();
16697     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16698   }
16699   cps->initDone = val;
16700   if(val) cps->reload = FALSE;
16701 }
16702
16703 /* Parse feature command from engine */
16704 void
16705 ParseFeatures (char *args, ChessProgramState *cps)
16706 {
16707   char *p = args;
16708   char *q = NULL;
16709   int val;
16710   char buf[MSG_SIZ];
16711
16712   for (;;) {
16713     while (*p == ' ') p++;
16714     if (*p == NULLCHAR) return;
16715
16716     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16717     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16718     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16719     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16720     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16721     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16722     if (BoolFeature(&p, "reuse", &val, cps)) {
16723       /* Engine can disable reuse, but can't enable it if user said no */
16724       if (!val) cps->reuse = FALSE;
16725       continue;
16726     }
16727     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16728     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16729       if (gameMode == TwoMachinesPlay) {
16730         DisplayTwoMachinesTitle();
16731       } else {
16732         DisplayTitle("");
16733       }
16734       continue;
16735     }
16736     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16737     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16738     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16739     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16740     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16741     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16742     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16743     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16744     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16745     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16746     if (IntFeature(&p, "done", &val, cps)) {
16747       FeatureDone(cps, val);
16748       continue;
16749     }
16750     /* Added by Tord: */
16751     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16752     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16753     /* End of additions by Tord */
16754
16755     /* [HGM] added features: */
16756     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16757     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16758     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16759     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16760     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16761     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16762     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16763     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16764         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16765         FREE(cps->option[cps->nrOptions].name);
16766         cps->option[cps->nrOptions].name = q; q = NULL;
16767         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16768           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16769             SendToProgram(buf, cps);
16770             continue;
16771         }
16772         if(cps->nrOptions >= MAX_OPTIONS) {
16773             cps->nrOptions--;
16774             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16775             DisplayError(buf, 0);
16776         }
16777         continue;
16778     }
16779     /* End of additions by HGM */
16780
16781     /* unknown feature: complain and skip */
16782     q = p;
16783     while (*q && *q != '=') q++;
16784     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16785     SendToProgram(buf, cps);
16786     p = q;
16787     if (*p == '=') {
16788       p++;
16789       if (*p == '\"') {
16790         p++;
16791         while (*p && *p != '\"') p++;
16792         if (*p == '\"') p++;
16793       } else {
16794         while (*p && *p != ' ') p++;
16795       }
16796     }
16797   }
16798
16799 }
16800
16801 void
16802 PeriodicUpdatesEvent (int newState)
16803 {
16804     if (newState == appData.periodicUpdates)
16805       return;
16806
16807     appData.periodicUpdates=newState;
16808
16809     /* Display type changes, so update it now */
16810 //    DisplayAnalysis();
16811
16812     /* Get the ball rolling again... */
16813     if (newState) {
16814         AnalysisPeriodicEvent(1);
16815         StartAnalysisClock();
16816     }
16817 }
16818
16819 void
16820 PonderNextMoveEvent (int newState)
16821 {
16822     if (newState == appData.ponderNextMove) return;
16823     if (gameMode == EditPosition) EditPositionDone(TRUE);
16824     if (newState) {
16825         SendToProgram("hard\n", &first);
16826         if (gameMode == TwoMachinesPlay) {
16827             SendToProgram("hard\n", &second);
16828         }
16829     } else {
16830         SendToProgram("easy\n", &first);
16831         thinkOutput[0] = NULLCHAR;
16832         if (gameMode == TwoMachinesPlay) {
16833             SendToProgram("easy\n", &second);
16834         }
16835     }
16836     appData.ponderNextMove = newState;
16837 }
16838
16839 void
16840 NewSettingEvent (int option, int *feature, char *command, int value)
16841 {
16842     char buf[MSG_SIZ];
16843
16844     if (gameMode == EditPosition) EditPositionDone(TRUE);
16845     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16846     if(feature == NULL || *feature) SendToProgram(buf, &first);
16847     if (gameMode == TwoMachinesPlay) {
16848         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16849     }
16850 }
16851
16852 void
16853 ShowThinkingEvent ()
16854 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16855 {
16856     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16857     int newState = appData.showThinking
16858         // [HGM] thinking: other features now need thinking output as well
16859         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16860
16861     if (oldState == newState) return;
16862     oldState = newState;
16863     if (gameMode == EditPosition) EditPositionDone(TRUE);
16864     if (oldState) {
16865         SendToProgram("post\n", &first);
16866         if (gameMode == TwoMachinesPlay) {
16867             SendToProgram("post\n", &second);
16868         }
16869     } else {
16870         SendToProgram("nopost\n", &first);
16871         thinkOutput[0] = NULLCHAR;
16872         if (gameMode == TwoMachinesPlay) {
16873             SendToProgram("nopost\n", &second);
16874         }
16875     }
16876 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16877 }
16878
16879 void
16880 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16881 {
16882   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16883   if (pr == NoProc) return;
16884   AskQuestion(title, question, replyPrefix, pr);
16885 }
16886
16887 void
16888 TypeInEvent (char firstChar)
16889 {
16890     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16891         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16892         gameMode == AnalyzeMode || gameMode == EditGame ||
16893         gameMode == EditPosition || gameMode == IcsExamining ||
16894         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16895         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16896                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16897                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16898         gameMode == Training) PopUpMoveDialog(firstChar);
16899 }
16900
16901 void
16902 TypeInDoneEvent (char *move)
16903 {
16904         Board board;
16905         int n, fromX, fromY, toX, toY;
16906         char promoChar;
16907         ChessMove moveType;
16908
16909         // [HGM] FENedit
16910         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16911                 EditPositionPasteFEN(move);
16912                 return;
16913         }
16914         // [HGM] movenum: allow move number to be typed in any mode
16915         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16916           ToNrEvent(2*n-1);
16917           return;
16918         }
16919         // undocumented kludge: allow command-line option to be typed in!
16920         // (potentially fatal, and does not implement the effect of the option.)
16921         // should only be used for options that are values on which future decisions will be made,
16922         // and definitely not on options that would be used during initialization.
16923         if(strstr(move, "!!! -") == move) {
16924             ParseArgsFromString(move+4);
16925             return;
16926         }
16927
16928       if (gameMode != EditGame && currentMove != forwardMostMove &&
16929         gameMode != Training) {
16930         DisplayMoveError(_("Displayed move is not current"));
16931       } else {
16932         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16933           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16934         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16935         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16936           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16937           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16938         } else {
16939           DisplayMoveError(_("Could not parse move"));
16940         }
16941       }
16942 }
16943
16944 void
16945 DisplayMove (int moveNumber)
16946 {
16947     char message[MSG_SIZ];
16948     char res[MSG_SIZ];
16949     char cpThinkOutput[MSG_SIZ];
16950
16951     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16952
16953     if (moveNumber == forwardMostMove - 1 ||
16954         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16955
16956         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16957
16958         if (strchr(cpThinkOutput, '\n')) {
16959             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16960         }
16961     } else {
16962         *cpThinkOutput = NULLCHAR;
16963     }
16964
16965     /* [AS] Hide thinking from human user */
16966     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16967         *cpThinkOutput = NULLCHAR;
16968         if( thinkOutput[0] != NULLCHAR ) {
16969             int i;
16970
16971             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16972                 cpThinkOutput[i] = '.';
16973             }
16974             cpThinkOutput[i] = NULLCHAR;
16975             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16976         }
16977     }
16978
16979     if (moveNumber == forwardMostMove - 1 &&
16980         gameInfo.resultDetails != NULL) {
16981         if (gameInfo.resultDetails[0] == NULLCHAR) {
16982           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16983         } else {
16984           snprintf(res, MSG_SIZ, " {%s} %s",
16985                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16986         }
16987     } else {
16988         res[0] = NULLCHAR;
16989     }
16990
16991     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16992         DisplayMessage(res, cpThinkOutput);
16993     } else {
16994       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16995                 WhiteOnMove(moveNumber) ? " " : ".. ",
16996                 parseList[moveNumber], res);
16997         DisplayMessage(message, cpThinkOutput);
16998     }
16999 }
17000
17001 void
17002 DisplayComment (int moveNumber, char *text)
17003 {
17004     char title[MSG_SIZ];
17005
17006     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17007       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17008     } else {
17009       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17010               WhiteOnMove(moveNumber) ? " " : ".. ",
17011               parseList[moveNumber]);
17012     }
17013     if (text != NULL && (appData.autoDisplayComment || commentUp))
17014         CommentPopUp(title, text);
17015 }
17016
17017 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17018  * might be busy thinking or pondering.  It can be omitted if your
17019  * gnuchess is configured to stop thinking immediately on any user
17020  * input.  However, that gnuchess feature depends on the FIONREAD
17021  * ioctl, which does not work properly on some flavors of Unix.
17022  */
17023 void
17024 Attention (ChessProgramState *cps)
17025 {
17026 #if ATTENTION
17027     if (!cps->useSigint) return;
17028     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17029     switch (gameMode) {
17030       case MachinePlaysWhite:
17031       case MachinePlaysBlack:
17032       case TwoMachinesPlay:
17033       case IcsPlayingWhite:
17034       case IcsPlayingBlack:
17035       case AnalyzeMode:
17036       case AnalyzeFile:
17037         /* Skip if we know it isn't thinking */
17038         if (!cps->maybeThinking) return;
17039         if (appData.debugMode)
17040           fprintf(debugFP, "Interrupting %s\n", cps->which);
17041         InterruptChildProcess(cps->pr);
17042         cps->maybeThinking = FALSE;
17043         break;
17044       default:
17045         break;
17046     }
17047 #endif /*ATTENTION*/
17048 }
17049
17050 int
17051 CheckFlags ()
17052 {
17053     if (whiteTimeRemaining <= 0) {
17054         if (!whiteFlag) {
17055             whiteFlag = TRUE;
17056             if (appData.icsActive) {
17057                 if (appData.autoCallFlag &&
17058                     gameMode == IcsPlayingBlack && !blackFlag) {
17059                   SendToICS(ics_prefix);
17060                   SendToICS("flag\n");
17061                 }
17062             } else {
17063                 if (blackFlag) {
17064                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17065                 } else {
17066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17067                     if (appData.autoCallFlag) {
17068                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17069                         return TRUE;
17070                     }
17071                 }
17072             }
17073         }
17074     }
17075     if (blackTimeRemaining <= 0) {
17076         if (!blackFlag) {
17077             blackFlag = TRUE;
17078             if (appData.icsActive) {
17079                 if (appData.autoCallFlag &&
17080                     gameMode == IcsPlayingWhite && !whiteFlag) {
17081                   SendToICS(ics_prefix);
17082                   SendToICS("flag\n");
17083                 }
17084             } else {
17085                 if (whiteFlag) {
17086                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17087                 } else {
17088                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17089                     if (appData.autoCallFlag) {
17090                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17091                         return TRUE;
17092                     }
17093                 }
17094             }
17095         }
17096     }
17097     return FALSE;
17098 }
17099
17100 void
17101 CheckTimeControl ()
17102 {
17103     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17104         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17105
17106     /*
17107      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17108      */
17109     if ( !WhiteOnMove(forwardMostMove) ) {
17110         /* White made time control */
17111         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17112         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17113         /* [HGM] time odds: correct new time quota for time odds! */
17114                                             / WhitePlayer()->timeOdds;
17115         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17116     } else {
17117         lastBlack -= blackTimeRemaining;
17118         /* Black made time control */
17119         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17120                                             / WhitePlayer()->other->timeOdds;
17121         lastWhite = whiteTimeRemaining;
17122     }
17123 }
17124
17125 void
17126 DisplayBothClocks ()
17127 {
17128     int wom = gameMode == EditPosition ?
17129       !blackPlaysFirst : WhiteOnMove(currentMove);
17130     DisplayWhiteClock(whiteTimeRemaining, wom);
17131     DisplayBlackClock(blackTimeRemaining, !wom);
17132 }
17133
17134
17135 /* Timekeeping seems to be a portability nightmare.  I think everyone
17136    has ftime(), but I'm really not sure, so I'm including some ifdefs
17137    to use other calls if you don't.  Clocks will be less accurate if
17138    you have neither ftime nor gettimeofday.
17139 */
17140
17141 /* VS 2008 requires the #include outside of the function */
17142 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17143 #include <sys/timeb.h>
17144 #endif
17145
17146 /* Get the current time as a TimeMark */
17147 void
17148 GetTimeMark (TimeMark *tm)
17149 {
17150 #if HAVE_GETTIMEOFDAY
17151
17152     struct timeval timeVal;
17153     struct timezone timeZone;
17154
17155     gettimeofday(&timeVal, &timeZone);
17156     tm->sec = (long) timeVal.tv_sec;
17157     tm->ms = (int) (timeVal.tv_usec / 1000L);
17158
17159 #else /*!HAVE_GETTIMEOFDAY*/
17160 #if HAVE_FTIME
17161
17162 // include <sys/timeb.h> / moved to just above start of function
17163     struct timeb timeB;
17164
17165     ftime(&timeB);
17166     tm->sec = (long) timeB.time;
17167     tm->ms = (int) timeB.millitm;
17168
17169 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17170     tm->sec = (long) time(NULL);
17171     tm->ms = 0;
17172 #endif
17173 #endif
17174 }
17175
17176 /* Return the difference in milliseconds between two
17177    time marks.  We assume the difference will fit in a long!
17178 */
17179 long
17180 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17181 {
17182     return 1000L*(tm2->sec - tm1->sec) +
17183            (long) (tm2->ms - tm1->ms);
17184 }
17185
17186
17187 /*
17188  * Code to manage the game clocks.
17189  *
17190  * In tournament play, black starts the clock and then white makes a move.
17191  * We give the human user a slight advantage if he is playing white---the
17192  * clocks don't run until he makes his first move, so it takes zero time.
17193  * Also, we don't account for network lag, so we could get out of sync
17194  * with GNU Chess's clock -- but then, referees are always right.
17195  */
17196
17197 static TimeMark tickStartTM;
17198 static long intendedTickLength;
17199
17200 long
17201 NextTickLength (long timeRemaining)
17202 {
17203     long nominalTickLength, nextTickLength;
17204
17205     if (timeRemaining > 0L && timeRemaining <= 10000L)
17206       nominalTickLength = 100L;
17207     else
17208       nominalTickLength = 1000L;
17209     nextTickLength = timeRemaining % nominalTickLength;
17210     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17211
17212     return nextTickLength;
17213 }
17214
17215 /* Adjust clock one minute up or down */
17216 void
17217 AdjustClock (Boolean which, int dir)
17218 {
17219     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17220     if(which) blackTimeRemaining += 60000*dir;
17221     else      whiteTimeRemaining += 60000*dir;
17222     DisplayBothClocks();
17223     adjustedClock = TRUE;
17224 }
17225
17226 /* Stop clocks and reset to a fresh time control */
17227 void
17228 ResetClocks ()
17229 {
17230     (void) StopClockTimer();
17231     if (appData.icsActive) {
17232         whiteTimeRemaining = blackTimeRemaining = 0;
17233     } else if (searchTime) {
17234         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17235         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17236     } else { /* [HGM] correct new time quote for time odds */
17237         whiteTC = blackTC = fullTimeControlString;
17238         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17239         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17240     }
17241     if (whiteFlag || blackFlag) {
17242         DisplayTitle("");
17243         whiteFlag = blackFlag = FALSE;
17244     }
17245     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17246     DisplayBothClocks();
17247     adjustedClock = FALSE;
17248 }
17249
17250 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17251
17252 /* Decrement running clock by amount of time that has passed */
17253 void
17254 DecrementClocks ()
17255 {
17256     long timeRemaining;
17257     long lastTickLength, fudge;
17258     TimeMark now;
17259
17260     if (!appData.clockMode) return;
17261     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17262
17263     GetTimeMark(&now);
17264
17265     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17266
17267     /* Fudge if we woke up a little too soon */
17268     fudge = intendedTickLength - lastTickLength;
17269     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17270
17271     if (WhiteOnMove(forwardMostMove)) {
17272         if(whiteNPS >= 0) lastTickLength = 0;
17273         timeRemaining = whiteTimeRemaining -= lastTickLength;
17274         if(timeRemaining < 0 && !appData.icsActive) {
17275             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17276             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17277                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17278                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17279             }
17280         }
17281         DisplayWhiteClock(whiteTimeRemaining - fudge,
17282                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17283     } else {
17284         if(blackNPS >= 0) lastTickLength = 0;
17285         timeRemaining = blackTimeRemaining -= lastTickLength;
17286         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17287             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17288             if(suddenDeath) {
17289                 blackStartMove = forwardMostMove;
17290                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17291             }
17292         }
17293         DisplayBlackClock(blackTimeRemaining - fudge,
17294                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17295     }
17296     if (CheckFlags()) return;
17297
17298     if(twoBoards) { // count down secondary board's clocks as well
17299         activePartnerTime -= lastTickLength;
17300         partnerUp = 1;
17301         if(activePartner == 'W')
17302             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17303         else
17304             DisplayBlackClock(activePartnerTime, TRUE);
17305         partnerUp = 0;
17306     }
17307
17308     tickStartTM = now;
17309     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17310     StartClockTimer(intendedTickLength);
17311
17312     /* if the time remaining has fallen below the alarm threshold, sound the
17313      * alarm. if the alarm has sounded and (due to a takeback or time control
17314      * with increment) the time remaining has increased to a level above the
17315      * threshold, reset the alarm so it can sound again.
17316      */
17317
17318     if (appData.icsActive && appData.icsAlarm) {
17319
17320         /* make sure we are dealing with the user's clock */
17321         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17322                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17323            )) return;
17324
17325         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17326             alarmSounded = FALSE;
17327         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17328             PlayAlarmSound();
17329             alarmSounded = TRUE;
17330         }
17331     }
17332 }
17333
17334
17335 /* A player has just moved, so stop the previously running
17336    clock and (if in clock mode) start the other one.
17337    We redisplay both clocks in case we're in ICS mode, because
17338    ICS gives us an update to both clocks after every move.
17339    Note that this routine is called *after* forwardMostMove
17340    is updated, so the last fractional tick must be subtracted
17341    from the color that is *not* on move now.
17342 */
17343 void
17344 SwitchClocks (int newMoveNr)
17345 {
17346     long lastTickLength;
17347     TimeMark now;
17348     int flagged = FALSE;
17349
17350     GetTimeMark(&now);
17351
17352     if (StopClockTimer() && appData.clockMode) {
17353         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17354         if (!WhiteOnMove(forwardMostMove)) {
17355             if(blackNPS >= 0) lastTickLength = 0;
17356             blackTimeRemaining -= lastTickLength;
17357            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17358 //         if(pvInfoList[forwardMostMove].time == -1)
17359                  pvInfoList[forwardMostMove].time =               // use GUI time
17360                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17361         } else {
17362            if(whiteNPS >= 0) lastTickLength = 0;
17363            whiteTimeRemaining -= lastTickLength;
17364            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17365 //         if(pvInfoList[forwardMostMove].time == -1)
17366                  pvInfoList[forwardMostMove].time =
17367                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17368         }
17369         flagged = CheckFlags();
17370     }
17371     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17372     CheckTimeControl();
17373
17374     if (flagged || !appData.clockMode) return;
17375
17376     switch (gameMode) {
17377       case MachinePlaysBlack:
17378       case MachinePlaysWhite:
17379       case BeginningOfGame:
17380         if (pausing) return;
17381         break;
17382
17383       case EditGame:
17384       case PlayFromGameFile:
17385       case IcsExamining:
17386         return;
17387
17388       default:
17389         break;
17390     }
17391
17392     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17393         if(WhiteOnMove(forwardMostMove))
17394              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17395         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17396     }
17397
17398     tickStartTM = now;
17399     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17400       whiteTimeRemaining : blackTimeRemaining);
17401     StartClockTimer(intendedTickLength);
17402 }
17403
17404
17405 /* Stop both clocks */
17406 void
17407 StopClocks ()
17408 {
17409     long lastTickLength;
17410     TimeMark now;
17411
17412     if (!StopClockTimer()) return;
17413     if (!appData.clockMode) return;
17414
17415     GetTimeMark(&now);
17416
17417     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17418     if (WhiteOnMove(forwardMostMove)) {
17419         if(whiteNPS >= 0) lastTickLength = 0;
17420         whiteTimeRemaining -= lastTickLength;
17421         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17422     } else {
17423         if(blackNPS >= 0) lastTickLength = 0;
17424         blackTimeRemaining -= lastTickLength;
17425         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17426     }
17427     CheckFlags();
17428 }
17429
17430 /* Start clock of player on move.  Time may have been reset, so
17431    if clock is already running, stop and restart it. */
17432 void
17433 StartClocks ()
17434 {
17435     (void) StopClockTimer(); /* in case it was running already */
17436     DisplayBothClocks();
17437     if (CheckFlags()) return;
17438
17439     if (!appData.clockMode) return;
17440     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17441
17442     GetTimeMark(&tickStartTM);
17443     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17444       whiteTimeRemaining : blackTimeRemaining);
17445
17446    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17447     whiteNPS = blackNPS = -1;
17448     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17449        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17450         whiteNPS = first.nps;
17451     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17452        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17453         blackNPS = first.nps;
17454     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17455         whiteNPS = second.nps;
17456     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17457         blackNPS = second.nps;
17458     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17459
17460     StartClockTimer(intendedTickLength);
17461 }
17462
17463 char *
17464 TimeString (long ms)
17465 {
17466     long second, minute, hour, day;
17467     char *sign = "";
17468     static char buf[32];
17469
17470     if (ms > 0 && ms <= 9900) {
17471       /* convert milliseconds to tenths, rounding up */
17472       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17473
17474       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17475       return buf;
17476     }
17477
17478     /* convert milliseconds to seconds, rounding up */
17479     /* use floating point to avoid strangeness of integer division
17480        with negative dividends on many machines */
17481     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17482
17483     if (second < 0) {
17484         sign = "-";
17485         second = -second;
17486     }
17487
17488     day = second / (60 * 60 * 24);
17489     second = second % (60 * 60 * 24);
17490     hour = second / (60 * 60);
17491     second = second % (60 * 60);
17492     minute = second / 60;
17493     second = second % 60;
17494
17495     if (day > 0)
17496       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17497               sign, day, hour, minute, second);
17498     else if (hour > 0)
17499       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17500     else
17501       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17502
17503     return buf;
17504 }
17505
17506
17507 /*
17508  * This is necessary because some C libraries aren't ANSI C compliant yet.
17509  */
17510 char *
17511 StrStr (char *string, char *match)
17512 {
17513     int i, length;
17514
17515     length = strlen(match);
17516
17517     for (i = strlen(string) - length; i >= 0; i--, string++)
17518       if (!strncmp(match, string, length))
17519         return string;
17520
17521     return NULL;
17522 }
17523
17524 char *
17525 StrCaseStr (char *string, char *match)
17526 {
17527     int i, j, length;
17528
17529     length = strlen(match);
17530
17531     for (i = strlen(string) - length; i >= 0; i--, string++) {
17532         for (j = 0; j < length; j++) {
17533             if (ToLower(match[j]) != ToLower(string[j]))
17534               break;
17535         }
17536         if (j == length) return string;
17537     }
17538
17539     return NULL;
17540 }
17541
17542 #ifndef _amigados
17543 int
17544 StrCaseCmp (char *s1, char *s2)
17545 {
17546     char c1, c2;
17547
17548     for (;;) {
17549         c1 = ToLower(*s1++);
17550         c2 = ToLower(*s2++);
17551         if (c1 > c2) return 1;
17552         if (c1 < c2) return -1;
17553         if (c1 == NULLCHAR) return 0;
17554     }
17555 }
17556
17557
17558 int
17559 ToLower (int c)
17560 {
17561     return isupper(c) ? tolower(c) : c;
17562 }
17563
17564
17565 int
17566 ToUpper (int c)
17567 {
17568     return islower(c) ? toupper(c) : c;
17569 }
17570 #endif /* !_amigados    */
17571
17572 char *
17573 StrSave (char *s)
17574 {
17575   char *ret;
17576
17577   if ((ret = (char *) malloc(strlen(s) + 1)))
17578     {
17579       safeStrCpy(ret, s, strlen(s)+1);
17580     }
17581   return ret;
17582 }
17583
17584 char *
17585 StrSavePtr (char *s, char **savePtr)
17586 {
17587     if (*savePtr) {
17588         free(*savePtr);
17589     }
17590     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17591       safeStrCpy(*savePtr, s, strlen(s)+1);
17592     }
17593     return(*savePtr);
17594 }
17595
17596 char *
17597 PGNDate ()
17598 {
17599     time_t clock;
17600     struct tm *tm;
17601     char buf[MSG_SIZ];
17602
17603     clock = time((time_t *)NULL);
17604     tm = localtime(&clock);
17605     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17606             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17607     return StrSave(buf);
17608 }
17609
17610
17611 char *
17612 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17613 {
17614     int i, j, fromX, fromY, toX, toY;
17615     int whiteToPlay;
17616     char buf[MSG_SIZ];
17617     char *p, *q;
17618     int emptycount;
17619     ChessSquare piece;
17620
17621     whiteToPlay = (gameMode == EditPosition) ?
17622       !blackPlaysFirst : (move % 2 == 0);
17623     p = buf;
17624
17625     /* Piece placement data */
17626     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17627         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17628         emptycount = 0;
17629         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17630             if (boards[move][i][j] == EmptySquare) {
17631                 emptycount++;
17632             } else { ChessSquare piece = boards[move][i][j];
17633                 if (emptycount > 0) {
17634                     if(emptycount<10) /* [HGM] can be >= 10 */
17635                         *p++ = '0' + emptycount;
17636                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17637                     emptycount = 0;
17638                 }
17639                 if(PieceToChar(piece) == '+') {
17640                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17641                     *p++ = '+';
17642                     piece = (ChessSquare)(DEMOTED piece);
17643                 }
17644                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17645                 if(p[-1] == '~') {
17646                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17647                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17648                     *p++ = '~';
17649                 }
17650             }
17651         }
17652         if (emptycount > 0) {
17653             if(emptycount<10) /* [HGM] can be >= 10 */
17654                 *p++ = '0' + emptycount;
17655             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17656             emptycount = 0;
17657         }
17658         *p++ = '/';
17659     }
17660     *(p - 1) = ' ';
17661
17662     /* [HGM] print Crazyhouse or Shogi holdings */
17663     if( gameInfo.holdingsWidth ) {
17664         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17665         q = p;
17666         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17667             piece = boards[move][i][BOARD_WIDTH-1];
17668             if( piece != EmptySquare )
17669               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17670                   *p++ = PieceToChar(piece);
17671         }
17672         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17673             piece = boards[move][BOARD_HEIGHT-i-1][0];
17674             if( piece != EmptySquare )
17675               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17676                   *p++ = PieceToChar(piece);
17677         }
17678
17679         if( q == p ) *p++ = '-';
17680         *p++ = ']';
17681         *p++ = ' ';
17682     }
17683
17684     /* Active color */
17685     *p++ = whiteToPlay ? 'w' : 'b';
17686     *p++ = ' ';
17687
17688   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17689     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17690   } else {
17691   if(nrCastlingRights) {
17692      q = p;
17693      if(appData.fischerCastling) {
17694        /* [HGM] write directly from rights */
17695            if(boards[move][CASTLING][2] != NoRights &&
17696               boards[move][CASTLING][0] != NoRights   )
17697                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17698            if(boards[move][CASTLING][2] != NoRights &&
17699               boards[move][CASTLING][1] != NoRights   )
17700                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17701            if(boards[move][CASTLING][5] != NoRights &&
17702               boards[move][CASTLING][3] != NoRights   )
17703                 *p++ = boards[move][CASTLING][3] + AAA;
17704            if(boards[move][CASTLING][5] != NoRights &&
17705               boards[move][CASTLING][4] != NoRights   )
17706                 *p++ = boards[move][CASTLING][4] + AAA;
17707      } else {
17708
17709         /* [HGM] write true castling rights */
17710         if( nrCastlingRights == 6 ) {
17711             int q, k=0;
17712             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17713                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17714             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17715                  boards[move][CASTLING][2] != NoRights  );
17716             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17717                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17718                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17719                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17720                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17721             }
17722             if(q) *p++ = 'Q';
17723             k = 0;
17724             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17725                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17726             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17727                  boards[move][CASTLING][5] != NoRights  );
17728             if(gameInfo.variant == VariantSChess) {
17729                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17730                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17731                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17732                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17733             }
17734             if(q) *p++ = 'q';
17735         }
17736      }
17737      if (q == p) *p++ = '-'; /* No castling rights */
17738      *p++ = ' ';
17739   }
17740
17741   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17742      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17743      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17744     /* En passant target square */
17745     if (move > backwardMostMove) {
17746         fromX = moveList[move - 1][0] - AAA;
17747         fromY = moveList[move - 1][1] - ONE;
17748         toX = moveList[move - 1][2] - AAA;
17749         toY = moveList[move - 1][3] - ONE;
17750         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17751             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17752             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17753             fromX == toX) {
17754             /* 2-square pawn move just happened */
17755             *p++ = toX + AAA;
17756             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17757         } else {
17758             *p++ = '-';
17759         }
17760     } else if(move == backwardMostMove) {
17761         // [HGM] perhaps we should always do it like this, and forget the above?
17762         if((signed char)boards[move][EP_STATUS] >= 0) {
17763             *p++ = boards[move][EP_STATUS] + AAA;
17764             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17765         } else {
17766             *p++ = '-';
17767         }
17768     } else {
17769         *p++ = '-';
17770     }
17771     *p++ = ' ';
17772   }
17773   }
17774
17775     if(moveCounts)
17776     {   int i = 0, j=move;
17777
17778         /* [HGM] find reversible plies */
17779         if (appData.debugMode) { int k;
17780             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17781             for(k=backwardMostMove; k<=forwardMostMove; k++)
17782                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17783
17784         }
17785
17786         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17787         if( j == backwardMostMove ) i += initialRulePlies;
17788         sprintf(p, "%d ", i);
17789         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17790
17791         /* Fullmove number */
17792         sprintf(p, "%d", (move / 2) + 1);
17793     } else *--p = NULLCHAR;
17794
17795     return StrSave(buf);
17796 }
17797
17798 Boolean
17799 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17800 {
17801     int i, j, k, w=0, subst=0, shuffle=0;
17802     char *p, c;
17803     int emptycount, virgin[BOARD_FILES];
17804     ChessSquare piece;
17805
17806     p = fen;
17807
17808     /* Piece placement data */
17809     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17810         j = 0;
17811         for (;;) {
17812             if (*p == '/' || *p == ' ' || *p == '[' ) {
17813                 if(j > w) w = j;
17814                 emptycount = gameInfo.boardWidth - j;
17815                 while (emptycount--)
17816                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17817                 if (*p == '/') p++;
17818                 else if(autoSize) { // we stumbled unexpectedly into end of board
17819                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17820                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17821                     }
17822                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17823                 }
17824                 break;
17825 #if(BOARD_FILES >= 10)*0
17826             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17827                 p++; emptycount=10;
17828                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17829                 while (emptycount--)
17830                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17831 #endif
17832             } else if (*p == '*') {
17833                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17834             } else if (isdigit(*p)) {
17835                 emptycount = *p++ - '0';
17836                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17837                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17838                 while (emptycount--)
17839                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17840             } else if (*p == '<') {
17841                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17842                 else if (i != 0 || !shuffle) return FALSE;
17843                 p++;
17844             } else if (shuffle && *p == '>') {
17845                 p++; // for now ignore closing shuffle range, and assume rank-end
17846             } else if (*p == '?') {
17847                 if (j >= gameInfo.boardWidth) return FALSE;
17848                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17849                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17850             } else if (*p == '+' || isalpha(*p)) {
17851                 if (j >= gameInfo.boardWidth) return FALSE;
17852                 if(*p=='+') {
17853                     piece = CharToPiece(*++p);
17854                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17855                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17856                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17857                 } else piece = CharToPiece(*p++);
17858
17859                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17860                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17861                     piece = (ChessSquare) (PROMOTED piece);
17862                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17863                     p++;
17864                 }
17865                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17866             } else {
17867                 return FALSE;
17868             }
17869         }
17870     }
17871     while (*p == '/' || *p == ' ') p++;
17872
17873     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17874
17875     /* [HGM] by default clear Crazyhouse holdings, if present */
17876     if(gameInfo.holdingsWidth) {
17877        for(i=0; i<BOARD_HEIGHT; i++) {
17878            board[i][0]             = EmptySquare; /* black holdings */
17879            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17880            board[i][1]             = (ChessSquare) 0; /* black counts */
17881            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17882        }
17883     }
17884
17885     /* [HGM] look for Crazyhouse holdings here */
17886     while(*p==' ') p++;
17887     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17888         int swap=0, wcnt=0, bcnt=0;
17889         if(*p == '[') p++;
17890         if(*p == '<') swap++, p++;
17891         if(*p == '-' ) p++; /* empty holdings */ else {
17892             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17893             /* if we would allow FEN reading to set board size, we would   */
17894             /* have to add holdings and shift the board read so far here   */
17895             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17896                 p++;
17897                 if((int) piece >= (int) BlackPawn ) {
17898                     i = (int)piece - (int)BlackPawn;
17899                     i = PieceToNumber((ChessSquare)i);
17900                     if( i >= gameInfo.holdingsSize ) return FALSE;
17901                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17902                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17903                     bcnt++;
17904                 } else {
17905                     i = (int)piece - (int)WhitePawn;
17906                     i = PieceToNumber((ChessSquare)i);
17907                     if( i >= gameInfo.holdingsSize ) return FALSE;
17908                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17909                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17910                     wcnt++;
17911                 }
17912             }
17913             if(subst) { // substitute back-rank question marks by holdings pieces
17914                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17915                     int k, m, n = bcnt + 1;
17916                     if(board[0][j] == ClearBoard) {
17917                         if(!wcnt) return FALSE;
17918                         n = rand() % wcnt;
17919                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17920                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17921                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17922                             break;
17923                         }
17924                     }
17925                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17926                         if(!bcnt) return FALSE;
17927                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17928                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17929                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17930                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17931                             break;
17932                         }
17933                     }
17934                 }
17935                 subst = 0;
17936             }
17937         }
17938         if(*p == ']') p++;
17939     }
17940
17941     if(subst) return FALSE; // substitution requested, but no holdings
17942
17943     while(*p == ' ') p++;
17944
17945     /* Active color */
17946     c = *p++;
17947     if(appData.colorNickNames) {
17948       if( c == appData.colorNickNames[0] ) c = 'w'; else
17949       if( c == appData.colorNickNames[1] ) c = 'b';
17950     }
17951     switch (c) {
17952       case 'w':
17953         *blackPlaysFirst = FALSE;
17954         break;
17955       case 'b':
17956         *blackPlaysFirst = TRUE;
17957         break;
17958       default:
17959         return FALSE;
17960     }
17961
17962     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17963     /* return the extra info in global variiables             */
17964
17965     /* set defaults in case FEN is incomplete */
17966     board[EP_STATUS] = EP_UNKNOWN;
17967     for(i=0; i<nrCastlingRights; i++ ) {
17968         board[CASTLING][i] =
17969             appData.fischerCastling ? NoRights : initialRights[i];
17970     }   /* assume possible unless obviously impossible */
17971     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17972     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17973     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17974                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17975     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17976     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17977     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17978                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17979     FENrulePlies = 0;
17980
17981     while(*p==' ') p++;
17982     if(nrCastlingRights) {
17983       int fischer = 0;
17984       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17985       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17986           /* castling indicator present, so default becomes no castlings */
17987           for(i=0; i<nrCastlingRights; i++ ) {
17988                  board[CASTLING][i] = NoRights;
17989           }
17990       }
17991       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17992              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17993              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17994              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17995         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17996
17997         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17998             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17999             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18000         }
18001         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18002             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18003         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18004                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18005         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18006                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18007         switch(c) {
18008           case'K':
18009               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18010               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18011               board[CASTLING][2] = whiteKingFile;
18012               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18013               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18014               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18015               break;
18016           case'Q':
18017               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18018               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18019               board[CASTLING][2] = whiteKingFile;
18020               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18021               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18022               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18023               break;
18024           case'k':
18025               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18026               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18027               board[CASTLING][5] = blackKingFile;
18028               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18029               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18030               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18031               break;
18032           case'q':
18033               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18034               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18035               board[CASTLING][5] = blackKingFile;
18036               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18037               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18038               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18039           case '-':
18040               break;
18041           default: /* FRC castlings */
18042               if(c >= 'a') { /* black rights */
18043                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18044                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18045                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18046                   if(i == BOARD_RGHT) break;
18047                   board[CASTLING][5] = i;
18048                   c -= AAA;
18049                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18050                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18051                   if(c > i)
18052                       board[CASTLING][3] = c;
18053                   else
18054                       board[CASTLING][4] = c;
18055               } else { /* white rights */
18056                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18057                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18058                     if(board[0][i] == WhiteKing) break;
18059                   if(i == BOARD_RGHT) break;
18060                   board[CASTLING][2] = i;
18061                   c -= AAA - 'a' + 'A';
18062                   if(board[0][c] >= WhiteKing) break;
18063                   if(c > i)
18064                       board[CASTLING][0] = c;
18065                   else
18066                       board[CASTLING][1] = c;
18067               }
18068         }
18069       }
18070       for(i=0; i<nrCastlingRights; i++)
18071         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18072       if(gameInfo.variant == VariantSChess)
18073         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18074       if(fischer && shuffle) appData.fischerCastling = TRUE;
18075     if (appData.debugMode) {
18076         fprintf(debugFP, "FEN castling rights:");
18077         for(i=0; i<nrCastlingRights; i++)
18078         fprintf(debugFP, " %d", board[CASTLING][i]);
18079         fprintf(debugFP, "\n");
18080     }
18081
18082       while(*p==' ') p++;
18083     }
18084
18085     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18086
18087     /* read e.p. field in games that know e.p. capture */
18088     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18089        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18090        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18091       if(*p=='-') {
18092         p++; board[EP_STATUS] = EP_NONE;
18093       } else {
18094          char c = *p++ - AAA;
18095
18096          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18097          if(*p >= '0' && *p <='9') p++;
18098          board[EP_STATUS] = c;
18099       }
18100     }
18101
18102
18103     if(sscanf(p, "%d", &i) == 1) {
18104         FENrulePlies = i; /* 50-move ply counter */
18105         /* (The move number is still ignored)    */
18106     }
18107
18108     return TRUE;
18109 }
18110
18111 void
18112 EditPositionPasteFEN (char *fen)
18113 {
18114   if (fen != NULL) {
18115     Board initial_position;
18116
18117     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18118       DisplayError(_("Bad FEN position in clipboard"), 0);
18119       return ;
18120     } else {
18121       int savedBlackPlaysFirst = blackPlaysFirst;
18122       EditPositionEvent();
18123       blackPlaysFirst = savedBlackPlaysFirst;
18124       CopyBoard(boards[0], initial_position);
18125       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18126       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18127       DisplayBothClocks();
18128       DrawPosition(FALSE, boards[currentMove]);
18129     }
18130   }
18131 }
18132
18133 static char cseq[12] = "\\   ";
18134
18135 Boolean
18136 set_cont_sequence (char *new_seq)
18137 {
18138     int len;
18139     Boolean ret;
18140
18141     // handle bad attempts to set the sequence
18142         if (!new_seq)
18143                 return 0; // acceptable error - no debug
18144
18145     len = strlen(new_seq);
18146     ret = (len > 0) && (len < sizeof(cseq));
18147     if (ret)
18148       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18149     else if (appData.debugMode)
18150       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18151     return ret;
18152 }
18153
18154 /*
18155     reformat a source message so words don't cross the width boundary.  internal
18156     newlines are not removed.  returns the wrapped size (no null character unless
18157     included in source message).  If dest is NULL, only calculate the size required
18158     for the dest buffer.  lp argument indicats line position upon entry, and it's
18159     passed back upon exit.
18160 */
18161 int
18162 wrap (char *dest, char *src, int count, int width, int *lp)
18163 {
18164     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18165
18166     cseq_len = strlen(cseq);
18167     old_line = line = *lp;
18168     ansi = len = clen = 0;
18169
18170     for (i=0; i < count; i++)
18171     {
18172         if (src[i] == '\033')
18173             ansi = 1;
18174
18175         // if we hit the width, back up
18176         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18177         {
18178             // store i & len in case the word is too long
18179             old_i = i, old_len = len;
18180
18181             // find the end of the last word
18182             while (i && src[i] != ' ' && src[i] != '\n')
18183             {
18184                 i--;
18185                 len--;
18186             }
18187
18188             // word too long?  restore i & len before splitting it
18189             if ((old_i-i+clen) >= width)
18190             {
18191                 i = old_i;
18192                 len = old_len;
18193             }
18194
18195             // extra space?
18196             if (i && src[i-1] == ' ')
18197                 len--;
18198
18199             if (src[i] != ' ' && src[i] != '\n')
18200             {
18201                 i--;
18202                 if (len)
18203                     len--;
18204             }
18205
18206             // now append the newline and continuation sequence
18207             if (dest)
18208                 dest[len] = '\n';
18209             len++;
18210             if (dest)
18211                 strncpy(dest+len, cseq, cseq_len);
18212             len += cseq_len;
18213             line = cseq_len;
18214             clen = cseq_len;
18215             continue;
18216         }
18217
18218         if (dest)
18219             dest[len] = src[i];
18220         len++;
18221         if (!ansi)
18222             line++;
18223         if (src[i] == '\n')
18224             line = 0;
18225         if (src[i] == 'm')
18226             ansi = 0;
18227     }
18228     if (dest && appData.debugMode)
18229     {
18230         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18231             count, width, line, len, *lp);
18232         show_bytes(debugFP, src, count);
18233         fprintf(debugFP, "\ndest: ");
18234         show_bytes(debugFP, dest, len);
18235         fprintf(debugFP, "\n");
18236     }
18237     *lp = dest ? line : old_line;
18238
18239     return len;
18240 }
18241
18242 // [HGM] vari: routines for shelving variations
18243 Boolean modeRestore = FALSE;
18244
18245 void
18246 PushInner (int firstMove, int lastMove)
18247 {
18248         int i, j, nrMoves = lastMove - firstMove;
18249
18250         // push current tail of game on stack
18251         savedResult[storedGames] = gameInfo.result;
18252         savedDetails[storedGames] = gameInfo.resultDetails;
18253         gameInfo.resultDetails = NULL;
18254         savedFirst[storedGames] = firstMove;
18255         savedLast [storedGames] = lastMove;
18256         savedFramePtr[storedGames] = framePtr;
18257         framePtr -= nrMoves; // reserve space for the boards
18258         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18259             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18260             for(j=0; j<MOVE_LEN; j++)
18261                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18262             for(j=0; j<2*MOVE_LEN; j++)
18263                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18264             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18265             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18266             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18267             pvInfoList[firstMove+i-1].depth = 0;
18268             commentList[framePtr+i] = commentList[firstMove+i];
18269             commentList[firstMove+i] = NULL;
18270         }
18271
18272         storedGames++;
18273         forwardMostMove = firstMove; // truncate game so we can start variation
18274 }
18275
18276 void
18277 PushTail (int firstMove, int lastMove)
18278 {
18279         if(appData.icsActive) { // only in local mode
18280                 forwardMostMove = currentMove; // mimic old ICS behavior
18281                 return;
18282         }
18283         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18284
18285         PushInner(firstMove, lastMove);
18286         if(storedGames == 1) GreyRevert(FALSE);
18287         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18288 }
18289
18290 void
18291 PopInner (Boolean annotate)
18292 {
18293         int i, j, nrMoves;
18294         char buf[8000], moveBuf[20];
18295
18296         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18297         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18298         nrMoves = savedLast[storedGames] - currentMove;
18299         if(annotate) {
18300                 int cnt = 10;
18301                 if(!WhiteOnMove(currentMove))
18302                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18303                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18304                 for(i=currentMove; i<forwardMostMove; i++) {
18305                         if(WhiteOnMove(i))
18306                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18307                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18308                         strcat(buf, moveBuf);
18309                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18310                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18311                 }
18312                 strcat(buf, ")");
18313         }
18314         for(i=1; i<=nrMoves; i++) { // copy last variation back
18315             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18316             for(j=0; j<MOVE_LEN; j++)
18317                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18318             for(j=0; j<2*MOVE_LEN; j++)
18319                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18320             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18321             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18322             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18323             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18324             commentList[currentMove+i] = commentList[framePtr+i];
18325             commentList[framePtr+i] = NULL;
18326         }
18327         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18328         framePtr = savedFramePtr[storedGames];
18329         gameInfo.result = savedResult[storedGames];
18330         if(gameInfo.resultDetails != NULL) {
18331             free(gameInfo.resultDetails);
18332       }
18333         gameInfo.resultDetails = savedDetails[storedGames];
18334         forwardMostMove = currentMove + nrMoves;
18335 }
18336
18337 Boolean
18338 PopTail (Boolean annotate)
18339 {
18340         if(appData.icsActive) return FALSE; // only in local mode
18341         if(!storedGames) return FALSE; // sanity
18342         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18343
18344         PopInner(annotate);
18345         if(currentMove < forwardMostMove) ForwardEvent(); else
18346         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18347
18348         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18349         return TRUE;
18350 }
18351
18352 void
18353 CleanupTail ()
18354 {       // remove all shelved variations
18355         int i;
18356         for(i=0; i<storedGames; i++) {
18357             if(savedDetails[i])
18358                 free(savedDetails[i]);
18359             savedDetails[i] = NULL;
18360         }
18361         for(i=framePtr; i<MAX_MOVES; i++) {
18362                 if(commentList[i]) free(commentList[i]);
18363                 commentList[i] = NULL;
18364         }
18365         framePtr = MAX_MOVES-1;
18366         storedGames = 0;
18367 }
18368
18369 void
18370 LoadVariation (int index, char *text)
18371 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18372         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18373         int level = 0, move;
18374
18375         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18376         // first find outermost bracketing variation
18377         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18378             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18379                 if(*p == '{') wait = '}'; else
18380                 if(*p == '[') wait = ']'; else
18381                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18382                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18383             }
18384             if(*p == wait) wait = NULLCHAR; // closing ]} found
18385             p++;
18386         }
18387         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18388         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18389         end[1] = NULLCHAR; // clip off comment beyond variation
18390         ToNrEvent(currentMove-1);
18391         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18392         // kludge: use ParsePV() to append variation to game
18393         move = currentMove;
18394         ParsePV(start, TRUE, TRUE);
18395         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18396         ClearPremoveHighlights();
18397         CommentPopDown();
18398         ToNrEvent(currentMove+1);
18399 }
18400
18401 void
18402 LoadTheme ()
18403 {
18404     char *p, *q, buf[MSG_SIZ];
18405     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18406         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18407         ParseArgsFromString(buf);
18408         ActivateTheme(TRUE); // also redo colors
18409         return;
18410     }
18411     p = nickName;
18412     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18413     {
18414         int len;
18415         q = appData.themeNames;
18416         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18417       if(appData.useBitmaps) {
18418         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18419                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18420                 appData.liteBackTextureMode,
18421                 appData.darkBackTextureMode );
18422       } else {
18423         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18424                 Col2Text(2),   // lightSquareColor
18425                 Col2Text(3) ); // darkSquareColor
18426       }
18427       if(appData.useBorder) {
18428         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18429                 appData.border);
18430       } else {
18431         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18432       }
18433       if(appData.useFont) {
18434         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18435                 appData.renderPiecesWithFont,
18436                 appData.fontToPieceTable,
18437                 Col2Text(9),    // appData.fontBackColorWhite
18438                 Col2Text(10) ); // appData.fontForeColorBlack
18439       } else {
18440         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18441                 appData.pieceDirectory);
18442         if(!appData.pieceDirectory[0])
18443           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18444                 Col2Text(0),   // whitePieceColor
18445                 Col2Text(1) ); // blackPieceColor
18446       }
18447       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18448                 Col2Text(4),   // highlightSquareColor
18449                 Col2Text(5) ); // premoveHighlightColor
18450         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18451         if(insert != q) insert[-1] = NULLCHAR;
18452         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18453         if(q)   free(q);
18454     }
18455     ActivateTheme(FALSE);
18456 }