Write broadcasts also to private chatbox of talker
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   default:
417     break;
418   }
419   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
420   return flags;
421 }
422
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
425
426 /*
427     [AS] Note: sometimes, the sscanf() function is used to parse the input
428     into a fixed-size buffer. Because of this, we must be prepared to
429     receive strings as long as the size of the input buffer, which is currently
430     set to 4K for Windows and 8K for the rest.
431     So, we must either allocate sufficiently large buffers here, or
432     reduce the size of the input buffer in the input reading part.
433 */
434
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
438
439 ChessProgramState first, second, pairing;
440
441 /* premove variables */
442 int premoveToX = 0;
443 int premoveToY = 0;
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
447 int gotPremove = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
450
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
453
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
481
482 int have_sent_ICS_logon = 0;
483 int movesPerSession;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
495
496 /* animateTraining preserves the state of appData.animate
497  * when Training mode is activated. This allows the
498  * response to be animated when appData.animate == TRUE and
499  * appData.animateDragging == TRUE.
500  */
501 Boolean animateTraining;
502
503 GameInfo gameInfo;
504
505 AppData appData;
506
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char  initialRights[BOARD_FILES];
511 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int   initialRulePlies, FENrulePlies;
513 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 int loadFlag = 0;
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
517
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int storedGames = 0;
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
527
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
533
534 ChessSquare  FIDEArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538         BlackKing, BlackBishop, BlackKnight, BlackRook }
539 };
540
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackKing, BlackKnight, BlackRook }
546 };
547
548 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551     { BlackRook, BlackMan, BlackBishop, BlackQueen,
552         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 };
554
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 };
561
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 };
568
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackMan, BlackFerz,
580         BlackKing, BlackMan, BlackKnight, BlackRook }
581 };
582
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare  lionArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593     { BlackRook, BlackLion, BlackBishop, BlackQueen,
594         BlackKing, BlackBishop, BlackKnight, BlackRook }
595 };
596
597
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 };
605
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 };
612
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 };
619
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 };
626
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 };
633
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 };
640
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
646 };
647
648 #ifdef GOTHIC
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 };
655 #else // !GOTHIC
656 #define GothicArray CapablancaArray
657 #endif // !GOTHIC
658
659 #ifdef FALCON
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 };
666 #else // !FALCON
667 #define FalconArray CapablancaArray
668 #endif // !FALCON
669
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
676
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 };
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 };
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
702
703
704 Board initialPosition;
705
706
707 /* Convert str to a rating. Checks for special cases of "----",
708
709    "++++", etc. Also strips ()'s */
710 int
711 string_to_rating (char *str)
712 {
713   while(*str && !isdigit(*str)) ++str;
714   if (!*str)
715     return 0;   /* One of the special "no rating" cases */
716   else
717     return atoi(str);
718 }
719
720 void
721 ClearProgramStats ()
722 {
723     /* Init programStats */
724     programStats.movelist[0] = 0;
725     programStats.depth = 0;
726     programStats.nr_moves = 0;
727     programStats.moves_left = 0;
728     programStats.nodes = 0;
729     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
730     programStats.score = 0;
731     programStats.got_only_move = 0;
732     programStats.got_fail = 0;
733     programStats.line_is_book = 0;
734 }
735
736 void
737 CommonEngineInit ()
738 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739     if (appData.firstPlaysBlack) {
740         first.twoMachinesColor = "black\n";
741         second.twoMachinesColor = "white\n";
742     } else {
743         first.twoMachinesColor = "white\n";
744         second.twoMachinesColor = "black\n";
745     }
746
747     first.other = &second;
748     second.other = &first;
749
750     { float norm = 1;
751         if(appData.timeOddsMode) {
752             norm = appData.timeOdds[0];
753             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754         }
755         first.timeOdds  = appData.timeOdds[0]/norm;
756         second.timeOdds = appData.timeOdds[1]/norm;
757     }
758
759     if(programVersion) free(programVersion);
760     if (appData.noChessProgram) {
761         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762         sprintf(programVersion, "%s", PACKAGE_STRING);
763     } else {
764       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
767     }
768 }
769
770 void
771 UnloadEngine (ChessProgramState *cps)
772 {
773         /* Kill off first chess program */
774         if (cps->isr != NULL)
775           RemoveInputSource(cps->isr);
776         cps->isr = NULL;
777
778         if (cps->pr != NoProc) {
779             ExitAnalyzeMode();
780             DoSleep( appData.delayBeforeQuit );
781             SendToProgram("quit\n", cps);
782             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783         }
784         cps->pr = NoProc;
785         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
786 }
787
788 void
789 ClearOptions (ChessProgramState *cps)
790 {
791     int i;
792     cps->nrOptions = cps->comboCnt = 0;
793     for(i=0; i<MAX_OPTIONS; i++) {
794         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795         cps->option[i].textValue = 0;
796     }
797 }
798
799 char *engineNames[] = {
800   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 N_("first"),
803   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
805 N_("second")
806 };
807
808 void
809 InitEngine (ChessProgramState *cps, int n)
810 {   // [HGM] all engine initialiation put in a function that does one engine
811
812     ClearOptions(cps);
813
814     cps->which = engineNames[n];
815     cps->maybeThinking = FALSE;
816     cps->pr = NoProc;
817     cps->isr = NULL;
818     cps->sendTime = 2;
819     cps->sendDrawOffers = 1;
820
821     cps->program = appData.chessProgram[n];
822     cps->host = appData.host[n];
823     cps->dir = appData.directory[n];
824     cps->initString = appData.engInitString[n];
825     cps->computerString = appData.computerString[n];
826     cps->useSigint  = TRUE;
827     cps->useSigterm = TRUE;
828     cps->reuse = appData.reuse[n];
829     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
830     cps->useSetboard = FALSE;
831     cps->useSAN = FALSE;
832     cps->usePing = FALSE;
833     cps->lastPing = 0;
834     cps->lastPong = 0;
835     cps->usePlayother = FALSE;
836     cps->useColors = TRUE;
837     cps->useUsermove = FALSE;
838     cps->sendICS = FALSE;
839     cps->sendName = appData.icsActive;
840     cps->sdKludge = FALSE;
841     cps->stKludge = FALSE;
842     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843     TidyProgramName(cps->program, cps->host, cps->tidy);
844     cps->matchWins = 0;
845     ASSIGN(cps->variants, appData.variant);
846     cps->analysisSupport = 2; /* detect */
847     cps->analyzing = FALSE;
848     cps->initDone = FALSE;
849     cps->reload = FALSE;
850
851     /* New features added by Tord: */
852     cps->useFEN960 = FALSE;
853     cps->useOOCastle = TRUE;
854     /* End of new features added by Tord. */
855     cps->fenOverride  = appData.fenOverride[n];
856
857     /* [HGM] time odds: set factor for each machine */
858     cps->timeOdds  = appData.timeOdds[n];
859
860     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861     cps->accumulateTC = appData.accumulateTC[n];
862     cps->maxNrOfSessions = 1;
863
864     /* [HGM] debug */
865     cps->debug = FALSE;
866
867     cps->drawDepth = appData.drawDepth[n];
868     cps->supportsNPS = UNKNOWN;
869     cps->memSize = FALSE;
870     cps->maxCores = FALSE;
871     ASSIGN(cps->egtFormats, "");
872
873     /* [HGM] options */
874     cps->optionSettings  = appData.engOptions[n];
875
876     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877     cps->isUCI = appData.isUCI[n]; /* [AS] */
878     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879     cps->highlight = 0;
880
881     if (appData.protocolVersion[n] > PROTOVER
882         || appData.protocolVersion[n] < 1)
883       {
884         char buf[MSG_SIZ];
885         int len;
886
887         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888                        appData.protocolVersion[n]);
889         if( (len >= MSG_SIZ) && appData.debugMode )
890           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891
892         DisplayFatalError(buf, 0, 2);
893       }
894     else
895       {
896         cps->protocolVersion = appData.protocolVersion[n];
897       }
898
899     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
900     ParseFeatures(appData.featureDefaults, cps);
901 }
902
903 ChessProgramState *savCps;
904
905 GameMode oldMode;
906
907 void
908 LoadEngine ()
909 {
910     int i;
911     if(WaitForEngine(savCps, LoadEngine)) return;
912     CommonEngineInit(); // recalculate time odds
913     if(gameInfo.variant != StringToVariant(appData.variant)) {
914         // we changed variant when loading the engine; this forces us to reset
915         Reset(TRUE, savCps != &first);
916         oldMode = BeginningOfGame; // to prevent restoring old mode
917     }
918     InitChessProgram(savCps, FALSE);
919     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920     DisplayMessage("", "");
921     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923     ThawUI();
924     SetGNUMode();
925     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
926 }
927
928 void
929 ReplaceEngine (ChessProgramState *cps, int n)
930 {
931     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932     keepInfo = 1;
933     if(oldMode != BeginningOfGame) EditGameEvent();
934     keepInfo = 0;
935     UnloadEngine(cps);
936     appData.noChessProgram = FALSE;
937     appData.clockMode = TRUE;
938     InitEngine(cps, n);
939     UpdateLogos(TRUE);
940     if(n) return; // only startup first engine immediately; second can wait
941     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
942     LoadEngine();
943 }
944
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947
948 static char resetOptions[] =
949         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953
954 void
955 FloatToFront(char **list, char *engineLine)
956 {
957     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958     int i=0;
959     if(appData.recentEngines <= 0) return;
960     TidyProgramName(engineLine, "localhost", tidy+1);
961     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962     strncpy(buf+1, *list, MSG_SIZ-50);
963     if(p = strstr(buf, tidy)) { // tidy name appears in list
964         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965         while(*p++ = *++q); // squeeze out
966     }
967     strcat(tidy, buf+1); // put list behind tidy name
968     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970     ASSIGN(*list, tidy+1);
971 }
972
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974
975 void
976 Load (ChessProgramState *cps, int i)
977 {
978     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984         appData.firstProtocolVersion = PROTOVER;
985         ParseArgsFromString(buf);
986         SwapEngines(i);
987         ReplaceEngine(cps, i);
988         FloatToFront(&appData.recentEngineList, engineLine);
989         return;
990     }
991     p = engineName;
992     while(q = strchr(p, SLASH)) p = q+1;
993     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994     if(engineDir[0] != NULLCHAR) {
995         ASSIGN(appData.directory[i], engineDir); p = engineName;
996     } else if(p != engineName) { // derive directory from engine path, when not given
997         p[-1] = 0;
998         ASSIGN(appData.directory[i], engineName);
999         p[-1] = SLASH;
1000         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001     } else { ASSIGN(appData.directory[i], "."); }
1002     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003     if(params[0]) {
1004         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005         snprintf(command, MSG_SIZ, "%s %s", p, params);
1006         p = command;
1007     }
1008     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009     ASSIGN(appData.chessProgram[i], p);
1010     appData.isUCI[i] = isUCI;
1011     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012     appData.hasOwnBookUCI[i] = hasBook;
1013     if(!nickName[0]) useNick = FALSE;
1014     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1015     if(addToList) {
1016         int len;
1017         char quote;
1018         q = firstChessProgramNames;
1019         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022                         quote, p, quote, appData.directory[i],
1023                         useNick ? " -fn \"" : "",
1024                         useNick ? nickName : "",
1025                         useNick ? "\"" : "",
1026                         v1 ? " -firstProtocolVersion 1" : "",
1027                         hasBook ? "" : " -fNoOwnBookUCI",
1028                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029                         storeVariant ? " -variant " : "",
1030                         storeVariant ? VariantName(gameInfo.variant) : "");
1031         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033         if(insert != q) insert[-1] = NULLCHAR;
1034         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035         if(q)   free(q);
1036         FloatToFront(&appData.recentEngineList, buf);
1037     }
1038     ReplaceEngine(cps, i);
1039 }
1040
1041 void
1042 InitTimeControls ()
1043 {
1044     int matched, min, sec;
1045     /*
1046      * Parse timeControl resource
1047      */
1048     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049                           appData.movesPerSession)) {
1050         char buf[MSG_SIZ];
1051         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052         DisplayFatalError(buf, 0, 2);
1053     }
1054
1055     /*
1056      * Parse searchTime resource
1057      */
1058     if (*appData.searchTime != NULLCHAR) {
1059         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060         if (matched == 1) {
1061             searchTime = min * 60;
1062         } else if (matched == 2) {
1063             searchTime = min * 60 + sec;
1064         } else {
1065             char buf[MSG_SIZ];
1066             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067             DisplayFatalError(buf, 0, 2);
1068         }
1069     }
1070 }
1071
1072 void
1073 InitBackEnd1 ()
1074 {
1075
1076     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078
1079     GetTimeMark(&programStartTime);
1080     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081     appData.seedBase = random() + (random()<<15);
1082     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083
1084     ClearProgramStats();
1085     programStats.ok_to_send = 1;
1086     programStats.seen_stat = 0;
1087
1088     /*
1089      * Initialize game list
1090      */
1091     ListNew(&gameList);
1092
1093
1094     /*
1095      * Internet chess server status
1096      */
1097     if (appData.icsActive) {
1098         appData.matchMode = FALSE;
1099         appData.matchGames = 0;
1100 #if ZIPPY
1101         appData.noChessProgram = !appData.zippyPlay;
1102 #else
1103         appData.zippyPlay = FALSE;
1104         appData.zippyTalk = FALSE;
1105         appData.noChessProgram = TRUE;
1106 #endif
1107         if (*appData.icsHelper != NULLCHAR) {
1108             appData.useTelnet = TRUE;
1109             appData.telnetProgram = appData.icsHelper;
1110         }
1111     } else {
1112         appData.zippyTalk = appData.zippyPlay = FALSE;
1113     }
1114
1115     /* [AS] Initialize pv info list [HGM] and game state */
1116     {
1117         int i, j;
1118
1119         for( i=0; i<=framePtr; i++ ) {
1120             pvInfoList[i].depth = -1;
1121             boards[i][EP_STATUS] = EP_NONE;
1122             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1123         }
1124     }
1125
1126     InitTimeControls();
1127
1128     /* [AS] Adjudication threshold */
1129     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130
1131     InitEngine(&first, 0);
1132     InitEngine(&second, 1);
1133     CommonEngineInit();
1134
1135     pairing.which = "pairing"; // pairing engine
1136     pairing.pr = NoProc;
1137     pairing.isr = NULL;
1138     pairing.program = appData.pairingEngine;
1139     pairing.host = "localhost";
1140     pairing.dir = ".";
1141
1142     if (appData.icsActive) {
1143         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1144     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145         appData.clockMode = FALSE;
1146         first.sendTime = second.sendTime = 0;
1147     }
1148
1149 #if ZIPPY
1150     /* Override some settings from environment variables, for backward
1151        compatibility.  Unfortunately it's not feasible to have the env
1152        vars just set defaults, at least in xboard.  Ugh.
1153     */
1154     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1155       ZippyInit();
1156     }
1157 #endif
1158
1159     if (!appData.icsActive) {
1160       char buf[MSG_SIZ];
1161       int len;
1162
1163       /* Check for variants that are supported only in ICS mode,
1164          or not at all.  Some that are accepted here nevertheless
1165          have bugs; see comments below.
1166       */
1167       VariantClass variant = StringToVariant(appData.variant);
1168       switch (variant) {
1169       case VariantBughouse:     /* need four players and two boards */
1170       case VariantKriegspiel:   /* need to hide pieces and move details */
1171         /* case VariantFischeRandom: (Fabien: moved below) */
1172         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173         if( (len >= MSG_SIZ) && appData.debugMode )
1174           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175
1176         DisplayFatalError(buf, 0, 2);
1177         return;
1178
1179       case VariantUnknown:
1180       case VariantLoadable:
1181       case Variant29:
1182       case Variant30:
1183       case Variant31:
1184       case Variant32:
1185       case Variant33:
1186       case Variant34:
1187       case Variant35:
1188       case Variant36:
1189       default:
1190         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191         if( (len >= MSG_SIZ) && appData.debugMode )
1192           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193
1194         DisplayFatalError(buf, 0, 2);
1195         return;
1196
1197       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1198       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1199       case VariantGothic:     /* [HGM] should work */
1200       case VariantCapablanca: /* [HGM] should work */
1201       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1202       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1203       case VariantChu:        /* [HGM] experimental */
1204       case VariantKnightmate: /* [HGM] should work */
1205       case VariantCylinder:   /* [HGM] untested */
1206       case VariantFalcon:     /* [HGM] untested */
1207       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208                                  offboard interposition not understood */
1209       case VariantNormal:     /* definitely works! */
1210       case VariantWildCastle: /* pieces not automatically shuffled */
1211       case VariantNoCastle:   /* pieces not automatically shuffled */
1212       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213       case VariantLosers:     /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantSuicide:    /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantGiveaway:   /* should work except for win condition,
1218                                  and doesn't know captures are mandatory */
1219       case VariantTwoKings:   /* should work */
1220       case VariantAtomic:     /* should work except for win condition */
1221       case Variant3Check:     /* should work except for win condition */
1222       case VariantShatranj:   /* should work except for all win conditions */
1223       case VariantMakruk:     /* should work except for draw countdown */
1224       case VariantASEAN :     /* should work except for draw countdown */
1225       case VariantBerolina:   /* might work if TestLegality is off */
1226       case VariantCapaRandom: /* should work */
1227       case VariantJanus:      /* should work */
1228       case VariantSuper:      /* experimental */
1229       case VariantGreat:      /* experimental, requires legality testing to be off */
1230       case VariantSChess:     /* S-Chess, should work */
1231       case VariantGrand:      /* should work */
1232       case VariantSpartan:    /* should work */
1233       case VariantLion:       /* should work */
1234       case VariantChuChess:   /* should work */
1235         break;
1236       }
1237     }
1238
1239 }
1240
1241 int
1242 NextIntegerFromString (char ** str, long * value)
1243 {
1244     int result = -1;
1245     char * s = *str;
1246
1247     while( *s == ' ' || *s == '\t' ) {
1248         s++;
1249     }
1250
1251     *value = 0;
1252
1253     if( *s >= '0' && *s <= '9' ) {
1254         while( *s >= '0' && *s <= '9' ) {
1255             *value = *value * 10 + (*s - '0');
1256             s++;
1257         }
1258
1259         result = 0;
1260     }
1261
1262     *str = s;
1263
1264     return result;
1265 }
1266
1267 int
1268 NextTimeControlFromString (char ** str, long * value)
1269 {
1270     long temp;
1271     int result = NextIntegerFromString( str, &temp );
1272
1273     if( result == 0 ) {
1274         *value = temp * 60; /* Minutes */
1275         if( **str == ':' ) {
1276             (*str)++;
1277             result = NextIntegerFromString( str, &temp );
1278             *value += temp; /* Seconds */
1279         }
1280     }
1281
1282     return result;
1283 }
1284
1285 int
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288     int result = -1, type = 0; long temp, temp2;
1289
1290     if(**str != ':') return -1; // old params remain in force!
1291     (*str)++;
1292     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293     if( NextIntegerFromString( str, &temp ) ) return -1;
1294     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295
1296     if(**str != '/') {
1297         /* time only: incremental or sudden-death time control */
1298         if(**str == '+') { /* increment follows; read it */
1299             (*str)++;
1300             if(**str == '!') type = *(*str)++; // Bronstein TC
1301             if(result = NextIntegerFromString( str, &temp2)) return -1;
1302             *inc = temp2 * 1000;
1303             if(**str == '.') { // read fraction of increment
1304                 char *start = ++(*str);
1305                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306                 temp2 *= 1000;
1307                 while(start++ < *str) temp2 /= 10;
1308                 *inc += temp2;
1309             }
1310         } else *inc = 0;
1311         *moves = 0; *tc = temp * 1000; *incType = type;
1312         return 0;
1313     }
1314
1315     (*str)++; /* classical time control */
1316     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1317
1318     if(result == 0) {
1319         *moves = temp;
1320         *tc    = temp2 * 1000;
1321         *inc   = 0;
1322         *incType = type;
1323     }
1324     return result;
1325 }
1326
1327 int
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 {   /* [HGM] get time to add from the multi-session time-control string */
1330     int incType, moves=1; /* kludge to force reading of first session */
1331     long time, increment;
1332     char *s = tcString;
1333
1334     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335     do {
1336         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338         if(movenr == -1) return time;    /* last move before new session     */
1339         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341         if(!moves) return increment;     /* current session is incremental   */
1342         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343     } while(movenr >= -1);               /* try again for next session       */
1344
1345     return 0; // no new time quota on this move
1346 }
1347
1348 int
1349 ParseTimeControl (char *tc, float ti, int mps)
1350 {
1351   long tc1;
1352   long tc2;
1353   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354   int min, sec=0;
1355
1356   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1359   if(ti > 0) {
1360
1361     if(mps)
1362       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363     else
1364       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365   } else {
1366     if(mps)
1367       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368     else
1369       snprintf(buf, MSG_SIZ, ":%s", mytc);
1370   }
1371   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372
1373   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1374     return FALSE;
1375   }
1376
1377   if( *tc == '/' ) {
1378     /* Parse second time control */
1379     tc++;
1380
1381     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1382       return FALSE;
1383     }
1384
1385     if( tc2 == 0 ) {
1386       return FALSE;
1387     }
1388
1389     timeControl_2 = tc2 * 1000;
1390   }
1391   else {
1392     timeControl_2 = 0;
1393   }
1394
1395   if( tc1 == 0 ) {
1396     return FALSE;
1397   }
1398
1399   timeControl = tc1 * 1000;
1400
1401   if (ti >= 0) {
1402     timeIncrement = ti * 1000;  /* convert to ms */
1403     movesPerSession = 0;
1404   } else {
1405     timeIncrement = 0;
1406     movesPerSession = mps;
1407   }
1408   return TRUE;
1409 }
1410
1411 void
1412 InitBackEnd2 ()
1413 {
1414     if (appData.debugMode) {
1415 #    ifdef __GIT_VERSION
1416       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 #    else
1418       fprintf(debugFP, "Version: %s\n", programVersion);
1419 #    endif
1420     }
1421     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422
1423     set_cont_sequence(appData.wrapContSeq);
1424     if (appData.matchGames > 0) {
1425         appData.matchMode = TRUE;
1426     } else if (appData.matchMode) {
1427         appData.matchGames = 1;
1428     }
1429     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430         appData.matchGames = appData.sameColorGames;
1431     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434     }
1435     Reset(TRUE, FALSE);
1436     if (appData.noChessProgram || first.protocolVersion == 1) {
1437       InitBackEnd3();
1438     } else {
1439       /* kludge: allow timeout for initial "feature" commands */
1440       FreezeUI();
1441       DisplayMessage("", _("Starting chess program"));
1442       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1443     }
1444 }
1445
1446 int
1447 CalculateIndex (int index, int gameNr)
1448 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449     int res;
1450     if(index > 0) return index; // fixed nmber
1451     if(index == 0) return 1;
1452     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1454     return res;
1455 }
1456
1457 int
1458 LoadGameOrPosition (int gameNr)
1459 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460     if (*appData.loadGameFile != NULLCHAR) {
1461         if (!LoadGameFromFile(appData.loadGameFile,
1462                 CalculateIndex(appData.loadGameIndex, gameNr),
1463                               appData.loadGameFile, FALSE)) {
1464             DisplayFatalError(_("Bad game file"), 0, 1);
1465             return 0;
1466         }
1467     } else if (*appData.loadPositionFile != NULLCHAR) {
1468         if (!LoadPositionFromFile(appData.loadPositionFile,
1469                 CalculateIndex(appData.loadPositionIndex, gameNr),
1470                                   appData.loadPositionFile)) {
1471             DisplayFatalError(_("Bad position file"), 0, 1);
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 void
1479 ReserveGame (int gameNr, char resChar)
1480 {
1481     FILE *tf = fopen(appData.tourneyFile, "r+");
1482     char *p, *q, c, buf[MSG_SIZ];
1483     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484     safeStrCpy(buf, lastMsg, MSG_SIZ);
1485     DisplayMessage(_("Pick new game"), "");
1486     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487     ParseArgsFromFile(tf);
1488     p = q = appData.results;
1489     if(appData.debugMode) {
1490       char *r = appData.participants;
1491       fprintf(debugFP, "results = '%s'\n", p);
1492       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493       fprintf(debugFP, "\n");
1494     }
1495     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496     nextGame = q - p;
1497     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498     safeStrCpy(q, p, strlen(p) + 2);
1499     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503         q[nextGame] = '*';
1504     }
1505     fseek(tf, -(strlen(p)+4), SEEK_END);
1506     c = fgetc(tf);
1507     if(c != '"') // depending on DOS or Unix line endings we can be one off
1508          fseek(tf, -(strlen(p)+2), SEEK_END);
1509     else fseek(tf, -(strlen(p)+3), SEEK_END);
1510     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511     DisplayMessage(buf, "");
1512     free(p); appData.results = q;
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515       int round = appData.defaultMatchGames * appData.tourneyType;
1516       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1517          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518         UnloadEngine(&first);  // next game belongs to other pairing;
1519         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520     }
1521     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1522 }
1523
1524 void
1525 MatchEvent (int mode)
1526 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527         int dummy;
1528         if(matchMode) { // already in match mode: switch it off
1529             abortMatch = TRUE;
1530             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531             return;
1532         }
1533 //      if(gameMode != BeginningOfGame) {
1534 //          DisplayError(_("You can only start a match from the initial position."), 0);
1535 //          return;
1536 //      }
1537         abortMatch = FALSE;
1538         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539         /* Set up machine vs. machine match */
1540         nextGame = 0;
1541         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542         if(appData.tourneyFile[0]) {
1543             ReserveGame(-1, 0);
1544             if(nextGame > appData.matchGames) {
1545                 char buf[MSG_SIZ];
1546                 if(strchr(appData.results, '*') == NULL) {
1547                     FILE *f;
1548                     appData.tourneyCycles++;
1549                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550                         fclose(f);
1551                         NextTourneyGame(-1, &dummy);
1552                         ReserveGame(-1, 0);
1553                         if(nextGame <= appData.matchGames) {
1554                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555                             matchMode = mode;
1556                             ScheduleDelayedEvent(NextMatchGame, 10000);
1557                             return;
1558                         }
1559                     }
1560                 }
1561                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562                 DisplayError(buf, 0);
1563                 appData.tourneyFile[0] = 0;
1564                 return;
1565             }
1566         } else
1567         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1568             DisplayFatalError(_("Can't have a match with no chess programs"),
1569                               0, 2);
1570             return;
1571         }
1572         matchMode = mode;
1573         matchGame = roundNr = 1;
1574         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1575         NextMatchGame();
1576 }
1577
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579
1580 void
1581 InitBackEnd3 P((void))
1582 {
1583     GameMode initialMode;
1584     char buf[MSG_SIZ];
1585     int err, len;
1586
1587     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1588        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1589         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1590        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1591        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592         char c, *q = first.variants, *p = strchr(q, ',');
1593         if(p) *p = NULLCHAR;
1594         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595             int w, h, s;
1596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599             Reset(TRUE, FALSE);         // and re-initialize
1600         }
1601         if(p) *p = ',';
1602     }
1603
1604     InitChessProgram(&first, startedFromSetupPosition);
1605
1606     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1607         free(programVersion);
1608         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611     }
1612
1613     if (appData.icsActive) {
1614 #ifdef WIN32
1615         /* [DM] Make a console window if needed [HGM] merged ifs */
1616         ConsoleCreate();
1617 #endif
1618         err = establish();
1619         if (err != 0)
1620           {
1621             if (*appData.icsCommPort != NULLCHAR)
1622               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623                              appData.icsCommPort);
1624             else
1625               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626                         appData.icsHost, appData.icsPort);
1627
1628             if( (len >= MSG_SIZ) && appData.debugMode )
1629               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631             DisplayFatalError(buf, err, 1);
1632             return;
1633         }
1634         SetICSMode();
1635         telnetISR =
1636           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637         fromUserISR =
1638           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641     } else if (appData.noChessProgram) {
1642         SetNCPMode();
1643     } else {
1644         SetGNUMode();
1645     }
1646
1647     if (*appData.cmailGameName != NULLCHAR) {
1648         SetCmailMode();
1649         OpenLoopback(&cmailPR);
1650         cmailISR =
1651           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1652     }
1653
1654     ThawUI();
1655     DisplayMessage("", "");
1656     if (StrCaseCmp(appData.initialMode, "") == 0) {
1657       initialMode = BeginningOfGame;
1658       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662         ModeHighlight();
1663       }
1664     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665       initialMode = TwoMachinesPlay;
1666     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667       initialMode = AnalyzeFile;
1668     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669       initialMode = AnalyzeMode;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671       initialMode = MachinePlaysWhite;
1672     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673       initialMode = MachinePlaysBlack;
1674     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675       initialMode = EditGame;
1676     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677       initialMode = EditPosition;
1678     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679       initialMode = Training;
1680     } else {
1681       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682       if( (len >= MSG_SIZ) && appData.debugMode )
1683         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684
1685       DisplayFatalError(buf, 0, 2);
1686       return;
1687     }
1688
1689     if (appData.matchMode) {
1690         if(appData.tourneyFile[0]) { // start tourney from command line
1691             FILE *f;
1692             if(f = fopen(appData.tourneyFile, "r")) {
1693                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694                 fclose(f);
1695                 appData.clockMode = TRUE;
1696                 SetGNUMode();
1697             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698         }
1699         MatchEvent(TRUE);
1700     } else if (*appData.cmailGameName != NULLCHAR) {
1701         /* Set up cmail mode */
1702         ReloadCmailMsgEvent(TRUE);
1703     } else {
1704         /* Set up other modes */
1705         if (initialMode == AnalyzeFile) {
1706           if (*appData.loadGameFile == NULLCHAR) {
1707             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1708             return;
1709           }
1710         }
1711         if (*appData.loadGameFile != NULLCHAR) {
1712             (void) LoadGameFromFile(appData.loadGameFile,
1713                                     appData.loadGameIndex,
1714                                     appData.loadGameFile, TRUE);
1715         } else if (*appData.loadPositionFile != NULLCHAR) {
1716             (void) LoadPositionFromFile(appData.loadPositionFile,
1717                                         appData.loadPositionIndex,
1718                                         appData.loadPositionFile);
1719             /* [HGM] try to make self-starting even after FEN load */
1720             /* to allow automatic setup of fairy variants with wtm */
1721             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722                 gameMode = BeginningOfGame;
1723                 setboardSpoiledMachineBlack = 1;
1724             }
1725             /* [HGM] loadPos: make that every new game uses the setup */
1726             /* from file as long as we do not switch variant          */
1727             if(!blackPlaysFirst) {
1728                 startedFromPositionFile = TRUE;
1729                 CopyBoard(filePosition, boards[0]);
1730             }
1731         }
1732         if (initialMode == AnalyzeMode) {
1733           if (appData.noChessProgram) {
1734             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735             return;
1736           }
1737           if (appData.icsActive) {
1738             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1739             return;
1740           }
1741           AnalyzeModeEvent();
1742         } else if (initialMode == AnalyzeFile) {
1743           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744           ShowThinkingEvent();
1745           AnalyzeFileEvent();
1746           AnalysisPeriodicEvent(1);
1747         } else if (initialMode == MachinePlaysWhite) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1750                               0, 2);
1751             return;
1752           }
1753           if (appData.icsActive) {
1754             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1755                               0, 2);
1756             return;
1757           }
1758           MachineWhiteEvent();
1759         } else if (initialMode == MachinePlaysBlack) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineBlackEvent();
1771         } else if (initialMode == TwoMachinesPlay) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           TwoMachinesEvent();
1783         } else if (initialMode == EditGame) {
1784           EditGameEvent();
1785         } else if (initialMode == EditPosition) {
1786           EditPositionEvent();
1787         } else if (initialMode == Training) {
1788           if (*appData.loadGameFile == NULLCHAR) {
1789             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1790             return;
1791           }
1792           TrainingEvent();
1793         }
1794     }
1795 }
1796
1797 void
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 {
1800     DisplayBook(current+1);
1801
1802     MoveHistorySet( movelist, first, last, current, pvInfoList );
1803
1804     EvalGraphSet( first, last, current, pvInfoList );
1805
1806     MakeEngineOutputTitle();
1807 }
1808
1809 /*
1810  * Establish will establish a contact to a remote host.port.
1811  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812  *  used to talk to the host.
1813  * Returns 0 if okay, error code if not.
1814  */
1815 int
1816 establish ()
1817 {
1818     char buf[MSG_SIZ];
1819
1820     if (*appData.icsCommPort != NULLCHAR) {
1821         /* Talk to the host through a serial comm port */
1822         return OpenCommPort(appData.icsCommPort, &icsPR);
1823
1824     } else if (*appData.gateway != NULLCHAR) {
1825         if (*appData.remoteShell == NULLCHAR) {
1826             /* Use the rcmd protocol to run telnet program on a gateway host */
1827             snprintf(buf, sizeof(buf), "%s %s %s",
1828                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1829             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830
1831         } else {
1832             /* Use the rsh program to run telnet program on a gateway host */
1833             if (*appData.remoteUser == NULLCHAR) {
1834                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835                         appData.gateway, appData.telnetProgram,
1836                         appData.icsHost, appData.icsPort);
1837             } else {
1838                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839                         appData.remoteShell, appData.gateway,
1840                         appData.remoteUser, appData.telnetProgram,
1841                         appData.icsHost, appData.icsPort);
1842             }
1843             return StartChildProcess(buf, "", &icsPR);
1844
1845         }
1846     } else if (appData.useTelnet) {
1847         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848
1849     } else {
1850         /* TCP socket interface differs somewhat between
1851            Unix and NT; handle details in the front end.
1852            */
1853         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1854     }
1855 }
1856
1857 void
1858 EscapeExpand (char *p, char *q)
1859 {       // [HGM] initstring: routine to shape up string arguments
1860         while(*p++ = *q++) if(p[-1] == '\\')
1861             switch(*q++) {
1862                 case 'n': p[-1] = '\n'; break;
1863                 case 'r': p[-1] = '\r'; break;
1864                 case 't': p[-1] = '\t'; break;
1865                 case '\\': p[-1] = '\\'; break;
1866                 case 0: *p = 0; return;
1867                 default: p[-1] = q[-1]; break;
1868             }
1869 }
1870
1871 void
1872 show_bytes (FILE *fp, char *buf, int count)
1873 {
1874     while (count--) {
1875         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876             fprintf(fp, "\\%03o", *buf & 0xff);
1877         } else {
1878             putc(*buf, fp);
1879         }
1880         buf++;
1881     }
1882     fflush(fp);
1883 }
1884
1885 /* Returns an errno value */
1886 int
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 {
1889     char buf[8192], *p, *q, *buflim;
1890     int left, newcount, outcount;
1891
1892     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893         *appData.gateway != NULLCHAR) {
1894         if (appData.debugMode) {
1895             fprintf(debugFP, ">ICS: ");
1896             show_bytes(debugFP, message, count);
1897             fprintf(debugFP, "\n");
1898         }
1899         return OutputToProcess(pr, message, count, outError);
1900     }
1901
1902     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1903     p = message;
1904     q = buf;
1905     left = count;
1906     newcount = 0;
1907     while (left) {
1908         if (q >= buflim) {
1909             if (appData.debugMode) {
1910                 fprintf(debugFP, ">ICS: ");
1911                 show_bytes(debugFP, buf, newcount);
1912                 fprintf(debugFP, "\n");
1913             }
1914             outcount = OutputToProcess(pr, buf, newcount, outError);
1915             if (outcount < newcount) return -1; /* to be sure */
1916             q = buf;
1917             newcount = 0;
1918         }
1919         if (*p == '\n') {
1920             *q++ = '\r';
1921             newcount++;
1922         } else if (((unsigned char) *p) == TN_IAC) {
1923             *q++ = (char) TN_IAC;
1924             newcount ++;
1925         }
1926         *q++ = *p++;
1927         newcount++;
1928         left--;
1929     }
1930     if (appData.debugMode) {
1931         fprintf(debugFP, ">ICS: ");
1932         show_bytes(debugFP, buf, newcount);
1933         fprintf(debugFP, "\n");
1934     }
1935     outcount = OutputToProcess(pr, buf, newcount, outError);
1936     if (outcount < newcount) return -1; /* to be sure */
1937     return count;
1938 }
1939
1940 void
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 {
1943     int outError, outCount;
1944     static int gotEof = 0;
1945     static FILE *ini;
1946
1947     /* Pass data read from player on to ICS */
1948     if (count > 0) {
1949         gotEof = 0;
1950         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951         if (outCount < count) {
1952             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953         }
1954         if(have_sent_ICS_logon == 2) {
1955           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956             fprintf(ini, "%s", message);
1957             have_sent_ICS_logon = 3;
1958           } else
1959             have_sent_ICS_logon = 1;
1960         } else if(have_sent_ICS_logon == 3) {
1961             fprintf(ini, "%s", message);
1962             fclose(ini);
1963           have_sent_ICS_logon = 1;
1964         }
1965     } else if (count < 0) {
1966         RemoveInputSource(isr);
1967         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968     } else if (gotEof++ > 0) {
1969         RemoveInputSource(isr);
1970         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1971     }
1972 }
1973
1974 void
1975 KeepAlive ()
1976 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979     SendToICS("date\n");
1980     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 }
1982
1983 /* added routine for printf style output to ics */
1984 void
1985 ics_printf (char *format, ...)
1986 {
1987     char buffer[MSG_SIZ];
1988     va_list args;
1989
1990     va_start(args, format);
1991     vsnprintf(buffer, sizeof(buffer), format, args);
1992     buffer[sizeof(buffer)-1] = '\0';
1993     SendToICS(buffer);
1994     va_end(args);
1995 }
1996
1997 void
1998 SendToICS (char *s)
1999 {
2000     int count, outCount, outError;
2001
2002     if (icsPR == NoProc) return;
2003
2004     count = strlen(s);
2005     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006     if (outCount < count) {
2007         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2008     }
2009 }
2010
2011 /* This is used for sending logon scripts to the ICS. Sending
2012    without a delay causes problems when using timestamp on ICC
2013    (at least on my machine). */
2014 void
2015 SendToICSDelayed (char *s, long msdelay)
2016 {
2017     int count, outCount, outError;
2018
2019     if (icsPR == NoProc) return;
2020
2021     count = strlen(s);
2022     if (appData.debugMode) {
2023         fprintf(debugFP, ">ICS: ");
2024         show_bytes(debugFP, s, count);
2025         fprintf(debugFP, "\n");
2026     }
2027     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028                                       msdelay);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034
2035 /* Remove all highlighting escape sequences in s
2036    Also deletes any suffix starting with '('
2037    */
2038 char *
2039 StripHighlightAndTitle (char *s)
2040 {
2041     static char retbuf[MSG_SIZ];
2042     char *p = retbuf;
2043
2044     while (*s != NULLCHAR) {
2045         while (*s == '\033') {
2046             while (*s != NULLCHAR && !isalpha(*s)) s++;
2047             if (*s != NULLCHAR) s++;
2048         }
2049         while (*s != NULLCHAR && *s != '\033') {
2050             if (*s == '(' || *s == '[') {
2051                 *p = NULLCHAR;
2052                 return retbuf;
2053             }
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 /* Remove all highlighting escape sequences in s */
2062 char *
2063 StripHighlight (char *s)
2064 {
2065     static char retbuf[MSG_SIZ];
2066     char *p = retbuf;
2067
2068     while (*s != NULLCHAR) {
2069         while (*s == '\033') {
2070             while (*s != NULLCHAR && !isalpha(*s)) s++;
2071             if (*s != NULLCHAR) s++;
2072         }
2073         while (*s != NULLCHAR && *s != '\033') {
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2083 char *
2084 VariantName (VariantClass v)
2085 {
2086     if(v == VariantUnknown || *engineVariant) return engineVariant;
2087     return variantNames[v];
2088 }
2089
2090
2091 /* Identify a variant from the strings the chess servers use or the
2092    PGN Variant tag names we use. */
2093 VariantClass
2094 StringToVariant (char *e)
2095 {
2096     char *p;
2097     int wnum = -1;
2098     VariantClass v = VariantNormal;
2099     int i, found = FALSE;
2100     char buf[MSG_SIZ];
2101     int len;
2102
2103     if (!e) return v;
2104
2105     /* [HGM] skip over optional board-size prefixes */
2106     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108         while( *e++ != '_');
2109     }
2110
2111     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2112         v = VariantNormal;
2113         found = TRUE;
2114     } else
2115     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116       if (p = StrCaseStr(e, variantNames[i])) {
2117         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118         v = (VariantClass) i;
2119         found = TRUE;
2120         break;
2121       }
2122     }
2123
2124     if (!found) {
2125       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126           || StrCaseStr(e, "wild/fr")
2127           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128         v = VariantFischeRandom;
2129       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130                  (i = 1, p = StrCaseStr(e, "w"))) {
2131         p += i;
2132         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2133         if (isdigit(*p)) {
2134           wnum = atoi(p);
2135         } else {
2136           wnum = -1;
2137         }
2138         switch (wnum) {
2139         case 0: /* FICS only, actually */
2140         case 1:
2141           /* Castling legal even if K starts on d-file */
2142           v = VariantWildCastle;
2143           break;
2144         case 2:
2145         case 3:
2146         case 4:
2147           /* Castling illegal even if K & R happen to start in
2148              normal positions. */
2149           v = VariantNoCastle;
2150           break;
2151         case 5:
2152         case 7:
2153         case 8:
2154         case 10:
2155         case 11:
2156         case 12:
2157         case 13:
2158         case 14:
2159         case 15:
2160         case 18:
2161         case 19:
2162           /* Castling legal iff K & R start in normal positions */
2163           v = VariantNormal;
2164           break;
2165         case 6:
2166         case 20:
2167         case 21:
2168           /* Special wilds for position setup; unclear what to do here */
2169           v = VariantLoadable;
2170           break;
2171         case 9:
2172           /* Bizarre ICC game */
2173           v = VariantTwoKings;
2174           break;
2175         case 16:
2176           v = VariantKriegspiel;
2177           break;
2178         case 17:
2179           v = VariantLosers;
2180           break;
2181         case 22:
2182           v = VariantFischeRandom;
2183           break;
2184         case 23:
2185           v = VariantCrazyhouse;
2186           break;
2187         case 24:
2188           v = VariantBughouse;
2189           break;
2190         case 25:
2191           v = Variant3Check;
2192           break;
2193         case 26:
2194           /* Not quite the same as FICS suicide! */
2195           v = VariantGiveaway;
2196           break;
2197         case 27:
2198           v = VariantAtomic;
2199           break;
2200         case 28:
2201           v = VariantShatranj;
2202           break;
2203
2204         /* Temporary names for future ICC types.  The name *will* change in
2205            the next xboard/WinBoard release after ICC defines it. */
2206         case 29:
2207           v = Variant29;
2208           break;
2209         case 30:
2210           v = Variant30;
2211           break;
2212         case 31:
2213           v = Variant31;
2214           break;
2215         case 32:
2216           v = Variant32;
2217           break;
2218         case 33:
2219           v = Variant33;
2220           break;
2221         case 34:
2222           v = Variant34;
2223           break;
2224         case 35:
2225           v = Variant35;
2226           break;
2227         case 36:
2228           v = Variant36;
2229           break;
2230         case 37:
2231           v = VariantShogi;
2232           break;
2233         case 38:
2234           v = VariantXiangqi;
2235           break;
2236         case 39:
2237           v = VariantCourier;
2238           break;
2239         case 40:
2240           v = VariantGothic;
2241           break;
2242         case 41:
2243           v = VariantCapablanca;
2244           break;
2245         case 42:
2246           v = VariantKnightmate;
2247           break;
2248         case 43:
2249           v = VariantFairy;
2250           break;
2251         case 44:
2252           v = VariantCylinder;
2253           break;
2254         case 45:
2255           v = VariantFalcon;
2256           break;
2257         case 46:
2258           v = VariantCapaRandom;
2259           break;
2260         case 47:
2261           v = VariantBerolina;
2262           break;
2263         case 48:
2264           v = VariantJanus;
2265           break;
2266         case 49:
2267           v = VariantSuper;
2268           break;
2269         case 50:
2270           v = VariantGreat;
2271           break;
2272         case -1:
2273           /* Found "wild" or "w" in the string but no number;
2274              must assume it's normal chess. */
2275           v = VariantNormal;
2276           break;
2277         default:
2278           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279           if( (len >= MSG_SIZ) && appData.debugMode )
2280             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281
2282           DisplayError(buf, 0);
2283           v = VariantUnknown;
2284           break;
2285         }
2286       }
2287     }
2288     if (appData.debugMode) {
2289       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290               e, wnum, VariantName(v));
2291     }
2292     return v;
2293 }
2294
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2297
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299    advance *index beyond it, and set leftover_start to the new value of
2300    *index; else return FALSE.  If pattern contains the character '*', it
2301    matches any sequence of characters not containing '\r', '\n', or the
2302    character following the '*' (if any), and the matched sequence(s) are
2303    copied into star_match.
2304    */
2305 int
2306 looking_at ( char *buf, int *index, char *pattern)
2307 {
2308     char *bufp = &buf[*index], *patternp = pattern;
2309     int star_count = 0;
2310     char *matchp = star_match[0];
2311
2312     for (;;) {
2313         if (*patternp == NULLCHAR) {
2314             *index = leftover_start = bufp - buf;
2315             *matchp = NULLCHAR;
2316             return TRUE;
2317         }
2318         if (*bufp == NULLCHAR) return FALSE;
2319         if (*patternp == '*') {
2320             if (*bufp == *(patternp + 1)) {
2321                 *matchp = NULLCHAR;
2322                 matchp = star_match[++star_count];
2323                 patternp += 2;
2324                 bufp++;
2325                 continue;
2326             } else if (*bufp == '\n' || *bufp == '\r') {
2327                 patternp++;
2328                 if (*patternp == NULLCHAR)
2329                   continue;
2330                 else
2331                   return FALSE;
2332             } else {
2333                 *matchp++ = *bufp++;
2334                 continue;
2335             }
2336         }
2337         if (*patternp != *bufp) return FALSE;
2338         patternp++;
2339         bufp++;
2340     }
2341 }
2342
2343 void
2344 SendToPlayer (char *data, int length)
2345 {
2346     int error, outCount;
2347     outCount = OutputToProcess(NoProc, data, length, &error);
2348     if (outCount < length) {
2349         DisplayFatalError(_("Error writing to display"), error, 1);
2350     }
2351 }
2352
2353 void
2354 PackHolding (char packed[], char *holding)
2355 {
2356     char *p = holding;
2357     char *q = packed;
2358     int runlength = 0;
2359     int curr = 9999;
2360     do {
2361         if (*p == curr) {
2362             runlength++;
2363         } else {
2364             switch (runlength) {
2365               case 0:
2366                 break;
2367               case 1:
2368                 *q++ = curr;
2369                 break;
2370               case 2:
2371                 *q++ = curr;
2372                 *q++ = curr;
2373                 break;
2374               default:
2375                 sprintf(q, "%d", runlength);
2376                 while (*q) q++;
2377                 *q++ = curr;
2378                 break;
2379             }
2380             runlength = 1;
2381             curr = *p;
2382         }
2383     } while (*p++);
2384     *q = NULLCHAR;
2385 }
2386
2387 /* Telnet protocol requests from the front end */
2388 void
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2390 {
2391     unsigned char msg[3];
2392     int outCount, outError;
2393
2394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395
2396     if (appData.debugMode) {
2397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2398         switch (ddww) {
2399           case TN_DO:
2400             ddwwStr = "DO";
2401             break;
2402           case TN_DONT:
2403             ddwwStr = "DONT";
2404             break;
2405           case TN_WILL:
2406             ddwwStr = "WILL";
2407             break;
2408           case TN_WONT:
2409             ddwwStr = "WONT";
2410             break;
2411           default:
2412             ddwwStr = buf1;
2413             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2414             break;
2415         }
2416         switch (option) {
2417           case TN_ECHO:
2418             optionStr = "ECHO";
2419             break;
2420           default:
2421             optionStr = buf2;
2422             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423             break;
2424         }
2425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2426     }
2427     msg[0] = TN_IAC;
2428     msg[1] = ddww;
2429     msg[2] = option;
2430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431     if (outCount < 3) {
2432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2433     }
2434 }
2435
2436 void
2437 DoEcho ()
2438 {
2439     if (!appData.icsActive) return;
2440     TelnetRequest(TN_DO, TN_ECHO);
2441 }
2442
2443 void
2444 DontEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DONT, TN_ECHO);
2448 }
2449
2450 void
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 {
2453     /* put the holdings sent to us by the server on the board holdings area */
2454     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2455     char p;
2456     ChessSquare piece;
2457
2458     if(gameInfo.holdingsWidth < 2)  return;
2459     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460         return; // prevent overwriting by pre-board holdings
2461
2462     if( (int)lowestPiece >= BlackPawn ) {
2463         holdingsColumn = 0;
2464         countsColumn = 1;
2465         holdingsStartRow = BOARD_HEIGHT-1;
2466         direction = -1;
2467     } else {
2468         holdingsColumn = BOARD_WIDTH-1;
2469         countsColumn = BOARD_WIDTH-2;
2470         holdingsStartRow = 0;
2471         direction = 1;
2472     }
2473
2474     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475         board[i][holdingsColumn] = EmptySquare;
2476         board[i][countsColumn]   = (ChessSquare) 0;
2477     }
2478     while( (p=*holdings++) != NULLCHAR ) {
2479         piece = CharToPiece( ToUpper(p) );
2480         if(piece == EmptySquare) continue;
2481         /*j = (int) piece - (int) WhitePawn;*/
2482         j = PieceToNumber(piece);
2483         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484         if(j < 0) continue;               /* should not happen */
2485         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487         board[holdingsStartRow+j*direction][countsColumn]++;
2488     }
2489 }
2490
2491
2492 void
2493 VariantSwitch (Board board, VariantClass newVariant)
2494 {
2495    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496    static Board oldBoard;
2497
2498    startedFromPositionFile = FALSE;
2499    if(gameInfo.variant == newVariant) return;
2500
2501    /* [HGM] This routine is called each time an assignment is made to
2502     * gameInfo.variant during a game, to make sure the board sizes
2503     * are set to match the new variant. If that means adding or deleting
2504     * holdings, we shift the playing board accordingly
2505     * This kludge is needed because in ICS observe mode, we get boards
2506     * of an ongoing game without knowing the variant, and learn about the
2507     * latter only later. This can be because of the move list we requested,
2508     * in which case the game history is refilled from the beginning anyway,
2509     * but also when receiving holdings of a crazyhouse game. In the latter
2510     * case we want to add those holdings to the already received position.
2511     */
2512
2513
2514    if (appData.debugMode) {
2515      fprintf(debugFP, "Switch board from %s to %s\n",
2516              VariantName(gameInfo.variant), VariantName(newVariant));
2517      setbuf(debugFP, NULL);
2518    }
2519    shuffleOpenings = 0;       /* [HGM] shuffle */
2520    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2521    switch(newVariant)
2522      {
2523      case VariantShogi:
2524        newWidth = 9;  newHeight = 9;
2525        gameInfo.holdingsSize = 7;
2526      case VariantBughouse:
2527      case VariantCrazyhouse:
2528        newHoldingsWidth = 2; break;
2529      case VariantGreat:
2530        newWidth = 10;
2531      case VariantSuper:
2532        newHoldingsWidth = 2;
2533        gameInfo.holdingsSize = 8;
2534        break;
2535      case VariantGothic:
2536      case VariantCapablanca:
2537      case VariantCapaRandom:
2538        newWidth = 10;
2539      default:
2540        newHoldingsWidth = gameInfo.holdingsSize = 0;
2541      };
2542
2543    if(newWidth  != gameInfo.boardWidth  ||
2544       newHeight != gameInfo.boardHeight ||
2545       newHoldingsWidth != gameInfo.holdingsWidth ) {
2546
2547      /* shift position to new playing area, if needed */
2548      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549        for(i=0; i<BOARD_HEIGHT; i++)
2550          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552              board[i][j];
2553        for(i=0; i<newHeight; i++) {
2554          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556        }
2557      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558        for(i=0; i<BOARD_HEIGHT; i++)
2559          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561              board[i][j];
2562      }
2563      board[HOLDINGS_SET] = 0;
2564      gameInfo.boardWidth  = newWidth;
2565      gameInfo.boardHeight = newHeight;
2566      gameInfo.holdingsWidth = newHoldingsWidth;
2567      gameInfo.variant = newVariant;
2568      InitDrawingSizes(-2, 0);
2569    } else gameInfo.variant = newVariant;
2570    CopyBoard(oldBoard, board);   // remember correctly formatted board
2571      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2572    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 }
2574
2575 static int loggedOn = FALSE;
2576
2577 /*-- Game start info cache: --*/
2578 int gs_gamenum;
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\   ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2586
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2589
2590 // [HGM] seekgraph
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2594 #define SQUARE 0x80
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2603
2604 void
2605 PlotSeekAd (int i)
2606 {
2607         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609         if(r < minRating+100 && r >=0 ) r = minRating+100;
2610         if(r > maxRating) r = maxRating;
2611         if(tc < 1.f) tc = 1.f;
2612         if(tc > 95.f) tc = 95.f;
2613         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614         y = ((double)r - minRating)/(maxRating - minRating)
2615             * (h-vMargin-squareSize/8-1) + vMargin;
2616         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617         if(strstr(seekAdList[i], " u ")) color = 1;
2618         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619            !strstr(seekAdList[i], "bullet") &&
2620            !strstr(seekAdList[i], "blitz") &&
2621            !strstr(seekAdList[i], "standard") ) color = 2;
2622         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2624 }
2625
2626 void
2627 PlotSingleSeekAd (int i)
2628 {
2629         PlotSeekAd(i);
2630 }
2631
2632 void
2633 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2634 {
2635         char buf[MSG_SIZ], *ext = "";
2636         VariantClass v = StringToVariant(type);
2637         if(strstr(type, "wild")) {
2638             ext = type + 4; // append wild number
2639             if(v == VariantFischeRandom) type = "chess960"; else
2640             if(v == VariantLoadable) type = "setup"; else
2641             type = VariantName(v);
2642         }
2643         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649             seekNrList[nrOfSeekAds] = nr;
2650             zList[nrOfSeekAds] = 0;
2651             seekAdList[nrOfSeekAds++] = StrSave(buf);
2652             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2653         }
2654 }
2655
2656 void
2657 EraseSeekDot (int i)
2658 {
2659     int x = xList[i], y = yList[i], d=squareSize/4, k;
2660     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662     // now replot every dot that overlapped
2663     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664         int xx = xList[k], yy = yList[k];
2665         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666             DrawSeekDot(xx, yy, colorList[k]);
2667     }
2668 }
2669
2670 void
2671 RemoveSeekAd (int nr)
2672 {
2673         int i;
2674         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675             EraseSeekDot(i);
2676             if(seekAdList[i]) free(seekAdList[i]);
2677             seekAdList[i] = seekAdList[--nrOfSeekAds];
2678             seekNrList[i] = seekNrList[nrOfSeekAds];
2679             ratingList[i] = ratingList[nrOfSeekAds];
2680             colorList[i]  = colorList[nrOfSeekAds];
2681             tcList[i] = tcList[nrOfSeekAds];
2682             xList[i]  = xList[nrOfSeekAds];
2683             yList[i]  = yList[nrOfSeekAds];
2684             zList[i]  = zList[nrOfSeekAds];
2685             seekAdList[nrOfSeekAds] = NULL;
2686             break;
2687         }
2688 }
2689
2690 Boolean
2691 MatchSoughtLine (char *line)
2692 {
2693     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694     int nr, base, inc, u=0; char dummy;
2695
2696     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698        (u=1) &&
2699        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2701         // match: compact and save the line
2702         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2703         return TRUE;
2704     }
2705     return FALSE;
2706 }
2707
2708 int
2709 DrawSeekGraph ()
2710 {
2711     int i;
2712     if(!seekGraphUp) return FALSE;
2713     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2715
2716     DrawSeekBackground(0, 0, w, h);
2717     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721         yy = h-1-yy;
2722         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723         if(i%500 == 0) {
2724             char buf[MSG_SIZ];
2725             snprintf(buf, MSG_SIZ, "%d", i);
2726             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727         }
2728     }
2729     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730     for(i=1; i<100; i+=(i<10?1:5)) {
2731         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734             char buf[MSG_SIZ];
2735             snprintf(buf, MSG_SIZ, "%d", i);
2736             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737         }
2738     }
2739     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2740     return TRUE;
2741 }
2742
2743 int
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 {
2746     static int lastDown = 0, displayed = 0, lastSecond;
2747     if(y < 0) return FALSE;
2748     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750         if(!seekGraphUp) return FALSE;
2751         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752         DrawPosition(TRUE, NULL);
2753         return TRUE;
2754     }
2755     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756         if(click == Release || moving) return FALSE;
2757         nrOfSeekAds = 0;
2758         soughtPending = TRUE;
2759         SendToICS(ics_prefix);
2760         SendToICS("sought\n"); // should this be "sought all"?
2761     } else { // issue challenge based on clicked ad
2762         int dist = 10000; int i, closest = 0, second = 0;
2763         for(i=0; i<nrOfSeekAds; i++) {
2764             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2765             if(d < dist) { dist = d; closest = i; }
2766             second += (d - zList[i] < 120); // count in-range ads
2767             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2768         }
2769         if(dist < 120) {
2770             char buf[MSG_SIZ];
2771             second = (second > 1);
2772             if(displayed != closest || second != lastSecond) {
2773                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774                 lastSecond = second; displayed = closest;
2775             }
2776             if(click == Press) {
2777                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778                 lastDown = closest;
2779                 return TRUE;
2780             } // on press 'hit', only show info
2781             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783             SendToICS(ics_prefix);
2784             SendToICS(buf);
2785             return TRUE; // let incoming board of started game pop down the graph
2786         } else if(click == Release) { // release 'miss' is ignored
2787             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788             if(moving == 2) { // right up-click
2789                 nrOfSeekAds = 0; // refresh graph
2790                 soughtPending = TRUE;
2791                 SendToICS(ics_prefix);
2792                 SendToICS("sought\n"); // should this be "sought all"?
2793             }
2794             return TRUE;
2795         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796         // press miss or release hit 'pop down' seek graph
2797         seekGraphUp = FALSE;
2798         DrawPosition(TRUE, NULL);
2799     }
2800     return TRUE;
2801 }
2802
2803 void
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 {
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2815
2816     static int started = STARTED_NONE;
2817     static char parse[20000];
2818     static int parse_pos = 0;
2819     static char buf[BUF_SIZE + 1];
2820     static int firstTime = TRUE, intfSet = FALSE;
2821     static ColorClass prevColor = ColorNormal;
2822     static int savingComment = FALSE;
2823     static int cmatch = 0; // continuation sequence match
2824     char *bp;
2825     char str[MSG_SIZ];
2826     int i, oldi;
2827     int buf_len;
2828     int next_out;
2829     int tkind;
2830     int backup;    /* [DM] For zippy color lines */
2831     char *p;
2832     char talker[MSG_SIZ]; // [HGM] chat
2833     int channel, collective=0;
2834
2835     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836
2837     if (appData.debugMode) {
2838       if (!error) {
2839         fprintf(debugFP, "<ICS: ");
2840         show_bytes(debugFP, data, count);
2841         fprintf(debugFP, "\n");
2842       }
2843     }
2844
2845     if (appData.debugMode) { int f = forwardMostMove;
2846         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849     }
2850     if (count > 0) {
2851         /* If last read ended with a partial line that we couldn't parse,
2852            prepend it to the new read and try again. */
2853         if (leftover_len > 0) {
2854             for (i=0; i<leftover_len; i++)
2855               buf[i] = buf[leftover_start + i];
2856         }
2857
2858     /* copy new characters into the buffer */
2859     bp = buf + leftover_len;
2860     buf_len=leftover_len;
2861     for (i=0; i<count; i++)
2862     {
2863         // ignore these
2864         if (data[i] == '\r')
2865             continue;
2866
2867         // join lines split by ICS?
2868         if (!appData.noJoin)
2869         {
2870             /*
2871                 Joining just consists of finding matches against the
2872                 continuation sequence, and discarding that sequence
2873                 if found instead of copying it.  So, until a match
2874                 fails, there's nothing to do since it might be the
2875                 complete sequence, and thus, something we don't want
2876                 copied.
2877             */
2878             if (data[i] == cont_seq[cmatch])
2879             {
2880                 cmatch++;
2881                 if (cmatch == strlen(cont_seq))
2882                 {
2883                     cmatch = 0; // complete match.  just reset the counter
2884
2885                     /*
2886                         it's possible for the ICS to not include the space
2887                         at the end of the last word, making our [correct]
2888                         join operation fuse two separate words.  the server
2889                         does this when the space occurs at the width setting.
2890                     */
2891                     if (!buf_len || buf[buf_len-1] != ' ')
2892                     {
2893                         *bp++ = ' ';
2894                         buf_len++;
2895                     }
2896                 }
2897                 continue;
2898             }
2899             else if (cmatch)
2900             {
2901                 /*
2902                     match failed, so we have to copy what matched before
2903                     falling through and copying this character.  In reality,
2904                     this will only ever be just the newline character, but
2905                     it doesn't hurt to be precise.
2906                 */
2907                 strncpy(bp, cont_seq, cmatch);
2908                 bp += cmatch;
2909                 buf_len += cmatch;
2910                 cmatch = 0;
2911             }
2912         }
2913
2914         // copy this char
2915         *bp++ = data[i];
2916         buf_len++;
2917     }
2918
2919         buf[buf_len] = NULLCHAR;
2920 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2921         next_out = 0;
2922         leftover_start = 0;
2923
2924         i = 0;
2925         while (i < buf_len) {
2926             /* Deal with part of the TELNET option negotiation
2927                protocol.  We refuse to do anything beyond the
2928                defaults, except that we allow the WILL ECHO option,
2929                which ICS uses to turn off password echoing when we are
2930                directly connected to it.  We reject this option
2931                if localLineEditing mode is on (always on in xboard)
2932                and we are talking to port 23, which might be a real
2933                telnet server that will try to keep WILL ECHO on permanently.
2934              */
2935             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937                 unsigned char option;
2938                 oldi = i;
2939                 switch ((unsigned char) buf[++i]) {
2940                   case TN_WILL:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<WILL ");
2943                     switch (option = (unsigned char) buf[++i]) {
2944                       case TN_ECHO:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "ECHO ");
2947                         /* Reply only if this is a change, according
2948                            to the protocol rules. */
2949                         if (remoteEchoOption) break;
2950                         if (appData.localLineEditing &&
2951                             atoi(appData.icsPort) == TN_PORT) {
2952                             TelnetRequest(TN_DONT, TN_ECHO);
2953                         } else {
2954                             EchoOff();
2955                             TelnetRequest(TN_DO, TN_ECHO);
2956                             remoteEchoOption = TRUE;
2957                         }
2958                         break;
2959                       default:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         /* Whatever this is, we don't want it. */
2963                         TelnetRequest(TN_DONT, option);
2964                         break;
2965                     }
2966                     break;
2967                   case TN_WONT:
2968                     if (appData.debugMode)
2969                       fprintf(debugFP, "\n<WONT ");
2970                     switch (option = (unsigned char) buf[++i]) {
2971                       case TN_ECHO:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "ECHO ");
2974                         /* Reply only if this is a change, according
2975                            to the protocol rules. */
2976                         if (!remoteEchoOption) break;
2977                         EchoOn();
2978                         TelnetRequest(TN_DONT, TN_ECHO);
2979                         remoteEchoOption = FALSE;
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", (unsigned char) option);
2984                         /* Whatever this is, it must already be turned
2985                            off, because we never agree to turn on
2986                            anything non-default, so according to the
2987                            protocol rules, we don't reply. */
2988                         break;
2989                     }
2990                     break;
2991                   case TN_DO:
2992                     if (appData.debugMode)
2993                       fprintf(debugFP, "\n<DO ");
2994                     switch (option = (unsigned char) buf[++i]) {
2995                       default:
2996                         /* Whatever this is, we refuse to do it. */
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", option);
2999                         TelnetRequest(TN_WONT, option);
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DONT:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DONT ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         /* Whatever this is, we are already not doing
3011                            it, because we never agree to do anything
3012                            non-default, so according to the protocol
3013                            rules, we don't reply. */
3014                         break;
3015                     }
3016                     break;
3017                   case TN_IAC:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<IAC ");
3020                     /* Doubled IAC; pass it through */
3021                     i--;
3022                     break;
3023                   default:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026                     /* Drop all other telnet commands on the floor */
3027                     break;
3028                 }
3029                 if (oldi > next_out)
3030                   SendToPlayer(&buf[next_out], oldi - next_out);
3031                 if (++i > next_out)
3032                   next_out = i;
3033                 continue;
3034             }
3035
3036             /* OK, this at least will *usually* work */
3037             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3038                 loggedOn = TRUE;
3039             }
3040
3041             if (loggedOn && !intfSet) {
3042                 if (ics_type == ICS_ICC) {
3043                   snprintf(str, MSG_SIZ,
3044                           "/set-quietly interface %s\n/set-quietly style 12\n",
3045                           programVersion);
3046                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3048                 } else if (ics_type == ICS_CHESSNET) {
3049                   snprintf(str, MSG_SIZ, "/style 12\n");
3050                 } else {
3051                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052                   strcat(str, programVersion);
3053                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 #ifdef WIN32
3057                   strcat(str, "$iset nohighlight 1\n");
3058 #endif
3059                   strcat(str, "$iset lock 1\n$style 12\n");
3060                 }
3061                 SendToICS(str);
3062                 NotifyFrontendLogin();
3063                 intfSet = TRUE;
3064             }
3065
3066             if (started == STARTED_COMMENT) {
3067                 /* Accumulate characters in comment */
3068                 parse[parse_pos++] = buf[i];
3069                 if (buf[i] == '\n') {
3070                     parse[parse_pos] = NULLCHAR;
3071                     if(chattingPartner>=0) {
3072                         char mess[MSG_SIZ];
3073                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074                         OutputChatMessage(chattingPartner, mess);
3075                         if(collective) { // broadcasted talk also goes to private chatbox of talker
3076                             int p;
3077                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080                                 OutputChatMessage(p, mess);
3081                                 break;
3082                             }
3083                         }
3084                         chattingPartner = -1;
3085                         next_out = i+1; // [HGM] suppress printing in ICS window
3086                     } else
3087                     if(!suppressKibitz) // [HGM] kibitz
3088                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3089                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3090                         int nrDigit = 0, nrAlph = 0, j;
3091                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3092                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3093                         parse[parse_pos] = NULLCHAR;
3094                         // try to be smart: if it does not look like search info, it should go to
3095                         // ICS interaction window after all, not to engine-output window.
3096                         for(j=0; j<parse_pos; j++) { // count letters and digits
3097                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3098                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3099                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3100                         }
3101                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3102                             int depth=0; float score;
3103                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3104                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3105                                 pvInfoList[forwardMostMove-1].depth = depth;
3106                                 pvInfoList[forwardMostMove-1].score = 100*score;
3107                             }
3108                             OutputKibitz(suppressKibitz, parse);
3109                         } else {
3110                             char tmp[MSG_SIZ];
3111                             if(gameMode == IcsObserving) // restore original ICS messages
3112                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3113                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3114                             else
3115                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3116                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3117                             SendToPlayer(tmp, strlen(tmp));
3118                         }
3119                         next_out = i+1; // [HGM] suppress printing in ICS window
3120                     }
3121                     started = STARTED_NONE;
3122                 } else {
3123                     /* Don't match patterns against characters in comment */
3124                     i++;
3125                     continue;
3126                 }
3127             }
3128             if (started == STARTED_CHATTER) {
3129                 if (buf[i] != '\n') {
3130                     /* Don't match patterns against characters in chatter */
3131                     i++;
3132                     continue;
3133                 }
3134                 started = STARTED_NONE;
3135                 if(suppressKibitz) next_out = i+1;
3136             }
3137
3138             /* Kludge to deal with rcmd protocol */
3139             if (firstTime && looking_at(buf, &i, "\001*")) {
3140                 DisplayFatalError(&buf[1], 0, 1);
3141                 continue;
3142             } else {
3143                 firstTime = FALSE;
3144             }
3145
3146             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3147                 ics_type = ICS_ICC;
3148                 ics_prefix = "/";
3149                 if (appData.debugMode)
3150                   fprintf(debugFP, "ics_type %d\n", ics_type);
3151                 continue;
3152             }
3153             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3154                 ics_type = ICS_FICS;
3155                 ics_prefix = "$";
3156                 if (appData.debugMode)
3157                   fprintf(debugFP, "ics_type %d\n", ics_type);
3158                 continue;
3159             }
3160             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3161                 ics_type = ICS_CHESSNET;
3162                 ics_prefix = "/";
3163                 if (appData.debugMode)
3164                   fprintf(debugFP, "ics_type %d\n", ics_type);
3165                 continue;
3166             }
3167
3168             if (!loggedOn &&
3169                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3170                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3171                  looking_at(buf, &i, "will be \"*\""))) {
3172               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3173               continue;
3174             }
3175
3176             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3177               char buf[MSG_SIZ];
3178               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3179               DisplayIcsInteractionTitle(buf);
3180               have_set_title = TRUE;
3181             }
3182
3183             /* skip finger notes */
3184             if (started == STARTED_NONE &&
3185                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3186                  (buf[i] == '1' && buf[i+1] == '0')) &&
3187                 buf[i+2] == ':' && buf[i+3] == ' ') {
3188               started = STARTED_CHATTER;
3189               i += 3;
3190               continue;
3191             }
3192
3193             oldi = i;
3194             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3195             if(appData.seekGraph) {
3196                 if(soughtPending && MatchSoughtLine(buf+i)) {
3197                     i = strstr(buf+i, "rated") - buf;
3198                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                     next_out = leftover_start = i;
3200                     started = STARTED_CHATTER;
3201                     suppressKibitz = TRUE;
3202                     continue;
3203                 }
3204                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3205                         && looking_at(buf, &i, "* ads displayed")) {
3206                     soughtPending = FALSE;
3207                     seekGraphUp = TRUE;
3208                     DrawSeekGraph();
3209                     continue;
3210                 }
3211                 if(appData.autoRefresh) {
3212                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3213                         int s = (ics_type == ICS_ICC); // ICC format differs
3214                         if(seekGraphUp)
3215                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3216                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3217                         looking_at(buf, &i, "*% "); // eat prompt
3218                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3219                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = i; // suppress
3221                         continue;
3222                     }
3223                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3224                         char *p = star_match[0];
3225                         while(*p) {
3226                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3227                             while(*p && *p++ != ' '); // next
3228                         }
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3231                         next_out = i;
3232                         continue;
3233                     }
3234                 }
3235             }
3236
3237             /* skip formula vars */
3238             if (started == STARTED_NONE &&
3239                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3240               started = STARTED_CHATTER;
3241               i += 3;
3242               continue;
3243             }
3244
3245             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3246             if (appData.autoKibitz && started == STARTED_NONE &&
3247                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3248                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3249                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3250                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3251                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3252                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3253                         suppressKibitz = TRUE;
3254                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                         next_out = i;
3256                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3257                                 && (gameMode == IcsPlayingWhite)) ||
3258                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3259                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3260                             started = STARTED_CHATTER; // own kibitz we simply discard
3261                         else {
3262                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3263                             parse_pos = 0; parse[0] = NULLCHAR;
3264                             savingComment = TRUE;
3265                             suppressKibitz = gameMode != IcsObserving ? 2 :
3266                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3267                         }
3268                         continue;
3269                 } else
3270                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3271                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3272                          && atoi(star_match[0])) {
3273                     // suppress the acknowledgements of our own autoKibitz
3274                     char *p;
3275                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3276                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3277                     SendToPlayer(star_match[0], strlen(star_match[0]));
3278                     if(looking_at(buf, &i, "*% ")) // eat prompt
3279                         suppressKibitz = FALSE;
3280                     next_out = i;
3281                     continue;
3282                 }
3283             } // [HGM] kibitz: end of patch
3284
3285             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3286
3287             // [HGM] chat: intercept tells by users for which we have an open chat window
3288             channel = -1;
3289             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3290                                            looking_at(buf, &i, "* whispers:") ||
3291                                            looking_at(buf, &i, "* kibitzes:") ||
3292                                            looking_at(buf, &i, "* shouts:") ||
3293                                            looking_at(buf, &i, "* c-shouts:") ||
3294                                            looking_at(buf, &i, "--> * ") ||
3295                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3296                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3297                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3299                 int p;
3300                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3301                 chattingPartner = -1; collective = 0;
3302
3303                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3304                 for(p=0; p<MAX_CHAT; p++) {
3305                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3306                     talker[0] = '['; strcat(talker, "] ");
3307                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3308                     collective = 1;
3309                     chattingPartner = p; break;
3310                     }
3311                 } else
3312                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3313                 for(p=0; p<MAX_CHAT; p++) {
3314                     if(!strcmp("kibitzes", chatPartner[p])) {
3315                         talker[0] = '['; strcat(talker, "] ");
3316                         collective = 1;
3317                         chattingPartner = p; break;
3318                     }
3319                 } else
3320                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3321                 for(p=0; p<MAX_CHAT; p++) {
3322                     if(!strcmp("whispers", chatPartner[p])) {
3323                         talker[0] = '['; strcat(talker, "] ");
3324                         collective = 1;
3325                         chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3329                   if(buf[i-8] == '-' && buf[i-3] == 't')
3330                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3331                     if(!strcmp("c-shouts", chatPartner[p])) {
3332                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3333                         collective = 1;
3334                         chattingPartner = p; break;
3335                     }
3336                   }
3337                   if(chattingPartner < 0)
3338                   for(p=0; p<MAX_CHAT; p++) {
3339                     if(!strcmp("shouts", chatPartner[p])) {
3340                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3341                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3342                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3343                         collective = 1;
3344                         chattingPartner = p; break;
3345                     }
3346                   }
3347                 }
3348                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3349                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3350                     talker[0] = 0; Colorize(ColorTell, FALSE);
3351                     chattingPartner = p; break;
3352                 }
3353                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3354                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3355                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3356                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3357                     started = STARTED_COMMENT;
3358                     parse_pos = 0; parse[0] = NULLCHAR;
3359                     savingComment = 3 + chattingPartner; // counts as TRUE
3360                     suppressKibitz = TRUE;
3361                     continue;
3362                 }
3363             } // [HGM] chat: end of patch
3364
3365           backup = i;
3366             if (appData.zippyTalk || appData.zippyPlay) {
3367                 /* [DM] Backup address for color zippy lines */
3368 #if ZIPPY
3369                if (loggedOn == TRUE)
3370                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3371                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3372 #endif
3373             } // [DM] 'else { ' deleted
3374                 if (
3375                     /* Regular tells and says */
3376                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3377                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3378                     looking_at(buf, &i, "* says: ") ||
3379                     /* Don't color "message" or "messages" output */
3380                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3381                     looking_at(buf, &i, "*. * at *:*: ") ||
3382                     looking_at(buf, &i, "--* (*:*): ") ||
3383                     /* Message notifications (same color as tells) */
3384                     looking_at(buf, &i, "* has left a message ") ||
3385                     looking_at(buf, &i, "* just sent you a message:\n") ||
3386                     /* Whispers and kibitzes */
3387                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3388                     looking_at(buf, &i, "* kibitzes: ") ||
3389                     /* Channel tells */
3390                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3391
3392                   if (tkind == 1 && strchr(star_match[0], ':')) {
3393                       /* Avoid "tells you:" spoofs in channels */
3394                      tkind = 3;
3395                   }
3396                   if (star_match[0][0] == NULLCHAR ||
3397                       strchr(star_match[0], ' ') ||
3398                       (tkind == 3 && strchr(star_match[1], ' '))) {
3399                     /* Reject bogus matches */
3400                     i = oldi;
3401                   } else {
3402                     if (appData.colorize) {
3403                       if (oldi > next_out) {
3404                         SendToPlayer(&buf[next_out], oldi - next_out);
3405                         next_out = oldi;
3406                       }
3407                       switch (tkind) {
3408                       case 1:
3409                         Colorize(ColorTell, FALSE);
3410                         curColor = ColorTell;
3411                         break;
3412                       case 2:
3413                         Colorize(ColorKibitz, FALSE);
3414                         curColor = ColorKibitz;
3415                         break;
3416                       case 3:
3417                         p = strrchr(star_match[1], '(');
3418                         if (p == NULL) {
3419                           p = star_match[1];
3420                         } else {
3421                           p++;
3422                         }
3423                         if (atoi(p) == 1) {
3424                           Colorize(ColorChannel1, FALSE);
3425                           curColor = ColorChannel1;
3426                         } else {
3427                           Colorize(ColorChannel, FALSE);
3428                           curColor = ColorChannel;
3429                         }
3430                         break;
3431                       case 5:
3432                         curColor = ColorNormal;
3433                         break;
3434                       }
3435                     }
3436                     if (started == STARTED_NONE && appData.autoComment &&
3437                         (gameMode == IcsObserving ||
3438                          gameMode == IcsPlayingWhite ||
3439                          gameMode == IcsPlayingBlack)) {
3440                       parse_pos = i - oldi;
3441                       memcpy(parse, &buf[oldi], parse_pos);
3442                       parse[parse_pos] = NULLCHAR;
3443                       started = STARTED_COMMENT;
3444                       savingComment = TRUE;
3445                     } else {
3446                       started = STARTED_CHATTER;
3447                       savingComment = FALSE;
3448                     }
3449                     loggedOn = TRUE;
3450                     continue;
3451                   }
3452                 }
3453
3454                 if (looking_at(buf, &i, "* s-shouts: ") ||
3455                     looking_at(buf, &i, "* c-shouts: ")) {
3456                     if (appData.colorize) {
3457                         if (oldi > next_out) {
3458                             SendToPlayer(&buf[next_out], oldi - next_out);
3459                             next_out = oldi;
3460                         }
3461                         Colorize(ColorSShout, FALSE);
3462                         curColor = ColorSShout;
3463                     }
3464                     loggedOn = TRUE;
3465                     started = STARTED_CHATTER;
3466                     continue;
3467                 }
3468
3469                 if (looking_at(buf, &i, "--->")) {
3470                     loggedOn = TRUE;
3471                     continue;
3472                 }
3473
3474                 if (looking_at(buf, &i, "* shouts: ") ||
3475                     looking_at(buf, &i, "--> ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorShout, FALSE);
3482                         curColor = ColorShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at( buf, &i, "Challenge:")) {
3490                     if (appData.colorize) {
3491                         if (oldi > next_out) {
3492                             SendToPlayer(&buf[next_out], oldi - next_out);
3493                             next_out = oldi;
3494                         }
3495                         Colorize(ColorChallenge, FALSE);
3496                         curColor = ColorChallenge;
3497                     }
3498                     loggedOn = TRUE;
3499                     continue;
3500                 }
3501
3502                 if (looking_at(buf, &i, "* offers you") ||
3503                     looking_at(buf, &i, "* offers to be") ||
3504                     looking_at(buf, &i, "* would like to") ||
3505                     looking_at(buf, &i, "* requests to") ||
3506                     looking_at(buf, &i, "Your opponent offers") ||
3507                     looking_at(buf, &i, "Your opponent requests")) {
3508
3509                     if (appData.colorize) {
3510                         if (oldi > next_out) {
3511                             SendToPlayer(&buf[next_out], oldi - next_out);
3512                             next_out = oldi;
3513                         }
3514                         Colorize(ColorRequest, FALSE);
3515                         curColor = ColorRequest;
3516                     }
3517                     continue;
3518                 }
3519
3520                 if (looking_at(buf, &i, "* (*) seeking")) {
3521                     if (appData.colorize) {
3522                         if (oldi > next_out) {
3523                             SendToPlayer(&buf[next_out], oldi - next_out);
3524                             next_out = oldi;
3525                         }
3526                         Colorize(ColorSeek, FALSE);
3527                         curColor = ColorSeek;
3528                     }
3529                     continue;
3530             }
3531
3532           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3533
3534             if (looking_at(buf, &i, "\\   ")) {
3535                 if (prevColor != ColorNormal) {
3536                     if (oldi > next_out) {
3537                         SendToPlayer(&buf[next_out], oldi - next_out);
3538                         next_out = oldi;
3539                     }
3540                     Colorize(prevColor, TRUE);
3541                     curColor = prevColor;
3542                 }
3543                 if (savingComment) {
3544                     parse_pos = i - oldi;
3545                     memcpy(parse, &buf[oldi], parse_pos);
3546                     parse[parse_pos] = NULLCHAR;
3547                     started = STARTED_COMMENT;
3548                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3549                         chattingPartner = savingComment - 3; // kludge to remember the box
3550                 } else {
3551                     started = STARTED_CHATTER;
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i, "Black Strength :") ||
3557                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3558                 looking_at(buf, &i, "<10>") ||
3559                 looking_at(buf, &i, "#@#")) {
3560                 /* Wrong board style */
3561                 loggedOn = TRUE;
3562                 SendToICS(ics_prefix);
3563                 SendToICS("set style 12\n");
3564                 SendToICS(ics_prefix);
3565                 SendToICS("refresh\n");
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "login:")) {
3570               if (!have_sent_ICS_logon) {
3571                 if(ICSInitScript())
3572                   have_sent_ICS_logon = 1;
3573                 else // no init script was found
3574                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3575               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3576                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3577               }
3578                 continue;
3579             }
3580
3581             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3582                 (looking_at(buf, &i, "\n<12> ") ||
3583                  looking_at(buf, &i, "<12> "))) {
3584                 loggedOn = TRUE;
3585                 if (oldi > next_out) {
3586                     SendToPlayer(&buf[next_out], oldi - next_out);
3587                 }
3588                 next_out = i;
3589                 started = STARTED_BOARD;
3590                 parse_pos = 0;
3591                 continue;
3592             }
3593
3594             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3595                 looking_at(buf, &i, "<b1> ")) {
3596                 if (oldi > next_out) {
3597                     SendToPlayer(&buf[next_out], oldi - next_out);
3598                 }
3599                 next_out = i;
3600                 started = STARTED_HOLDINGS;
3601                 parse_pos = 0;
3602                 continue;
3603             }
3604
3605             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3606                 loggedOn = TRUE;
3607                 /* Header for a move list -- first line */
3608
3609                 switch (ics_getting_history) {
3610                   case H_FALSE:
3611                     switch (gameMode) {
3612                       case IcsIdle:
3613                       case BeginningOfGame:
3614                         /* User typed "moves" or "oldmoves" while we
3615                            were idle.  Pretend we asked for these
3616                            moves and soak them up so user can step
3617                            through them and/or save them.
3618                            */
3619                         Reset(FALSE, TRUE);
3620                         gameMode = IcsObserving;
3621                         ModeHighlight();
3622                         ics_gamenum = -1;
3623                         ics_getting_history = H_GOT_UNREQ_HEADER;
3624                         break;
3625                       case EditGame: /*?*/
3626                       case EditPosition: /*?*/
3627                         /* Should above feature work in these modes too? */
3628                         /* For now it doesn't */
3629                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3630                         break;
3631                       default:
3632                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3633                         break;
3634                     }
3635                     break;
3636                   case H_REQUESTED:
3637                     /* Is this the right one? */
3638                     if (gameInfo.white && gameInfo.black &&
3639                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3640                         strcmp(gameInfo.black, star_match[2]) == 0) {
3641                         /* All is well */
3642                         ics_getting_history = H_GOT_REQ_HEADER;
3643                     }
3644                     break;
3645                   case H_GOT_REQ_HEADER:
3646                   case H_GOT_UNREQ_HEADER:
3647                   case H_GOT_UNWANTED_HEADER:
3648                   case H_GETTING_MOVES:
3649                     /* Should not happen */
3650                     DisplayError(_("Error gathering move list: two headers"), 0);
3651                     ics_getting_history = H_FALSE;
3652                     break;
3653                 }
3654
3655                 /* Save player ratings into gameInfo if needed */
3656                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3657                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3658                     (gameInfo.whiteRating == -1 ||
3659                      gameInfo.blackRating == -1)) {
3660
3661                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3662                     gameInfo.blackRating = string_to_rating(star_match[3]);
3663                     if (appData.debugMode)
3664                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3665                               gameInfo.whiteRating, gameInfo.blackRating);
3666                 }
3667                 continue;
3668             }
3669
3670             if (looking_at(buf, &i,
3671               "* * match, initial time: * minute*, increment: * second")) {
3672                 /* Header for a move list -- second line */
3673                 /* Initial board will follow if this is a wild game */
3674                 if (gameInfo.event != NULL) free(gameInfo.event);
3675                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3676                 gameInfo.event = StrSave(str);
3677                 /* [HGM] we switched variant. Translate boards if needed. */
3678                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3679                 continue;
3680             }
3681
3682             if (looking_at(buf, &i, "Move  ")) {
3683                 /* Beginning of a move list */
3684                 switch (ics_getting_history) {
3685                   case H_FALSE:
3686                     /* Normally should not happen */
3687                     /* Maybe user hit reset while we were parsing */
3688                     break;
3689                   case H_REQUESTED:
3690                     /* Happens if we are ignoring a move list that is not
3691                      * the one we just requested.  Common if the user
3692                      * tries to observe two games without turning off
3693                      * getMoveList */
3694                     break;
3695                   case H_GETTING_MOVES:
3696                     /* Should not happen */
3697                     DisplayError(_("Error gathering move list: nested"), 0);
3698                     ics_getting_history = H_FALSE;
3699                     break;
3700                   case H_GOT_REQ_HEADER:
3701                     ics_getting_history = H_GETTING_MOVES;
3702                     started = STARTED_MOVES;
3703                     parse_pos = 0;
3704                     if (oldi > next_out) {
3705                         SendToPlayer(&buf[next_out], oldi - next_out);
3706                     }
3707                     break;
3708                   case H_GOT_UNREQ_HEADER:
3709                     ics_getting_history = H_GETTING_MOVES;
3710                     started = STARTED_MOVES_NOHIDE;
3711                     parse_pos = 0;
3712                     break;
3713                   case H_GOT_UNWANTED_HEADER:
3714                     ics_getting_history = H_FALSE;
3715                     break;
3716                 }
3717                 continue;
3718             }
3719
3720             if (looking_at(buf, &i, "% ") ||
3721                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3722                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3723                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3724                     soughtPending = FALSE;
3725                     seekGraphUp = TRUE;
3726                     DrawSeekGraph();
3727                 }
3728                 if(suppressKibitz) next_out = i;
3729                 savingComment = FALSE;
3730                 suppressKibitz = 0;
3731                 switch (started) {
3732                   case STARTED_MOVES:
3733                   case STARTED_MOVES_NOHIDE:
3734                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3735                     parse[parse_pos + i - oldi] = NULLCHAR;
3736                     ParseGameHistory(parse);
3737 #if ZIPPY
3738                     if (appData.zippyPlay && first.initDone) {
3739                         FeedMovesToProgram(&first, forwardMostMove);
3740                         if (gameMode == IcsPlayingWhite) {
3741                             if (WhiteOnMove(forwardMostMove)) {
3742                                 if (first.sendTime) {
3743                                   if (first.useColors) {
3744                                     SendToProgram("black\n", &first);
3745                                   }
3746                                   SendTimeRemaining(&first, TRUE);
3747                                 }
3748                                 if (first.useColors) {
3749                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3750                                 }
3751                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3752                                 first.maybeThinking = TRUE;
3753                             } else {
3754                                 if (first.usePlayother) {
3755                                   if (first.sendTime) {
3756                                     SendTimeRemaining(&first, TRUE);
3757                                   }
3758                                   SendToProgram("playother\n", &first);
3759                                   firstMove = FALSE;
3760                                 } else {
3761                                   firstMove = TRUE;
3762                                 }
3763                             }
3764                         } else if (gameMode == IcsPlayingBlack) {
3765                             if (!WhiteOnMove(forwardMostMove)) {
3766                                 if (first.sendTime) {
3767                                   if (first.useColors) {
3768                                     SendToProgram("white\n", &first);
3769                                   }
3770                                   SendTimeRemaining(&first, FALSE);
3771                                 }
3772                                 if (first.useColors) {
3773                                   SendToProgram("black\n", &first);
3774                                 }
3775                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3776                                 first.maybeThinking = TRUE;
3777                             } else {
3778                                 if (first.usePlayother) {
3779                                   if (first.sendTime) {
3780                                     SendTimeRemaining(&first, FALSE);
3781                                   }
3782                                   SendToProgram("playother\n", &first);
3783                                   firstMove = FALSE;
3784                                 } else {
3785                                   firstMove = TRUE;
3786                                 }
3787                             }
3788                         }
3789                     }
3790 #endif
3791                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3792                         /* Moves came from oldmoves or moves command
3793                            while we weren't doing anything else.
3794                            */
3795                         currentMove = forwardMostMove;
3796                         ClearHighlights();/*!!could figure this out*/
3797                         flipView = appData.flipView;
3798                         DrawPosition(TRUE, boards[currentMove]);
3799                         DisplayBothClocks();
3800                         snprintf(str, MSG_SIZ, "%s %s %s",
3801                                 gameInfo.white, _("vs."),  gameInfo.black);
3802                         DisplayTitle(str);
3803                         gameMode = IcsIdle;
3804                     } else {
3805                         /* Moves were history of an active game */
3806                         if (gameInfo.resultDetails != NULL) {
3807                             free(gameInfo.resultDetails);
3808                             gameInfo.resultDetails = NULL;
3809                         }
3810                     }
3811                     HistorySet(parseList, backwardMostMove,
3812                                forwardMostMove, currentMove-1);
3813                     DisplayMove(currentMove - 1);
3814                     if (started == STARTED_MOVES) next_out = i;
3815                     started = STARTED_NONE;
3816                     ics_getting_history = H_FALSE;
3817                     break;
3818
3819                   case STARTED_OBSERVE:
3820                     started = STARTED_NONE;
3821                     SendToICS(ics_prefix);
3822                     SendToICS("refresh\n");
3823                     break;
3824
3825                   default:
3826                     break;
3827                 }
3828                 if(bookHit) { // [HGM] book: simulate book reply
3829                     static char bookMove[MSG_SIZ]; // a bit generous?
3830
3831                     programStats.nodes = programStats.depth = programStats.time =
3832                     programStats.score = programStats.got_only_move = 0;
3833                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3834
3835                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3836                     strcat(bookMove, bookHit);
3837                     HandleMachineMove(bookMove, &first);
3838                 }
3839                 continue;
3840             }
3841
3842             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3843                  started == STARTED_HOLDINGS ||
3844                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3845                 /* Accumulate characters in move list or board */
3846                 parse[parse_pos++] = buf[i];
3847             }
3848
3849             /* Start of game messages.  Mostly we detect start of game
3850                when the first board image arrives.  On some versions
3851                of the ICS, though, we need to do a "refresh" after starting
3852                to observe in order to get the current board right away. */
3853             if (looking_at(buf, &i, "Adding game * to observation list")) {
3854                 started = STARTED_OBSERVE;
3855                 continue;
3856             }
3857
3858             /* Handle auto-observe */
3859             if (appData.autoObserve &&
3860                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3861                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3862                 char *player;
3863                 /* Choose the player that was highlighted, if any. */
3864                 if (star_match[0][0] == '\033' ||
3865                     star_match[1][0] != '\033') {
3866                     player = star_match[0];
3867                 } else {
3868                     player = star_match[2];
3869                 }
3870                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3871                         ics_prefix, StripHighlightAndTitle(player));
3872                 SendToICS(str);
3873
3874                 /* Save ratings from notify string */
3875                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3876                 player1Rating = string_to_rating(star_match[1]);
3877                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3878                 player2Rating = string_to_rating(star_match[3]);
3879
3880                 if (appData.debugMode)
3881                   fprintf(debugFP,
3882                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3883                           player1Name, player1Rating,
3884                           player2Name, player2Rating);
3885
3886                 continue;
3887             }
3888
3889             /* Deal with automatic examine mode after a game,
3890                and with IcsObserving -> IcsExamining transition */
3891             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3892                 looking_at(buf, &i, "has made you an examiner of game *")) {
3893
3894                 int gamenum = atoi(star_match[0]);
3895                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3896                     gamenum == ics_gamenum) {
3897                     /* We were already playing or observing this game;
3898                        no need to refetch history */
3899                     gameMode = IcsExamining;
3900                     if (pausing) {
3901                         pauseExamForwardMostMove = forwardMostMove;
3902                     } else if (currentMove < forwardMostMove) {
3903                         ForwardInner(forwardMostMove);
3904                     }
3905                 } else {
3906                     /* I don't think this case really can happen */
3907                     SendToICS(ics_prefix);
3908                     SendToICS("refresh\n");
3909                 }
3910                 continue;
3911             }
3912
3913             /* Error messages */
3914 //          if (ics_user_moved) {
3915             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3916                 if (looking_at(buf, &i, "Illegal move") ||
3917                     looking_at(buf, &i, "Not a legal move") ||
3918                     looking_at(buf, &i, "Your king is in check") ||
3919                     looking_at(buf, &i, "It isn't your turn") ||
3920                     looking_at(buf, &i, "It is not your move")) {
3921                     /* Illegal move */
3922                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3923                         currentMove = forwardMostMove-1;
3924                         DisplayMove(currentMove - 1); /* before DMError */
3925                         DrawPosition(FALSE, boards[currentMove]);
3926                         SwitchClocks(forwardMostMove-1); // [HGM] race
3927                         DisplayBothClocks();
3928                     }
3929                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3930                     ics_user_moved = 0;
3931                     continue;
3932                 }
3933             }
3934
3935             if (looking_at(buf, &i, "still have time") ||
3936                 looking_at(buf, &i, "not out of time") ||
3937                 looking_at(buf, &i, "either player is out of time") ||
3938                 looking_at(buf, &i, "has timeseal; checking")) {
3939                 /* We must have called his flag a little too soon */
3940                 whiteFlag = blackFlag = FALSE;
3941                 continue;
3942             }
3943
3944             if (looking_at(buf, &i, "added * seconds to") ||
3945                 looking_at(buf, &i, "seconds were added to")) {
3946                 /* Update the clocks */
3947                 SendToICS(ics_prefix);
3948                 SendToICS("refresh\n");
3949                 continue;
3950             }
3951
3952             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3953                 ics_clock_paused = TRUE;
3954                 StopClocks();
3955                 continue;
3956             }
3957
3958             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3959                 ics_clock_paused = FALSE;
3960                 StartClocks();
3961                 continue;
3962             }
3963
3964             /* Grab player ratings from the Creating: message.
3965                Note we have to check for the special case when
3966                the ICS inserts things like [white] or [black]. */
3967             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3968                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3969                 /* star_matches:
3970                    0    player 1 name (not necessarily white)
3971                    1    player 1 rating
3972                    2    empty, white, or black (IGNORED)
3973                    3    player 2 name (not necessarily black)
3974                    4    player 2 rating
3975
3976                    The names/ratings are sorted out when the game
3977                    actually starts (below).
3978                 */
3979                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3980                 player1Rating = string_to_rating(star_match[1]);
3981                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3982                 player2Rating = string_to_rating(star_match[4]);
3983
3984                 if (appData.debugMode)
3985                   fprintf(debugFP,
3986                           "Ratings from 'Creating:' %s %d, %s %d\n",
3987                           player1Name, player1Rating,
3988                           player2Name, player2Rating);
3989
3990                 continue;
3991             }
3992
3993             /* Improved generic start/end-of-game messages */
3994             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3995                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3996                 /* If tkind == 0: */
3997                 /* star_match[0] is the game number */
3998                 /*           [1] is the white player's name */
3999                 /*           [2] is the black player's name */
4000                 /* For end-of-game: */
4001                 /*           [3] is the reason for the game end */
4002                 /*           [4] is a PGN end game-token, preceded by " " */
4003                 /* For start-of-game: */
4004                 /*           [3] begins with "Creating" or "Continuing" */
4005                 /*           [4] is " *" or empty (don't care). */
4006                 int gamenum = atoi(star_match[0]);
4007                 char *whitename, *blackname, *why, *endtoken;
4008                 ChessMove endtype = EndOfFile;
4009
4010                 if (tkind == 0) {
4011                   whitename = star_match[1];
4012                   blackname = star_match[2];
4013                   why = star_match[3];
4014                   endtoken = star_match[4];
4015                 } else {
4016                   whitename = star_match[1];
4017                   blackname = star_match[3];
4018                   why = star_match[5];
4019                   endtoken = star_match[6];
4020                 }
4021
4022                 /* Game start messages */
4023                 if (strncmp(why, "Creating ", 9) == 0 ||
4024                     strncmp(why, "Continuing ", 11) == 0) {
4025                     gs_gamenum = gamenum;
4026                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4027                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4028                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4029 #if ZIPPY
4030                     if (appData.zippyPlay) {
4031                         ZippyGameStart(whitename, blackname);
4032                     }
4033 #endif /*ZIPPY*/
4034                     partnerBoardValid = FALSE; // [HGM] bughouse
4035                     continue;
4036                 }
4037
4038                 /* Game end messages */
4039                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4040                     ics_gamenum != gamenum) {
4041                     continue;
4042                 }
4043                 while (endtoken[0] == ' ') endtoken++;
4044                 switch (endtoken[0]) {
4045                   case '*':
4046                   default:
4047                     endtype = GameUnfinished;
4048                     break;
4049                   case '0':
4050                     endtype = BlackWins;
4051                     break;
4052                   case '1':
4053                     if (endtoken[1] == '/')
4054                       endtype = GameIsDrawn;
4055                     else
4056                       endtype = WhiteWins;
4057                     break;
4058                 }
4059                 GameEnds(endtype, why, GE_ICS);
4060 #if ZIPPY
4061                 if (appData.zippyPlay && first.initDone) {
4062                     ZippyGameEnd(endtype, why);
4063                     if (first.pr == NoProc) {
4064                       /* Start the next process early so that we'll
4065                          be ready for the next challenge */
4066                       StartChessProgram(&first);
4067                     }
4068                     /* Send "new" early, in case this command takes
4069                        a long time to finish, so that we'll be ready
4070                        for the next challenge. */
4071                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4072                     Reset(TRUE, TRUE);
4073                 }
4074 #endif /*ZIPPY*/
4075                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4076                 continue;
4077             }
4078
4079             if (looking_at(buf, &i, "Removing game * from observation") ||
4080                 looking_at(buf, &i, "no longer observing game *") ||
4081                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4082                 if (gameMode == IcsObserving &&
4083                     atoi(star_match[0]) == ics_gamenum)
4084                   {
4085                       /* icsEngineAnalyze */
4086                       if (appData.icsEngineAnalyze) {
4087                             ExitAnalyzeMode();
4088                             ModeHighlight();
4089                       }
4090                       StopClocks();
4091                       gameMode = IcsIdle;
4092                       ics_gamenum = -1;
4093                       ics_user_moved = FALSE;
4094                   }
4095                 continue;
4096             }
4097
4098             if (looking_at(buf, &i, "no longer examining game *")) {
4099                 if (gameMode == IcsExamining &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       gameMode = IcsIdle;
4103                       ics_gamenum = -1;
4104                       ics_user_moved = FALSE;
4105                   }
4106                 continue;
4107             }
4108
4109             /* Advance leftover_start past any newlines we find,
4110                so only partial lines can get reparsed */
4111             if (looking_at(buf, &i, "\n")) {
4112                 prevColor = curColor;
4113                 if (curColor != ColorNormal) {
4114                     if (oldi > next_out) {
4115                         SendToPlayer(&buf[next_out], oldi - next_out);
4116                         next_out = oldi;
4117                     }
4118                     Colorize(ColorNormal, FALSE);
4119                     curColor = ColorNormal;
4120                 }
4121                 if (started == STARTED_BOARD) {
4122                     started = STARTED_NONE;
4123                     parse[parse_pos] = NULLCHAR;
4124                     ParseBoard12(parse);
4125                     ics_user_moved = 0;
4126
4127                     /* Send premove here */
4128                     if (appData.premove) {
4129                       char str[MSG_SIZ];
4130                       if (currentMove == 0 &&
4131                           gameMode == IcsPlayingWhite &&
4132                           appData.premoveWhite) {
4133                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4134                         if (appData.debugMode)
4135                           fprintf(debugFP, "Sending premove:\n");
4136                         SendToICS(str);
4137                       } else if (currentMove == 1 &&
4138                                  gameMode == IcsPlayingBlack &&
4139                                  appData.premoveBlack) {
4140                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4141                         if (appData.debugMode)
4142                           fprintf(debugFP, "Sending premove:\n");
4143                         SendToICS(str);
4144                       } else if (gotPremove) {
4145                         gotPremove = 0;
4146                         ClearPremoveHighlights();
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                           UserMoveEvent(premoveFromX, premoveFromY,
4150                                         premoveToX, premoveToY,
4151                                         premovePromoChar);
4152                       }
4153                     }
4154
4155                     /* Usually suppress following prompt */
4156                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4157                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4158                         if (looking_at(buf, &i, "*% ")) {
4159                             savingComment = FALSE;
4160                             suppressKibitz = 0;
4161                         }
4162                     }
4163                     next_out = i;
4164                 } else if (started == STARTED_HOLDINGS) {
4165                     int gamenum;
4166                     char new_piece[MSG_SIZ];
4167                     started = STARTED_NONE;
4168                     parse[parse_pos] = NULLCHAR;
4169                     if (appData.debugMode)
4170                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4171                                                         parse, currentMove);
4172                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4173                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4174                         if (gameInfo.variant == VariantNormal) {
4175                           /* [HGM] We seem to switch variant during a game!
4176                            * Presumably no holdings were displayed, so we have
4177                            * to move the position two files to the right to
4178                            * create room for them!
4179                            */
4180                           VariantClass newVariant;
4181                           switch(gameInfo.boardWidth) { // base guess on board width
4182                                 case 9:  newVariant = VariantShogi; break;
4183                                 case 10: newVariant = VariantGreat; break;
4184                                 default: newVariant = VariantCrazyhouse; break;
4185                           }
4186                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4187                           /* Get a move list just to see the header, which
4188                              will tell us whether this is really bug or zh */
4189                           if (ics_getting_history == H_FALSE) {
4190                             ics_getting_history = H_REQUESTED;
4191                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4192                             SendToICS(str);
4193                           }
4194                         }
4195                         new_piece[0] = NULLCHAR;
4196                         sscanf(parse, "game %d white [%s black [%s <- %s",
4197                                &gamenum, white_holding, black_holding,
4198                                new_piece);
4199                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4200                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4201                         /* [HGM] copy holdings to board holdings area */
4202                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4203                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4204                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4205 #if ZIPPY
4206                         if (appData.zippyPlay && first.initDone) {
4207                             ZippyHoldings(white_holding, black_holding,
4208                                           new_piece);
4209                         }
4210 #endif /*ZIPPY*/
4211                         if (tinyLayout || smallLayout) {
4212                             char wh[16], bh[16];
4213                             PackHolding(wh, white_holding);
4214                             PackHolding(bh, black_holding);
4215                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4216                                     gameInfo.white, gameInfo.black);
4217                         } else {
4218                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4219                                     gameInfo.white, white_holding, _("vs."),
4220                                     gameInfo.black, black_holding);
4221                         }
4222                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4223                         DrawPosition(FALSE, boards[currentMove]);
4224                         DisplayTitle(str);
4225                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4226                         sscanf(parse, "game %d white [%s black [%s <- %s",
4227                                &gamenum, white_holding, black_holding,
4228                                new_piece);
4229                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4230                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4231                         /* [HGM] copy holdings to partner-board holdings area */
4232                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4233                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4234                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4235                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4236                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4237                       }
4238                     }
4239                     /* Suppress following prompt */
4240                     if (looking_at(buf, &i, "*% ")) {
4241                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4242                         savingComment = FALSE;
4243                         suppressKibitz = 0;
4244                     }
4245                     next_out = i;
4246                 }
4247                 continue;
4248             }
4249
4250             i++;                /* skip unparsed character and loop back */
4251         }
4252
4253         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4254 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4255 //          SendToPlayer(&buf[next_out], i - next_out);
4256             started != STARTED_HOLDINGS && leftover_start > next_out) {
4257             SendToPlayer(&buf[next_out], leftover_start - next_out);
4258             next_out = i;
4259         }
4260
4261         leftover_len = buf_len - leftover_start;
4262         /* if buffer ends with something we couldn't parse,
4263            reparse it after appending the next read */
4264
4265     } else if (count == 0) {
4266         RemoveInputSource(isr);
4267         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4268     } else {
4269         DisplayFatalError(_("Error reading from ICS"), error, 1);
4270     }
4271 }
4272
4273
4274 /* Board style 12 looks like this:
4275
4276    <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
4277
4278  * The "<12> " is stripped before it gets to this routine.  The two
4279  * trailing 0's (flip state and clock ticking) are later addition, and
4280  * some chess servers may not have them, or may have only the first.
4281  * Additional trailing fields may be added in the future.
4282  */
4283
4284 #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"
4285
4286 #define RELATION_OBSERVING_PLAYED    0
4287 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4288 #define RELATION_PLAYING_MYMOVE      1
4289 #define RELATION_PLAYING_NOTMYMOVE  -1
4290 #define RELATION_EXAMINING           2
4291 #define RELATION_ISOLATED_BOARD     -3
4292 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4293
4294 void
4295 ParseBoard12 (char *string)
4296 {
4297 #if ZIPPY
4298     int i, takeback;
4299     char *bookHit = NULL; // [HGM] book
4300 #endif
4301     GameMode newGameMode;
4302     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4303     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4304     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4305     char to_play, board_chars[200];
4306     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4307     char black[32], white[32];
4308     Board board;
4309     int prevMove = currentMove;
4310     int ticking = 2;
4311     ChessMove moveType;
4312     int fromX, fromY, toX, toY;
4313     char promoChar;
4314     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4315     Boolean weird = FALSE, reqFlag = FALSE;
4316
4317     fromX = fromY = toX = toY = -1;
4318
4319     newGame = FALSE;
4320
4321     if (appData.debugMode)
4322       fprintf(debugFP, "Parsing board: %s\n", string);
4323
4324     move_str[0] = NULLCHAR;
4325     elapsed_time[0] = NULLCHAR;
4326     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4327         int  i = 0, j;
4328         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4329             if(string[i] == ' ') { ranks++; files = 0; }
4330             else files++;
4331             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4332             i++;
4333         }
4334         for(j = 0; j <i; j++) board_chars[j] = string[j];
4335         board_chars[i] = '\0';
4336         string += i + 1;
4337     }
4338     n = sscanf(string, PATTERN, &to_play, &double_push,
4339                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4340                &gamenum, white, black, &relation, &basetime, &increment,
4341                &white_stren, &black_stren, &white_time, &black_time,
4342                &moveNum, str, elapsed_time, move_str, &ics_flip,
4343                &ticking);
4344
4345     if (n < 21) {
4346         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4347         DisplayError(str, 0);
4348         return;
4349     }
4350
4351     /* Convert the move number to internal form */
4352     moveNum = (moveNum - 1) * 2;
4353     if (to_play == 'B') moveNum++;
4354     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4355       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4356                         0, 1);
4357       return;
4358     }
4359
4360     switch (relation) {
4361       case RELATION_OBSERVING_PLAYED:
4362       case RELATION_OBSERVING_STATIC:
4363         if (gamenum == -1) {
4364             /* Old ICC buglet */
4365             relation = RELATION_OBSERVING_STATIC;
4366         }
4367         newGameMode = IcsObserving;
4368         break;
4369       case RELATION_PLAYING_MYMOVE:
4370       case RELATION_PLAYING_NOTMYMOVE:
4371         newGameMode =
4372           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4373             IcsPlayingWhite : IcsPlayingBlack;
4374         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4375         break;
4376       case RELATION_EXAMINING:
4377         newGameMode = IcsExamining;
4378         break;
4379       case RELATION_ISOLATED_BOARD:
4380       default:
4381         /* Just display this board.  If user was doing something else,
4382            we will forget about it until the next board comes. */
4383         newGameMode = IcsIdle;
4384         break;
4385       case RELATION_STARTING_POSITION:
4386         newGameMode = gameMode;
4387         break;
4388     }
4389
4390     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4391         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4392          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4393       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4394       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4395       static int lastBgGame = -1;
4396       char *toSqr;
4397       for (k = 0; k < ranks; k++) {
4398         for (j = 0; j < files; j++)
4399           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4400         if(gameInfo.holdingsWidth > 1) {
4401              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4402              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4403         }
4404       }
4405       CopyBoard(partnerBoard, board);
4406       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4407         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4408         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4409       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4410       if(toSqr = strchr(str, '-')) {
4411         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4412         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4413       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4414       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4415       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4416       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4417       if(twoBoards) {
4418           DisplayWhiteClock(white_time*fac, to_play == 'W');
4419           DisplayBlackClock(black_time*fac, to_play != 'W');
4420           activePartner = to_play;
4421           if(gamenum != lastBgGame) {
4422               char buf[MSG_SIZ];
4423               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4424               DisplayTitle(buf);
4425           }
4426           lastBgGame = gamenum;
4427           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4428                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4429       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4430                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4431       if(!twoBoards) DisplayMessage(partnerStatus, "");
4432         partnerBoardValid = TRUE;
4433       return;
4434     }
4435
4436     if(appData.dualBoard && appData.bgObserve) {
4437         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4438             SendToICS(ics_prefix), SendToICS("pobserve\n");
4439         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4440             char buf[MSG_SIZ];
4441             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4442             SendToICS(buf);
4443         }
4444     }
4445
4446     /* Modify behavior for initial board display on move listing
4447        of wild games.
4448        */
4449     switch (ics_getting_history) {
4450       case H_FALSE:
4451       case H_REQUESTED:
4452         break;
4453       case H_GOT_REQ_HEADER:
4454       case H_GOT_UNREQ_HEADER:
4455         /* This is the initial position of the current game */
4456         gamenum = ics_gamenum;
4457         moveNum = 0;            /* old ICS bug workaround */
4458         if (to_play == 'B') {
4459           startedFromSetupPosition = TRUE;
4460           blackPlaysFirst = TRUE;
4461           moveNum = 1;
4462           if (forwardMostMove == 0) forwardMostMove = 1;
4463           if (backwardMostMove == 0) backwardMostMove = 1;
4464           if (currentMove == 0) currentMove = 1;
4465         }
4466         newGameMode = gameMode;
4467         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4468         break;
4469       case H_GOT_UNWANTED_HEADER:
4470         /* This is an initial board that we don't want */
4471         return;
4472       case H_GETTING_MOVES:
4473         /* Should not happen */
4474         DisplayError(_("Error gathering move list: extra board"), 0);
4475         ics_getting_history = H_FALSE;
4476         return;
4477     }
4478
4479    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4480                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4481                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4482      /* [HGM] We seem to have switched variant unexpectedly
4483       * Try to guess new variant from board size
4484       */
4485           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4486           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4487           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4488           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4489           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4490           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4491           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4492           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4493           /* Get a move list just to see the header, which
4494              will tell us whether this is really bug or zh */
4495           if (ics_getting_history == H_FALSE) {
4496             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4497             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4498             SendToICS(str);
4499           }
4500     }
4501
4502     /* Take action if this is the first board of a new game, or of a
4503        different game than is currently being displayed.  */
4504     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4505         relation == RELATION_ISOLATED_BOARD) {
4506
4507         /* Forget the old game and get the history (if any) of the new one */
4508         if (gameMode != BeginningOfGame) {
4509           Reset(TRUE, TRUE);
4510         }
4511         newGame = TRUE;
4512         if (appData.autoRaiseBoard) BoardToTop();
4513         prevMove = -3;
4514         if (gamenum == -1) {
4515             newGameMode = IcsIdle;
4516         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4517                    appData.getMoveList && !reqFlag) {
4518             /* Need to get game history */
4519             ics_getting_history = H_REQUESTED;
4520             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4521             SendToICS(str);
4522         }
4523
4524         /* Initially flip the board to have black on the bottom if playing
4525            black or if the ICS flip flag is set, but let the user change
4526            it with the Flip View button. */
4527         flipView = appData.autoFlipView ?
4528           (newGameMode == IcsPlayingBlack) || ics_flip :
4529           appData.flipView;
4530
4531         /* Done with values from previous mode; copy in new ones */
4532         gameMode = newGameMode;
4533         ModeHighlight();
4534         ics_gamenum = gamenum;
4535         if (gamenum == gs_gamenum) {
4536             int klen = strlen(gs_kind);
4537             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4538             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4539             gameInfo.event = StrSave(str);
4540         } else {
4541             gameInfo.event = StrSave("ICS game");
4542         }
4543         gameInfo.site = StrSave(appData.icsHost);
4544         gameInfo.date = PGNDate();
4545         gameInfo.round = StrSave("-");
4546         gameInfo.white = StrSave(white);
4547         gameInfo.black = StrSave(black);
4548         timeControl = basetime * 60 * 1000;
4549         timeControl_2 = 0;
4550         timeIncrement = increment * 1000;
4551         movesPerSession = 0;
4552         gameInfo.timeControl = TimeControlTagValue();
4553         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4554   if (appData.debugMode) {
4555     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4556     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4557     setbuf(debugFP, NULL);
4558   }
4559
4560         gameInfo.outOfBook = NULL;
4561
4562         /* Do we have the ratings? */
4563         if (strcmp(player1Name, white) == 0 &&
4564             strcmp(player2Name, black) == 0) {
4565             if (appData.debugMode)
4566               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4567                       player1Rating, player2Rating);
4568             gameInfo.whiteRating = player1Rating;
4569             gameInfo.blackRating = player2Rating;
4570         } else if (strcmp(player2Name, white) == 0 &&
4571                    strcmp(player1Name, black) == 0) {
4572             if (appData.debugMode)
4573               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4574                       player2Rating, player1Rating);
4575             gameInfo.whiteRating = player2Rating;
4576             gameInfo.blackRating = player1Rating;
4577         }
4578         player1Name[0] = player2Name[0] = NULLCHAR;
4579
4580         /* Silence shouts if requested */
4581         if (appData.quietPlay &&
4582             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4583             SendToICS(ics_prefix);
4584             SendToICS("set shout 0\n");
4585         }
4586     }
4587
4588     /* Deal with midgame name changes */
4589     if (!newGame) {
4590         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4591             if (gameInfo.white) free(gameInfo.white);
4592             gameInfo.white = StrSave(white);
4593         }
4594         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4595             if (gameInfo.black) free(gameInfo.black);
4596             gameInfo.black = StrSave(black);
4597         }
4598     }
4599
4600     /* Throw away game result if anything actually changes in examine mode */
4601     if (gameMode == IcsExamining && !newGame) {
4602         gameInfo.result = GameUnfinished;
4603         if (gameInfo.resultDetails != NULL) {
4604             free(gameInfo.resultDetails);
4605             gameInfo.resultDetails = NULL;
4606         }
4607     }
4608
4609     /* In pausing && IcsExamining mode, we ignore boards coming
4610        in if they are in a different variation than we are. */
4611     if (pauseExamInvalid) return;
4612     if (pausing && gameMode == IcsExamining) {
4613         if (moveNum <= pauseExamForwardMostMove) {
4614             pauseExamInvalid = TRUE;
4615             forwardMostMove = pauseExamForwardMostMove;
4616             return;
4617         }
4618     }
4619
4620   if (appData.debugMode) {
4621     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4622   }
4623     /* Parse the board */
4624     for (k = 0; k < ranks; k++) {
4625       for (j = 0; j < files; j++)
4626         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4627       if(gameInfo.holdingsWidth > 1) {
4628            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4629            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4630       }
4631     }
4632     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4633       board[5][BOARD_RGHT+1] = WhiteAngel;
4634       board[6][BOARD_RGHT+1] = WhiteMarshall;
4635       board[1][0] = BlackMarshall;
4636       board[2][0] = BlackAngel;
4637       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4638     }
4639     CopyBoard(boards[moveNum], board);
4640     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4641     if (moveNum == 0) {
4642         startedFromSetupPosition =
4643           !CompareBoards(board, initialPosition);
4644         if(startedFromSetupPosition)
4645             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4646     }
4647
4648     /* [HGM] Set castling rights. Take the outermost Rooks,
4649        to make it also work for FRC opening positions. Note that board12
4650        is really defective for later FRC positions, as it has no way to
4651        indicate which Rook can castle if they are on the same side of King.
4652        For the initial position we grant rights to the outermost Rooks,
4653        and remember thos rights, and we then copy them on positions
4654        later in an FRC game. This means WB might not recognize castlings with
4655        Rooks that have moved back to their original position as illegal,
4656        but in ICS mode that is not its job anyway.
4657     */
4658     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4659     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4660
4661         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4662             if(board[0][i] == WhiteRook) j = i;
4663         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4664         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4665             if(board[0][i] == WhiteRook) j = i;
4666         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4667         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4669         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4672         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4673
4674         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4675         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4676         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4677             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4678         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4679             if(board[BOARD_HEIGHT-1][k] == bKing)
4680                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4681         if(gameInfo.variant == VariantTwoKings) {
4682             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4683             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4684             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4685         }
4686     } else { int r;
4687         r = boards[moveNum][CASTLING][0] = initialRights[0];
4688         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4689         r = boards[moveNum][CASTLING][1] = initialRights[1];
4690         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4691         r = boards[moveNum][CASTLING][3] = initialRights[3];
4692         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4693         r = boards[moveNum][CASTLING][4] = initialRights[4];
4694         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4695         /* wildcastle kludge: always assume King has rights */
4696         r = boards[moveNum][CASTLING][2] = initialRights[2];
4697         r = boards[moveNum][CASTLING][5] = initialRights[5];
4698     }
4699     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4700     boards[moveNum][EP_STATUS] = EP_NONE;
4701     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4702     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4703     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4704
4705
4706     if (ics_getting_history == H_GOT_REQ_HEADER ||
4707         ics_getting_history == H_GOT_UNREQ_HEADER) {
4708         /* This was an initial position from a move list, not
4709            the current position */
4710         return;
4711     }
4712
4713     /* Update currentMove and known move number limits */
4714     newMove = newGame || moveNum > forwardMostMove;
4715
4716     if (newGame) {
4717         forwardMostMove = backwardMostMove = currentMove = moveNum;
4718         if (gameMode == IcsExamining && moveNum == 0) {
4719           /* Workaround for ICS limitation: we are not told the wild
4720              type when starting to examine a game.  But if we ask for
4721              the move list, the move list header will tell us */
4722             ics_getting_history = H_REQUESTED;
4723             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4724             SendToICS(str);
4725         }
4726     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4727                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4728 #if ZIPPY
4729         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4730         /* [HGM] applied this also to an engine that is silently watching        */
4731         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4732             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4733             gameInfo.variant == currentlyInitializedVariant) {
4734           takeback = forwardMostMove - moveNum;
4735           for (i = 0; i < takeback; i++) {
4736             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4737             SendToProgram("undo\n", &first);
4738           }
4739         }
4740 #endif
4741
4742         forwardMostMove = moveNum;
4743         if (!pausing || currentMove > forwardMostMove)
4744           currentMove = forwardMostMove;
4745     } else {
4746         /* New part of history that is not contiguous with old part */
4747         if (pausing && gameMode == IcsExamining) {
4748             pauseExamInvalid = TRUE;
4749             forwardMostMove = pauseExamForwardMostMove;
4750             return;
4751         }
4752         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4753 #if ZIPPY
4754             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4755                 // [HGM] when we will receive the move list we now request, it will be
4756                 // fed to the engine from the first move on. So if the engine is not
4757                 // in the initial position now, bring it there.
4758                 InitChessProgram(&first, 0);
4759             }
4760 #endif
4761             ics_getting_history = H_REQUESTED;
4762             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4763             SendToICS(str);
4764         }
4765         forwardMostMove = backwardMostMove = currentMove = moveNum;
4766     }
4767
4768     /* Update the clocks */
4769     if (strchr(elapsed_time, '.')) {
4770       /* Time is in ms */
4771       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4772       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4773     } else {
4774       /* Time is in seconds */
4775       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4776       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4777     }
4778
4779
4780 #if ZIPPY
4781     if (appData.zippyPlay && newGame &&
4782         gameMode != IcsObserving && gameMode != IcsIdle &&
4783         gameMode != IcsExamining)
4784       ZippyFirstBoard(moveNum, basetime, increment);
4785 #endif
4786
4787     /* Put the move on the move list, first converting
4788        to canonical algebraic form. */
4789     if (moveNum > 0) {
4790   if (appData.debugMode) {
4791     int f = forwardMostMove;
4792     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4793             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4794             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4795     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4796     fprintf(debugFP, "moveNum = %d\n", moveNum);
4797     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4798     setbuf(debugFP, NULL);
4799   }
4800         if (moveNum <= backwardMostMove) {
4801             /* We don't know what the board looked like before
4802                this move.  Punt. */
4803           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4804             strcat(parseList[moveNum - 1], " ");
4805             strcat(parseList[moveNum - 1], elapsed_time);
4806             moveList[moveNum - 1][0] = NULLCHAR;
4807         } else if (strcmp(move_str, "none") == 0) {
4808             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4809             /* Again, we don't know what the board looked like;
4810                this is really the start of the game. */
4811             parseList[moveNum - 1][0] = NULLCHAR;
4812             moveList[moveNum - 1][0] = NULLCHAR;
4813             backwardMostMove = moveNum;
4814             startedFromSetupPosition = TRUE;
4815             fromX = fromY = toX = toY = -1;
4816         } else {
4817           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4818           //                 So we parse the long-algebraic move string in stead of the SAN move
4819           int valid; char buf[MSG_SIZ], *prom;
4820
4821           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4822                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4823           // str looks something like "Q/a1-a2"; kill the slash
4824           if(str[1] == '/')
4825             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4826           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4827           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4828                 strcat(buf, prom); // long move lacks promo specification!
4829           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4830                 if(appData.debugMode)
4831                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4832                 safeStrCpy(move_str, buf, MSG_SIZ);
4833           }
4834           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4835                                 &fromX, &fromY, &toX, &toY, &promoChar)
4836                || ParseOneMove(buf, moveNum - 1, &moveType,
4837                                 &fromX, &fromY, &toX, &toY, &promoChar);
4838           // end of long SAN patch
4839           if (valid) {
4840             (void) CoordsToAlgebraic(boards[moveNum - 1],
4841                                      PosFlags(moveNum - 1),
4842                                      fromY, fromX, toY, toX, promoChar,
4843                                      parseList[moveNum-1]);
4844             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4845               case MT_NONE:
4846               case MT_STALEMATE:
4847               default:
4848                 break;
4849               case MT_CHECK:
4850                 if(!IS_SHOGI(gameInfo.variant))
4851                     strcat(parseList[moveNum - 1], "+");
4852                 break;
4853               case MT_CHECKMATE:
4854               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4855                 strcat(parseList[moveNum - 1], "#");
4856                 break;
4857             }
4858             strcat(parseList[moveNum - 1], " ");
4859             strcat(parseList[moveNum - 1], elapsed_time);
4860             /* currentMoveString is set as a side-effect of ParseOneMove */
4861             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4862             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4863             strcat(moveList[moveNum - 1], "\n");
4864
4865             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4866                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4867               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4868                 ChessSquare old, new = boards[moveNum][k][j];
4869                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4870                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4871                   if(old == new) continue;
4872                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4873                   else if(new == WhiteWazir || new == BlackWazir) {
4874                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4875                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4876                       else boards[moveNum][k][j] = old; // preserve type of Gold
4877                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4878                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4879               }
4880           } else {
4881             /* Move from ICS was illegal!?  Punt. */
4882             if (appData.debugMode) {
4883               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4884               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4885             }
4886             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4887             strcat(parseList[moveNum - 1], " ");
4888             strcat(parseList[moveNum - 1], elapsed_time);
4889             moveList[moveNum - 1][0] = NULLCHAR;
4890             fromX = fromY = toX = toY = -1;
4891           }
4892         }
4893   if (appData.debugMode) {
4894     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4895     setbuf(debugFP, NULL);
4896   }
4897
4898 #if ZIPPY
4899         /* Send move to chess program (BEFORE animating it). */
4900         if (appData.zippyPlay && !newGame && newMove &&
4901            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4902
4903             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4904                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4905                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4906                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4907                             move_str);
4908                     DisplayError(str, 0);
4909                 } else {
4910                     if (first.sendTime) {
4911                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4912                     }
4913                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4914                     if (firstMove && !bookHit) {
4915                         firstMove = FALSE;
4916                         if (first.useColors) {
4917                           SendToProgram(gameMode == IcsPlayingWhite ?
4918                                         "white\ngo\n" :
4919                                         "black\ngo\n", &first);
4920                         } else {
4921                           SendToProgram("go\n", &first);
4922                         }
4923                         first.maybeThinking = TRUE;
4924                     }
4925                 }
4926             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4927               if (moveList[moveNum - 1][0] == NULLCHAR) {
4928                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4929                 DisplayError(str, 0);
4930               } else {
4931                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4932                 SendMoveToProgram(moveNum - 1, &first);
4933               }
4934             }
4935         }
4936 #endif
4937     }
4938
4939     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4940         /* If move comes from a remote source, animate it.  If it
4941            isn't remote, it will have already been animated. */
4942         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4943             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4944         }
4945         if (!pausing && appData.highlightLastMove) {
4946             SetHighlights(fromX, fromY, toX, toY);
4947         }
4948     }
4949
4950     /* Start the clocks */
4951     whiteFlag = blackFlag = FALSE;
4952     appData.clockMode = !(basetime == 0 && increment == 0);
4953     if (ticking == 0) {
4954       ics_clock_paused = TRUE;
4955       StopClocks();
4956     } else if (ticking == 1) {
4957       ics_clock_paused = FALSE;
4958     }
4959     if (gameMode == IcsIdle ||
4960         relation == RELATION_OBSERVING_STATIC ||
4961         relation == RELATION_EXAMINING ||
4962         ics_clock_paused)
4963       DisplayBothClocks();
4964     else
4965       StartClocks();
4966
4967     /* Display opponents and material strengths */
4968     if (gameInfo.variant != VariantBughouse &&
4969         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4970         if (tinyLayout || smallLayout) {
4971             if(gameInfo.variant == VariantNormal)
4972               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4974                     basetime, increment);
4975             else
4976               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4977                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4978                     basetime, increment, (int) gameInfo.variant);
4979         } else {
4980             if(gameInfo.variant == VariantNormal)
4981               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4982                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4983                     basetime, increment);
4984             else
4985               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4986                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4987                     basetime, increment, VariantName(gameInfo.variant));
4988         }
4989         DisplayTitle(str);
4990   if (appData.debugMode) {
4991     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4992   }
4993     }
4994
4995
4996     /* Display the board */
4997     if (!pausing && !appData.noGUI) {
4998
4999       if (appData.premove)
5000           if (!gotPremove ||
5001              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5002              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5003               ClearPremoveHighlights();
5004
5005       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5006         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5007       DrawPosition(j, boards[currentMove]);
5008
5009       DisplayMove(moveNum - 1);
5010       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5011             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5012               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5013         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5014       }
5015     }
5016
5017     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5018 #if ZIPPY
5019     if(bookHit) { // [HGM] book: simulate book reply
5020         static char bookMove[MSG_SIZ]; // a bit generous?
5021
5022         programStats.nodes = programStats.depth = programStats.time =
5023         programStats.score = programStats.got_only_move = 0;
5024         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5025
5026         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5027         strcat(bookMove, bookHit);
5028         HandleMachineMove(bookMove, &first);
5029     }
5030 #endif
5031 }
5032
5033 void
5034 GetMoveListEvent ()
5035 {
5036     char buf[MSG_SIZ];
5037     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5038         ics_getting_history = H_REQUESTED;
5039         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5040         SendToICS(buf);
5041     }
5042 }
5043
5044 void
5045 SendToBoth (char *msg)
5046 {   // to make it easy to keep two engines in step in dual analysis
5047     SendToProgram(msg, &first);
5048     if(second.analyzing) SendToProgram(msg, &second);
5049 }
5050
5051 void
5052 AnalysisPeriodicEvent (int force)
5053 {
5054     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5055          && !force) || !appData.periodicUpdates)
5056       return;
5057
5058     /* Send . command to Crafty to collect stats */
5059     SendToBoth(".\n");
5060
5061     /* Don't send another until we get a response (this makes
5062        us stop sending to old Crafty's which don't understand
5063        the "." command (sending illegal cmds resets node count & time,
5064        which looks bad)) */
5065     programStats.ok_to_send = 0;
5066 }
5067
5068 void
5069 ics_update_width (int new_width)
5070 {
5071         ics_printf("set width %d\n", new_width);
5072 }
5073
5074 void
5075 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5076 {
5077     char buf[MSG_SIZ];
5078
5079     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5080         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5081             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5082             SendToProgram(buf, cps);
5083             return;
5084         }
5085         // null move in variant where engine does not understand it (for analysis purposes)
5086         SendBoard(cps, moveNum + 1); // send position after move in stead.
5087         return;
5088     }
5089     if (cps->useUsermove) {
5090       SendToProgram("usermove ", cps);
5091     }
5092     if (cps->useSAN) {
5093       char *space;
5094       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5095         int len = space - parseList[moveNum];
5096         memcpy(buf, parseList[moveNum], len);
5097         buf[len++] = '\n';
5098         buf[len] = NULLCHAR;
5099       } else {
5100         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5101       }
5102       SendToProgram(buf, cps);
5103     } else {
5104       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5105         AlphaRank(moveList[moveNum], 4);
5106         SendToProgram(moveList[moveNum], cps);
5107         AlphaRank(moveList[moveNum], 4); // and back
5108       } else
5109       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5110        * the engine. It would be nice to have a better way to identify castle
5111        * moves here. */
5112       if(appData.fischerCastling && cps->useOOCastle) {
5113         int fromX = moveList[moveNum][0] - AAA;
5114         int fromY = moveList[moveNum][1] - ONE;
5115         int toX = moveList[moveNum][2] - AAA;
5116         int toY = moveList[moveNum][3] - ONE;
5117         if((boards[moveNum][fromY][fromX] == WhiteKing
5118             && boards[moveNum][toY][toX] == WhiteRook)
5119            || (boards[moveNum][fromY][fromX] == BlackKing
5120                && boards[moveNum][toY][toX] == BlackRook)) {
5121           if(toX > fromX) SendToProgram("O-O\n", cps);
5122           else SendToProgram("O-O-O\n", cps);
5123         }
5124         else SendToProgram(moveList[moveNum], cps);
5125       } else
5126       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5127           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5128                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5129                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5130                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5131           SendToProgram(buf, cps);
5132       } else
5133       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5134         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5135           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5136           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5137                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5138         } else
5139           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5140                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5141         SendToProgram(buf, cps);
5142       }
5143       else SendToProgram(moveList[moveNum], cps);
5144       /* End of additions by Tord */
5145     }
5146
5147     /* [HGM] setting up the opening has brought engine in force mode! */
5148     /*       Send 'go' if we are in a mode where machine should play. */
5149     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5150         (gameMode == TwoMachinesPlay   ||
5151 #if ZIPPY
5152          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5153 #endif
5154          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5155         SendToProgram("go\n", cps);
5156   if (appData.debugMode) {
5157     fprintf(debugFP, "(extra)\n");
5158   }
5159     }
5160     setboardSpoiledMachineBlack = 0;
5161 }
5162
5163 void
5164 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5165 {
5166     char user_move[MSG_SIZ];
5167     char suffix[4];
5168
5169     if(gameInfo.variant == VariantSChess && promoChar) {
5170         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5171         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5172     } else suffix[0] = NULLCHAR;
5173
5174     switch (moveType) {
5175       default:
5176         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5177                 (int)moveType, fromX, fromY, toX, toY);
5178         DisplayError(user_move + strlen("say "), 0);
5179         break;
5180       case WhiteKingSideCastle:
5181       case BlackKingSideCastle:
5182       case WhiteQueenSideCastleWild:
5183       case BlackQueenSideCastleWild:
5184       /* PUSH Fabien */
5185       case WhiteHSideCastleFR:
5186       case BlackHSideCastleFR:
5187       /* POP Fabien */
5188         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5189         break;
5190       case WhiteQueenSideCastle:
5191       case BlackQueenSideCastle:
5192       case WhiteKingSideCastleWild:
5193       case BlackKingSideCastleWild:
5194       /* PUSH Fabien */
5195       case WhiteASideCastleFR:
5196       case BlackASideCastleFR:
5197       /* POP Fabien */
5198         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5199         break;
5200       case WhiteNonPromotion:
5201       case BlackNonPromotion:
5202         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5203         break;
5204       case WhitePromotion:
5205       case BlackPromotion:
5206         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5207            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5208           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5209                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5210                 PieceToChar(WhiteFerz));
5211         else if(gameInfo.variant == VariantGreat)
5212           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5213                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5214                 PieceToChar(WhiteMan));
5215         else
5216           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5217                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5218                 promoChar);
5219         break;
5220       case WhiteDrop:
5221       case BlackDrop:
5222       drop:
5223         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5224                  ToUpper(PieceToChar((ChessSquare) fromX)),
5225                  AAA + toX, ONE + toY);
5226         break;
5227       case IllegalMove:  /* could be a variant we don't quite understand */
5228         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5229       case NormalMove:
5230       case WhiteCapturesEnPassant:
5231       case BlackCapturesEnPassant:
5232         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5234         break;
5235     }
5236     SendToICS(user_move);
5237     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5238         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5239 }
5240
5241 void
5242 UploadGameEvent ()
5243 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5244     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5245     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5246     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5247       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5248       return;
5249     }
5250     if(gameMode != IcsExamining) { // is this ever not the case?
5251         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5252
5253         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5254           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5255         } else { // on FICS we must first go to general examine mode
5256           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5257         }
5258         if(gameInfo.variant != VariantNormal) {
5259             // try figure out wild number, as xboard names are not always valid on ICS
5260             for(i=1; i<=36; i++) {
5261               snprintf(buf, MSG_SIZ, "wild/%d", i);
5262                 if(StringToVariant(buf) == gameInfo.variant) break;
5263             }
5264             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5265             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5266             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5267         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5268         SendToICS(ics_prefix);
5269         SendToICS(buf);
5270         if(startedFromSetupPosition || backwardMostMove != 0) {
5271           fen = PositionToFEN(backwardMostMove, NULL, 1);
5272           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5273             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5274             SendToICS(buf);
5275           } else { // FICS: everything has to set by separate bsetup commands
5276             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5277             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5278             SendToICS(buf);
5279             if(!WhiteOnMove(backwardMostMove)) {
5280                 SendToICS("bsetup tomove black\n");
5281             }
5282             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5283             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5284             SendToICS(buf);
5285             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5286             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5287             SendToICS(buf);
5288             i = boards[backwardMostMove][EP_STATUS];
5289             if(i >= 0) { // set e.p.
5290               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5291                 SendToICS(buf);
5292             }
5293             bsetup++;
5294           }
5295         }
5296       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5297             SendToICS("bsetup done\n"); // switch to normal examining.
5298     }
5299     for(i = backwardMostMove; i<last; i++) {
5300         char buf[20];
5301         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5302         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5303             int len = strlen(moveList[i]);
5304             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5305             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5306         }
5307         SendToICS(buf);
5308     }
5309     SendToICS(ics_prefix);
5310     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5311 }
5312
5313 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5314
5315 void
5316 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5317 {
5318     if (rf == DROP_RANK) {
5319       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5320       sprintf(move, "%c@%c%c\n",
5321                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5322     } else {
5323         if (promoChar == 'x' || promoChar == NULLCHAR) {
5324           sprintf(move, "%c%c%c%c\n",
5325                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5326           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5327         } else {
5328             sprintf(move, "%c%c%c%c%c\n",
5329                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5330         }
5331     }
5332 }
5333
5334 void
5335 ProcessICSInitScript (FILE *f)
5336 {
5337     char buf[MSG_SIZ];
5338
5339     while (fgets(buf, MSG_SIZ, f)) {
5340         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5341     }
5342
5343     fclose(f);
5344 }
5345
5346
5347 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5348 int dragging;
5349 static ClickType lastClickType;
5350
5351 int
5352 Partner (ChessSquare *p)
5353 { // change piece into promotion partner if one shogi-promotes to the other
5354   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5355   ChessSquare partner;
5356   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5357   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5358   *p = partner;
5359   return 1;
5360 }
5361
5362 void
5363 Sweep (int step)
5364 {
5365     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5366     static int toggleFlag;
5367     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5368     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5369     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5370     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5371     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5372     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5373     do {
5374         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5375         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5376         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5377         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5378         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5379         if(!step) step = -1;
5380     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5381             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5382             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5383             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5384     if(toX >= 0) {
5385         int victim = boards[currentMove][toY][toX];
5386         boards[currentMove][toY][toX] = promoSweep;
5387         DrawPosition(FALSE, boards[currentMove]);
5388         boards[currentMove][toY][toX] = victim;
5389     } else
5390     ChangeDragPiece(promoSweep);
5391 }
5392
5393 int
5394 PromoScroll (int x, int y)
5395 {
5396   int step = 0;
5397
5398   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5399   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5400   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5401   if(!step) return FALSE;
5402   lastX = x; lastY = y;
5403   if((promoSweep < BlackPawn) == flipView) step = -step;
5404   if(step > 0) selectFlag = 1;
5405   if(!selectFlag) Sweep(step);
5406   return FALSE;
5407 }
5408
5409 void
5410 NextPiece (int step)
5411 {
5412     ChessSquare piece = boards[currentMove][toY][toX];
5413     do {
5414         pieceSweep -= step;
5415         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5416         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5417         if(!step) step = -1;
5418     } while(PieceToChar(pieceSweep) == '.');
5419     boards[currentMove][toY][toX] = pieceSweep;
5420     DrawPosition(FALSE, boards[currentMove]);
5421     boards[currentMove][toY][toX] = piece;
5422 }
5423 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5424 void
5425 AlphaRank (char *move, int n)
5426 {
5427 //    char *p = move, c; int x, y;
5428
5429     if (appData.debugMode) {
5430         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5431     }
5432
5433     if(move[1]=='*' &&
5434        move[2]>='0' && move[2]<='9' &&
5435        move[3]>='a' && move[3]<='x'    ) {
5436         move[1] = '@';
5437         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5438         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5439     } else
5440     if(move[0]>='0' && move[0]<='9' &&
5441        move[1]>='a' && move[1]<='x' &&
5442        move[2]>='0' && move[2]<='9' &&
5443        move[3]>='a' && move[3]<='x'    ) {
5444         /* input move, Shogi -> normal */
5445         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5446         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5447         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5448         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5449     } else
5450     if(move[1]=='@' &&
5451        move[3]>='0' && move[3]<='9' &&
5452        move[2]>='a' && move[2]<='x'    ) {
5453         move[1] = '*';
5454         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5455         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5456     } else
5457     if(
5458        move[0]>='a' && move[0]<='x' &&
5459        move[3]>='0' && move[3]<='9' &&
5460        move[2]>='a' && move[2]<='x'    ) {
5461          /* output move, normal -> Shogi */
5462         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5463         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5464         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5465         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5466         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5467     }
5468     if (appData.debugMode) {
5469         fprintf(debugFP, "   out = '%s'\n", move);
5470     }
5471 }
5472
5473 char yy_textstr[8000];
5474
5475 /* Parser for moves from gnuchess, ICS, or user typein box */
5476 Boolean
5477 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5478 {
5479     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5480
5481     switch (*moveType) {
5482       case WhitePromotion:
5483       case BlackPromotion:
5484       case WhiteNonPromotion:
5485       case BlackNonPromotion:
5486       case NormalMove:
5487       case FirstLeg:
5488       case WhiteCapturesEnPassant:
5489       case BlackCapturesEnPassant:
5490       case WhiteKingSideCastle:
5491       case WhiteQueenSideCastle:
5492       case BlackKingSideCastle:
5493       case BlackQueenSideCastle:
5494       case WhiteKingSideCastleWild:
5495       case WhiteQueenSideCastleWild:
5496       case BlackKingSideCastleWild:
5497       case BlackQueenSideCastleWild:
5498       /* Code added by Tord: */
5499       case WhiteHSideCastleFR:
5500       case WhiteASideCastleFR:
5501       case BlackHSideCastleFR:
5502       case BlackASideCastleFR:
5503       /* End of code added by Tord */
5504       case IllegalMove:         /* bug or odd chess variant */
5505         *fromX = currentMoveString[0] - AAA;
5506         *fromY = currentMoveString[1] - ONE;
5507         *toX = currentMoveString[2] - AAA;
5508         *toY = currentMoveString[3] - ONE;
5509         *promoChar = currentMoveString[4];
5510         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5511             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5512     if (appData.debugMode) {
5513         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5514     }
5515             *fromX = *fromY = *toX = *toY = 0;
5516             return FALSE;
5517         }
5518         if (appData.testLegality) {
5519           return (*moveType != IllegalMove);
5520         } else {
5521           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5522                          // [HGM] lion: if this is a double move we are less critical
5523                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5524         }
5525
5526       case WhiteDrop:
5527       case BlackDrop:
5528         *fromX = *moveType == WhiteDrop ?
5529           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5530           (int) CharToPiece(ToLower(currentMoveString[0]));
5531         *fromY = DROP_RANK;
5532         *toX = currentMoveString[2] - AAA;
5533         *toY = currentMoveString[3] - ONE;
5534         *promoChar = NULLCHAR;
5535         return TRUE;
5536
5537       case AmbiguousMove:
5538       case ImpossibleMove:
5539       case EndOfFile:
5540       case ElapsedTime:
5541       case Comment:
5542       case PGNTag:
5543       case NAG:
5544       case WhiteWins:
5545       case BlackWins:
5546       case GameIsDrawn:
5547       default:
5548     if (appData.debugMode) {
5549         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5550     }
5551         /* bug? */
5552         *fromX = *fromY = *toX = *toY = 0;
5553         *promoChar = NULLCHAR;
5554         return FALSE;
5555     }
5556 }
5557
5558 Boolean pushed = FALSE;
5559 char *lastParseAttempt;
5560
5561 void
5562 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5563 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5564   int fromX, fromY, toX, toY; char promoChar;
5565   ChessMove moveType;
5566   Boolean valid;
5567   int nr = 0;
5568
5569   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5570   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5571     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5572     pushed = TRUE;
5573   }
5574   endPV = forwardMostMove;
5575   do {
5576     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5577     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5578     lastParseAttempt = pv;
5579     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5580     if(!valid && nr == 0 &&
5581        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5582         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5583         // Hande case where played move is different from leading PV move
5584         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5585         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5586         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5587         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5588           endPV += 2; // if position different, keep this
5589           moveList[endPV-1][0] = fromX + AAA;
5590           moveList[endPV-1][1] = fromY + ONE;
5591           moveList[endPV-1][2] = toX + AAA;
5592           moveList[endPV-1][3] = toY + ONE;
5593           parseList[endPV-1][0] = NULLCHAR;
5594           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5595         }
5596       }
5597     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5598     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5599     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5600     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5601         valid++; // allow comments in PV
5602         continue;
5603     }
5604     nr++;
5605     if(endPV+1 > framePtr) break; // no space, truncate
5606     if(!valid) break;
5607     endPV++;
5608     CopyBoard(boards[endPV], boards[endPV-1]);
5609     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5610     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5611     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5612     CoordsToAlgebraic(boards[endPV - 1],
5613                              PosFlags(endPV - 1),
5614                              fromY, fromX, toY, toX, promoChar,
5615                              parseList[endPV - 1]);
5616   } while(valid);
5617   if(atEnd == 2) return; // used hidden, for PV conversion
5618   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5619   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5620   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5621                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5622   DrawPosition(TRUE, boards[currentMove]);
5623 }
5624
5625 int
5626 MultiPV (ChessProgramState *cps)
5627 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5628         int i;
5629         for(i=0; i<cps->nrOptions; i++)
5630             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5631                 return i;
5632         return -1;
5633 }
5634
5635 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5636
5637 Boolean
5638 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5639 {
5640         int startPV, multi, lineStart, origIndex = index;
5641         char *p, buf2[MSG_SIZ];
5642         ChessProgramState *cps = (pane ? &second : &first);
5643
5644         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5645         lastX = x; lastY = y;
5646         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5647         lineStart = startPV = index;
5648         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5649         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5650         index = startPV;
5651         do{ while(buf[index] && buf[index] != '\n') index++;
5652         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5653         buf[index] = 0;
5654         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5655                 int n = cps->option[multi].value;
5656                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5657                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5658                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5659                 cps->option[multi].value = n;
5660                 *start = *end = 0;
5661                 return FALSE;
5662         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5663                 ExcludeClick(origIndex - lineStart);
5664                 return FALSE;
5665         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5666                 Collapse(origIndex - lineStart);
5667                 return FALSE;
5668         }
5669         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5670         *start = startPV; *end = index-1;
5671         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5672         return TRUE;
5673 }
5674
5675 char *
5676 PvToSAN (char *pv)
5677 {
5678         static char buf[10*MSG_SIZ];
5679         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5680         *buf = NULLCHAR;
5681         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5682         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5683         for(i = forwardMostMove; i<endPV; i++){
5684             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5685             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5686             k += strlen(buf+k);
5687         }
5688         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5689         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5690         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5691         endPV = savedEnd;
5692         return buf;
5693 }
5694
5695 Boolean
5696 LoadPV (int x, int y)
5697 { // called on right mouse click to load PV
5698   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5699   lastX = x; lastY = y;
5700   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5701   extendGame = FALSE;
5702   return TRUE;
5703 }
5704
5705 void
5706 UnLoadPV ()
5707 {
5708   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5709   if(endPV < 0) return;
5710   if(appData.autoCopyPV) CopyFENToClipboard();
5711   endPV = -1;
5712   if(extendGame && currentMove > forwardMostMove) {
5713         Boolean saveAnimate = appData.animate;
5714         if(pushed) {
5715             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5716                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5717             } else storedGames--; // abandon shelved tail of original game
5718         }
5719         pushed = FALSE;
5720         forwardMostMove = currentMove;
5721         currentMove = oldFMM;
5722         appData.animate = FALSE;
5723         ToNrEvent(forwardMostMove);
5724         appData.animate = saveAnimate;
5725   }
5726   currentMove = forwardMostMove;
5727   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5728   ClearPremoveHighlights();
5729   DrawPosition(TRUE, boards[currentMove]);
5730 }
5731
5732 void
5733 MovePV (int x, int y, int h)
5734 { // step through PV based on mouse coordinates (called on mouse move)
5735   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5736
5737   // we must somehow check if right button is still down (might be released off board!)
5738   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5739   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5740   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5741   if(!step) return;
5742   lastX = x; lastY = y;
5743
5744   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5745   if(endPV < 0) return;
5746   if(y < margin) step = 1; else
5747   if(y > h - margin) step = -1;
5748   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5749   currentMove += step;
5750   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5751   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5752                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5753   DrawPosition(FALSE, boards[currentMove]);
5754 }
5755
5756
5757 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5758 // All positions will have equal probability, but the current method will not provide a unique
5759 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5760 #define DARK 1
5761 #define LITE 2
5762 #define ANY 3
5763
5764 int squaresLeft[4];
5765 int piecesLeft[(int)BlackPawn];
5766 int seed, nrOfShuffles;
5767
5768 void
5769 GetPositionNumber ()
5770 {       // sets global variable seed
5771         int i;
5772
5773         seed = appData.defaultFrcPosition;
5774         if(seed < 0) { // randomize based on time for negative FRC position numbers
5775                 for(i=0; i<50; i++) seed += random();
5776                 seed = random() ^ random() >> 8 ^ random() << 8;
5777                 if(seed<0) seed = -seed;
5778         }
5779 }
5780
5781 int
5782 put (Board board, int pieceType, int rank, int n, int shade)
5783 // put the piece on the (n-1)-th empty squares of the given shade
5784 {
5785         int i;
5786
5787         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5788                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5789                         board[rank][i] = (ChessSquare) pieceType;
5790                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5791                         squaresLeft[ANY]--;
5792                         piecesLeft[pieceType]--;
5793                         return i;
5794                 }
5795         }
5796         return -1;
5797 }
5798
5799
5800 void
5801 AddOnePiece (Board board, int pieceType, int rank, int shade)
5802 // calculate where the next piece goes, (any empty square), and put it there
5803 {
5804         int i;
5805
5806         i = seed % squaresLeft[shade];
5807         nrOfShuffles *= squaresLeft[shade];
5808         seed /= squaresLeft[shade];
5809         put(board, pieceType, rank, i, shade);
5810 }
5811
5812 void
5813 AddTwoPieces (Board board, int pieceType, int rank)
5814 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5815 {
5816         int i, n=squaresLeft[ANY], j=n-1, k;
5817
5818         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5819         i = seed % k;  // pick one
5820         nrOfShuffles *= k;
5821         seed /= k;
5822         while(i >= j) i -= j--;
5823         j = n - 1 - j; i += j;
5824         put(board, pieceType, rank, j, ANY);
5825         put(board, pieceType, rank, i, ANY);
5826 }
5827
5828 void
5829 SetUpShuffle (Board board, int number)
5830 {
5831         int i, p, first=1;
5832
5833         GetPositionNumber(); nrOfShuffles = 1;
5834
5835         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5836         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5837         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5838
5839         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5840
5841         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5842             p = (int) board[0][i];
5843             if(p < (int) BlackPawn) piecesLeft[p] ++;
5844             board[0][i] = EmptySquare;
5845         }
5846
5847         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5848             // shuffles restricted to allow normal castling put KRR first
5849             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5850                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5851             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5852                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5853             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5854                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5855             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5856                 put(board, WhiteRook, 0, 0, ANY);
5857             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5858         }
5859
5860         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5861             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5862             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5863                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5864                 while(piecesLeft[p] >= 2) {
5865                     AddOnePiece(board, p, 0, LITE);
5866                     AddOnePiece(board, p, 0, DARK);
5867                 }
5868                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5869             }
5870
5871         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5872             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5873             // but we leave King and Rooks for last, to possibly obey FRC restriction
5874             if(p == (int)WhiteRook) continue;
5875             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5876             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5877         }
5878
5879         // now everything is placed, except perhaps King (Unicorn) and Rooks
5880
5881         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5882             // Last King gets castling rights
5883             while(piecesLeft[(int)WhiteUnicorn]) {
5884                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5885                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5886             }
5887
5888             while(piecesLeft[(int)WhiteKing]) {
5889                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5890                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5891             }
5892
5893
5894         } else {
5895             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5896             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5897         }
5898
5899         // Only Rooks can be left; simply place them all
5900         while(piecesLeft[(int)WhiteRook]) {
5901                 i = put(board, WhiteRook, 0, 0, ANY);
5902                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5903                         if(first) {
5904                                 first=0;
5905                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5906                         }
5907                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5908                 }
5909         }
5910         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5911             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5912         }
5913
5914         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5915 }
5916
5917 int
5918 SetCharTable (char *table, const char * map)
5919 /* [HGM] moved here from winboard.c because of its general usefulness */
5920 /*       Basically a safe strcpy that uses the last character as King */
5921 {
5922     int result = FALSE; int NrPieces;
5923
5924     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5925                     && NrPieces >= 12 && !(NrPieces&1)) {
5926         int i; /* [HGM] Accept even length from 12 to 34 */
5927
5928         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5929         for( i=0; i<NrPieces/2-1; i++ ) {
5930             table[i] = map[i];
5931             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5932         }
5933         table[(int) WhiteKing]  = map[NrPieces/2-1];
5934         table[(int) BlackKing]  = map[NrPieces-1];
5935
5936         result = TRUE;
5937     }
5938
5939     return result;
5940 }
5941
5942 void
5943 Prelude (Board board)
5944 {       // [HGM] superchess: random selection of exo-pieces
5945         int i, j, k; ChessSquare p;
5946         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5947
5948         GetPositionNumber(); // use FRC position number
5949
5950         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5951             SetCharTable(pieceToChar, appData.pieceToCharTable);
5952             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5953                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5954         }
5955
5956         j = seed%4;                 seed /= 4;
5957         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5958         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5959         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5960         j = seed%3 + (seed%3 >= j); seed /= 3;
5961         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5962         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5963         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5964         j = seed%3;                 seed /= 3;
5965         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5966         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5967         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5968         j = seed%2 + (seed%2 >= j); seed /= 2;
5969         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5970         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5971         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5972         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5973         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5974         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5975         put(board, exoPieces[0],    0, 0, ANY);
5976         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5977 }
5978
5979 void
5980 InitPosition (int redraw)
5981 {
5982     ChessSquare (* pieces)[BOARD_FILES];
5983     int i, j, pawnRow=1, pieceRows=1, overrule,
5984     oldx = gameInfo.boardWidth,
5985     oldy = gameInfo.boardHeight,
5986     oldh = gameInfo.holdingsWidth;
5987     static int oldv;
5988
5989     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5990
5991     /* [AS] Initialize pv info list [HGM] and game status */
5992     {
5993         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5994             pvInfoList[i].depth = 0;
5995             boards[i][EP_STATUS] = EP_NONE;
5996             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5997         }
5998
5999         initialRulePlies = 0; /* 50-move counter start */
6000
6001         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6002         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6003     }
6004
6005
6006     /* [HGM] logic here is completely changed. In stead of full positions */
6007     /* the initialized data only consist of the two backranks. The switch */
6008     /* selects which one we will use, which is than copied to the Board   */
6009     /* initialPosition, which for the rest is initialized by Pawns and    */
6010     /* empty squares. This initial position is then copied to boards[0],  */
6011     /* possibly after shuffling, so that it remains available.            */
6012
6013     gameInfo.holdingsWidth = 0; /* default board sizes */
6014     gameInfo.boardWidth    = 8;
6015     gameInfo.boardHeight   = 8;
6016     gameInfo.holdingsSize  = 0;
6017     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6018     for(i=0; i<BOARD_FILES-2; i++)
6019       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6020     initialPosition[EP_STATUS] = EP_NONE;
6021     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6022     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6023          SetCharTable(pieceNickName, appData.pieceNickNames);
6024     else SetCharTable(pieceNickName, "............");
6025     pieces = FIDEArray;
6026
6027     switch (gameInfo.variant) {
6028     case VariantFischeRandom:
6029       shuffleOpenings = TRUE;
6030       appData.fischerCastling = TRUE;
6031     default:
6032       break;
6033     case VariantShatranj:
6034       pieces = ShatranjArray;
6035       nrCastlingRights = 0;
6036       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6037       break;
6038     case VariantMakruk:
6039       pieces = makrukArray;
6040       nrCastlingRights = 0;
6041       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6042       break;
6043     case VariantASEAN:
6044       pieces = aseanArray;
6045       nrCastlingRights = 0;
6046       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6047       break;
6048     case VariantTwoKings:
6049       pieces = twoKingsArray;
6050       break;
6051     case VariantGrand:
6052       pieces = GrandArray;
6053       nrCastlingRights = 0;
6054       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6055       gameInfo.boardWidth = 10;
6056       gameInfo.boardHeight = 10;
6057       gameInfo.holdingsSize = 7;
6058       break;
6059     case VariantCapaRandom:
6060       shuffleOpenings = TRUE;
6061       appData.fischerCastling = TRUE;
6062     case VariantCapablanca:
6063       pieces = CapablancaArray;
6064       gameInfo.boardWidth = 10;
6065       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6066       break;
6067     case VariantGothic:
6068       pieces = GothicArray;
6069       gameInfo.boardWidth = 10;
6070       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6071       break;
6072     case VariantSChess:
6073       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6074       gameInfo.holdingsSize = 7;
6075       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6076       break;
6077     case VariantJanus:
6078       pieces = JanusArray;
6079       gameInfo.boardWidth = 10;
6080       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6081       nrCastlingRights = 6;
6082         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6083         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6084         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6085         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6086         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6087         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6088       break;
6089     case VariantFalcon:
6090       pieces = FalconArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6093       break;
6094     case VariantXiangqi:
6095       pieces = XiangqiArray;
6096       gameInfo.boardWidth  = 9;
6097       gameInfo.boardHeight = 10;
6098       nrCastlingRights = 0;
6099       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6100       break;
6101     case VariantShogi:
6102       pieces = ShogiArray;
6103       gameInfo.boardWidth  = 9;
6104       gameInfo.boardHeight = 9;
6105       gameInfo.holdingsSize = 7;
6106       nrCastlingRights = 0;
6107       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6108       break;
6109     case VariantChu:
6110       pieces = ChuArray; pieceRows = 3;
6111       gameInfo.boardWidth  = 12;
6112       gameInfo.boardHeight = 12;
6113       nrCastlingRights = 0;
6114       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6115                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6116       break;
6117     case VariantCourier:
6118       pieces = CourierArray;
6119       gameInfo.boardWidth  = 12;
6120       nrCastlingRights = 0;
6121       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6122       break;
6123     case VariantKnightmate:
6124       pieces = KnightmateArray;
6125       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6126       break;
6127     case VariantSpartan:
6128       pieces = SpartanArray;
6129       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6130       break;
6131     case VariantLion:
6132       pieces = lionArray;
6133       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6134       break;
6135     case VariantChuChess:
6136       pieces = ChuChessArray;
6137       gameInfo.boardWidth = 10;
6138       gameInfo.boardHeight = 10;
6139       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6140       break;
6141     case VariantFairy:
6142       pieces = fairyArray;
6143       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6144       break;
6145     case VariantGreat:
6146       pieces = GreatArray;
6147       gameInfo.boardWidth = 10;
6148       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6149       gameInfo.holdingsSize = 8;
6150       break;
6151     case VariantSuper:
6152       pieces = FIDEArray;
6153       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6154       gameInfo.holdingsSize = 8;
6155       startedFromSetupPosition = TRUE;
6156       break;
6157     case VariantCrazyhouse:
6158     case VariantBughouse:
6159       pieces = FIDEArray;
6160       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6161       gameInfo.holdingsSize = 5;
6162       break;
6163     case VariantWildCastle:
6164       pieces = FIDEArray;
6165       /* !!?shuffle with kings guaranteed to be on d or e file */
6166       shuffleOpenings = 1;
6167       break;
6168     case VariantNoCastle:
6169       pieces = FIDEArray;
6170       nrCastlingRights = 0;
6171       /* !!?unconstrained back-rank shuffle */
6172       shuffleOpenings = 1;
6173       break;
6174     }
6175
6176     overrule = 0;
6177     if(appData.NrFiles >= 0) {
6178         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6179         gameInfo.boardWidth = appData.NrFiles;
6180     }
6181     if(appData.NrRanks >= 0) {
6182         gameInfo.boardHeight = appData.NrRanks;
6183     }
6184     if(appData.holdingsSize >= 0) {
6185         i = appData.holdingsSize;
6186         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6187         gameInfo.holdingsSize = i;
6188     }
6189     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6190     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6191         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6192
6193     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6194     if(pawnRow < 1) pawnRow = 1;
6195     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6196        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6197     if(gameInfo.variant == VariantChu) pawnRow = 3;
6198
6199     /* User pieceToChar list overrules defaults */
6200     if(appData.pieceToCharTable != NULL)
6201         SetCharTable(pieceToChar, appData.pieceToCharTable);
6202
6203     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6204
6205         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6206             s = (ChessSquare) 0; /* account holding counts in guard band */
6207         for( i=0; i<BOARD_HEIGHT; i++ )
6208             initialPosition[i][j] = s;
6209
6210         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6211         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6212         initialPosition[pawnRow][j] = WhitePawn;
6213         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6214         if(gameInfo.variant == VariantXiangqi) {
6215             if(j&1) {
6216                 initialPosition[pawnRow][j] =
6217                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6218                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6219                    initialPosition[2][j] = WhiteCannon;
6220                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6221                 }
6222             }
6223         }
6224         if(gameInfo.variant == VariantChu) {
6225              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6226                initialPosition[pawnRow+1][j] = WhiteCobra,
6227                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6228              for(i=1; i<pieceRows; i++) {
6229                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6230                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6231              }
6232         }
6233         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6234             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6235                initialPosition[0][j] = WhiteRook;
6236                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6237             }
6238         }
6239         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6240     }
6241     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6242     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6243
6244             j=BOARD_LEFT+1;
6245             initialPosition[1][j] = WhiteBishop;
6246             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6247             j=BOARD_RGHT-2;
6248             initialPosition[1][j] = WhiteRook;
6249             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6250     }
6251
6252     if( nrCastlingRights == -1) {
6253         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6254         /*       This sets default castling rights from none to normal corners   */
6255         /* Variants with other castling rights must set them themselves above    */
6256         nrCastlingRights = 6;
6257
6258         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6259         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6260         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6261         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6262         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6263         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6264      }
6265
6266      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6267      if(gameInfo.variant == VariantGreat) { // promotion commoners
6268         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6269         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6270         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6271         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6272      }
6273      if( gameInfo.variant == VariantSChess ) {
6274       initialPosition[1][0] = BlackMarshall;
6275       initialPosition[2][0] = BlackAngel;
6276       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6277       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6278       initialPosition[1][1] = initialPosition[2][1] =
6279       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6280      }
6281   if (appData.debugMode) {
6282     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6283   }
6284     if(shuffleOpenings) {
6285         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6286         startedFromSetupPosition = TRUE;
6287     }
6288     if(startedFromPositionFile) {
6289       /* [HGM] loadPos: use PositionFile for every new game */
6290       CopyBoard(initialPosition, filePosition);
6291       for(i=0; i<nrCastlingRights; i++)
6292           initialRights[i] = filePosition[CASTLING][i];
6293       startedFromSetupPosition = TRUE;
6294     }
6295
6296     CopyBoard(boards[0], initialPosition);
6297
6298     if(oldx != gameInfo.boardWidth ||
6299        oldy != gameInfo.boardHeight ||
6300        oldv != gameInfo.variant ||
6301        oldh != gameInfo.holdingsWidth
6302                                          )
6303             InitDrawingSizes(-2 ,0);
6304
6305     oldv = gameInfo.variant;
6306     if (redraw)
6307       DrawPosition(TRUE, boards[currentMove]);
6308 }
6309
6310 void
6311 SendBoard (ChessProgramState *cps, int moveNum)
6312 {
6313     char message[MSG_SIZ];
6314
6315     if (cps->useSetboard) {
6316       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6317       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6318       SendToProgram(message, cps);
6319       free(fen);
6320
6321     } else {
6322       ChessSquare *bp;
6323       int i, j, left=0, right=BOARD_WIDTH;
6324       /* Kludge to set black to move, avoiding the troublesome and now
6325        * deprecated "black" command.
6326        */
6327       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6328         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6329
6330       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6331
6332       SendToProgram("edit\n", cps);
6333       SendToProgram("#\n", cps);
6334       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6335         bp = &boards[moveNum][i][left];
6336         for (j = left; j < right; j++, bp++) {
6337           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6338           if ((int) *bp < (int) BlackPawn) {
6339             if(j == BOARD_RGHT+1)
6340                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6341             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6342             if(message[0] == '+' || message[0] == '~') {
6343               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6344                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6345                         AAA + j, ONE + i);
6346             }
6347             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6348                 message[1] = BOARD_RGHT   - 1 - j + '1';
6349                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6350             }
6351             SendToProgram(message, cps);
6352           }
6353         }
6354       }
6355
6356       SendToProgram("c\n", cps);
6357       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6358         bp = &boards[moveNum][i][left];
6359         for (j = left; j < right; j++, bp++) {
6360           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6361           if (((int) *bp != (int) EmptySquare)
6362               && ((int) *bp >= (int) BlackPawn)) {
6363             if(j == BOARD_LEFT-2)
6364                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6365             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6366                     AAA + j, ONE + i);
6367             if(message[0] == '+' || message[0] == '~') {
6368               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6369                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6370                         AAA + j, ONE + i);
6371             }
6372             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6373                 message[1] = BOARD_RGHT   - 1 - j + '1';
6374                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6375             }
6376             SendToProgram(message, cps);
6377           }
6378         }
6379       }
6380
6381       SendToProgram(".\n", cps);
6382     }
6383     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6384 }
6385
6386 char exclusionHeader[MSG_SIZ];
6387 int exCnt, excludePtr;
6388 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6389 static Exclusion excluTab[200];
6390 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6391
6392 static void
6393 WriteMap (int s)
6394 {
6395     int j;
6396     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6397     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6398 }
6399
6400 static void
6401 ClearMap ()
6402 {
6403     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6404     excludePtr = 24; exCnt = 0;
6405     WriteMap(0);
6406 }
6407
6408 static void
6409 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6410 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6411     char buf[2*MOVE_LEN], *p;
6412     Exclusion *e = excluTab;
6413     int i;
6414     for(i=0; i<exCnt; i++)
6415         if(e[i].ff == fromX && e[i].fr == fromY &&
6416            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6417     if(i == exCnt) { // was not in exclude list; add it
6418         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6419         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6420             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6421             return; // abort
6422         }
6423         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6424         excludePtr++; e[i].mark = excludePtr++;
6425         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6426         exCnt++;
6427     }
6428     exclusionHeader[e[i].mark] = state;
6429 }
6430
6431 static int
6432 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6433 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6434     char buf[MSG_SIZ];
6435     int j, k;
6436     ChessMove moveType;
6437     if((signed char)promoChar == -1) { // kludge to indicate best move
6438         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6439             return 1; // if unparsable, abort
6440     }
6441     // update exclusion map (resolving toggle by consulting existing state)
6442     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6443     j = k%8; k >>= 3;
6444     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6445     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6446          excludeMap[k] |=   1<<j;
6447     else excludeMap[k] &= ~(1<<j);
6448     // update header
6449     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6450     // inform engine
6451     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6452     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6453     SendToBoth(buf);
6454     return (state == '+');
6455 }
6456
6457 static void
6458 ExcludeClick (int index)
6459 {
6460     int i, j;
6461     Exclusion *e = excluTab;
6462     if(index < 25) { // none, best or tail clicked
6463         if(index < 13) { // none: include all
6464             WriteMap(0); // clear map
6465             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6466             SendToBoth("include all\n"); // and inform engine
6467         } else if(index > 18) { // tail
6468             if(exclusionHeader[19] == '-') { // tail was excluded
6469                 SendToBoth("include all\n");
6470                 WriteMap(0); // clear map completely
6471                 // now re-exclude selected moves
6472                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6473                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6474             } else { // tail was included or in mixed state
6475                 SendToBoth("exclude all\n");
6476                 WriteMap(0xFF); // fill map completely
6477                 // now re-include selected moves
6478                 j = 0; // count them
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, '+'), j++;
6481                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6482             }
6483         } else { // best
6484             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6485         }
6486     } else {
6487         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6488             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6489             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6490             break;
6491         }
6492     }
6493 }
6494
6495 ChessSquare
6496 DefaultPromoChoice (int white)
6497 {
6498     ChessSquare result;
6499     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6500        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6501         result = WhiteFerz; // no choice
6502     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6503         result= WhiteKing; // in Suicide Q is the last thing we want
6504     else if(gameInfo.variant == VariantSpartan)
6505         result = white ? WhiteQueen : WhiteAngel;
6506     else result = WhiteQueen;
6507     if(!white) result = WHITE_TO_BLACK result;
6508     return result;
6509 }
6510
6511 static int autoQueen; // [HGM] oneclick
6512
6513 int
6514 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6515 {
6516     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6517     /* [HGM] add Shogi promotions */
6518     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6519     ChessSquare piece, partner;
6520     ChessMove moveType;
6521     Boolean premove;
6522
6523     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6524     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6525
6526     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6527       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6528         return FALSE;
6529
6530     piece = boards[currentMove][fromY][fromX];
6531     if(gameInfo.variant == VariantChu) {
6532         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6533         promotionZoneSize = BOARD_HEIGHT/3;
6534         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6535     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6536         promotionZoneSize = BOARD_HEIGHT/3;
6537         highestPromotingPiece = (int)WhiteAlfil;
6538     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6539         promotionZoneSize = 3;
6540     }
6541
6542     // Treat Lance as Pawn when it is not representing Amazon or Lance
6543     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6544         if(piece == WhiteLance) piece = WhitePawn; else
6545         if(piece == BlackLance) piece = BlackPawn;
6546     }
6547
6548     // next weed out all moves that do not touch the promotion zone at all
6549     if((int)piece >= BlackPawn) {
6550         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6551              return FALSE;
6552         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6553         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6554     } else {
6555         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6556            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6557         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6558              return FALSE;
6559     }
6560
6561     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6562
6563     // weed out mandatory Shogi promotions
6564     if(gameInfo.variant == VariantShogi) {
6565         if(piece >= BlackPawn) {
6566             if(toY == 0 && piece == BlackPawn ||
6567                toY == 0 && piece == BlackQueen ||
6568                toY <= 1 && piece == BlackKnight) {
6569                 *promoChoice = '+';
6570                 return FALSE;
6571             }
6572         } else {
6573             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6574                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6575                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6576                 *promoChoice = '+';
6577                 return FALSE;
6578             }
6579         }
6580     }
6581
6582     // weed out obviously illegal Pawn moves
6583     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6584         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6585         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6586         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6587         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6588         // note we are not allowed to test for valid (non-)capture, due to premove
6589     }
6590
6591     // we either have a choice what to promote to, or (in Shogi) whether to promote
6592     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6593        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6594         ChessSquare p=BlackFerz;  // no choice
6595         while(p < EmptySquare) {  //but make sure we use piece that exists
6596             *promoChoice = PieceToChar(p++);
6597             if(*promoChoice != '.') break;
6598         }
6599         return FALSE;
6600     }
6601     // no sense asking what we must promote to if it is going to explode...
6602     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6603         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6604         return FALSE;
6605     }
6606     // give caller the default choice even if we will not make it
6607     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6608     partner = piece; // pieces can promote if the pieceToCharTable says so
6609     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6610     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6611     if(        sweepSelect && gameInfo.variant != VariantGreat
6612                            && gameInfo.variant != VariantGrand
6613                            && gameInfo.variant != VariantSuper) return FALSE;
6614     if(autoQueen) return FALSE; // predetermined
6615
6616     // suppress promotion popup on illegal moves that are not premoves
6617     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6618               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6619     if(appData.testLegality && !premove) {
6620         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6621                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6622         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6623         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6624             return FALSE;
6625     }
6626
6627     return TRUE;
6628 }
6629
6630 int
6631 InPalace (int row, int column)
6632 {   /* [HGM] for Xiangqi */
6633     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6634          column < (BOARD_WIDTH + 4)/2 &&
6635          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6636     return FALSE;
6637 }
6638
6639 int
6640 PieceForSquare (int x, int y)
6641 {
6642   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6643      return -1;
6644   else
6645      return boards[currentMove][y][x];
6646 }
6647
6648 int
6649 OKToStartUserMove (int x, int y)
6650 {
6651     ChessSquare from_piece;
6652     int white_piece;
6653
6654     if (matchMode) return FALSE;
6655     if (gameMode == EditPosition) return TRUE;
6656
6657     if (x >= 0 && y >= 0)
6658       from_piece = boards[currentMove][y][x];
6659     else
6660       from_piece = EmptySquare;
6661
6662     if (from_piece == EmptySquare) return FALSE;
6663
6664     white_piece = (int)from_piece >= (int)WhitePawn &&
6665       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6666
6667     switch (gameMode) {
6668       case AnalyzeFile:
6669       case TwoMachinesPlay:
6670       case EndOfGame:
6671         return FALSE;
6672
6673       case IcsObserving:
6674       case IcsIdle:
6675         return FALSE;
6676
6677       case MachinePlaysWhite:
6678       case IcsPlayingBlack:
6679         if (appData.zippyPlay) return FALSE;
6680         if (white_piece) {
6681             DisplayMoveError(_("You are playing Black"));
6682             return FALSE;
6683         }
6684         break;
6685
6686       case MachinePlaysBlack:
6687       case IcsPlayingWhite:
6688         if (appData.zippyPlay) return FALSE;
6689         if (!white_piece) {
6690             DisplayMoveError(_("You are playing White"));
6691             return FALSE;
6692         }
6693         break;
6694
6695       case PlayFromGameFile:
6696             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6697       case EditGame:
6698         if (!white_piece && WhiteOnMove(currentMove)) {
6699             DisplayMoveError(_("It is White's turn"));
6700             return FALSE;
6701         }
6702         if (white_piece && !WhiteOnMove(currentMove)) {
6703             DisplayMoveError(_("It is Black's turn"));
6704             return FALSE;
6705         }
6706         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6707             /* Editing correspondence game history */
6708             /* Could disallow this or prompt for confirmation */
6709             cmailOldMove = -1;
6710         }
6711         break;
6712
6713       case BeginningOfGame:
6714         if (appData.icsActive) return FALSE;
6715         if (!appData.noChessProgram) {
6716             if (!white_piece) {
6717                 DisplayMoveError(_("You are playing White"));
6718                 return FALSE;
6719             }
6720         }
6721         break;
6722
6723       case Training:
6724         if (!white_piece && WhiteOnMove(currentMove)) {
6725             DisplayMoveError(_("It is White's turn"));
6726             return FALSE;
6727         }
6728         if (white_piece && !WhiteOnMove(currentMove)) {
6729             DisplayMoveError(_("It is Black's turn"));
6730             return FALSE;
6731         }
6732         break;
6733
6734       default:
6735       case IcsExamining:
6736         break;
6737     }
6738     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6739         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6740         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6741         && gameMode != AnalyzeFile && gameMode != Training) {
6742         DisplayMoveError(_("Displayed position is not current"));
6743         return FALSE;
6744     }
6745     return TRUE;
6746 }
6747
6748 Boolean
6749 OnlyMove (int *x, int *y, Boolean captures)
6750 {
6751     DisambiguateClosure cl;
6752     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6753     switch(gameMode) {
6754       case MachinePlaysBlack:
6755       case IcsPlayingWhite:
6756       case BeginningOfGame:
6757         if(!WhiteOnMove(currentMove)) return FALSE;
6758         break;
6759       case MachinePlaysWhite:
6760       case IcsPlayingBlack:
6761         if(WhiteOnMove(currentMove)) return FALSE;
6762         break;
6763       case EditGame:
6764         break;
6765       default:
6766         return FALSE;
6767     }
6768     cl.pieceIn = EmptySquare;
6769     cl.rfIn = *y;
6770     cl.ffIn = *x;
6771     cl.rtIn = -1;
6772     cl.ftIn = -1;
6773     cl.promoCharIn = NULLCHAR;
6774     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6775     if( cl.kind == NormalMove ||
6776         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6777         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6778         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6779       fromX = cl.ff;
6780       fromY = cl.rf;
6781       *x = cl.ft;
6782       *y = cl.rt;
6783       return TRUE;
6784     }
6785     if(cl.kind != ImpossibleMove) return FALSE;
6786     cl.pieceIn = EmptySquare;
6787     cl.rfIn = -1;
6788     cl.ffIn = -1;
6789     cl.rtIn = *y;
6790     cl.ftIn = *x;
6791     cl.promoCharIn = NULLCHAR;
6792     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6793     if( cl.kind == NormalMove ||
6794         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6795         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6796         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6797       fromX = cl.ff;
6798       fromY = cl.rf;
6799       *x = cl.ft;
6800       *y = cl.rt;
6801       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6802       return TRUE;
6803     }
6804     return FALSE;
6805 }
6806
6807 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6808 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6809 int lastLoadGameUseList = FALSE;
6810 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6811 ChessMove lastLoadGameStart = EndOfFile;
6812 int doubleClick;
6813
6814 void
6815 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6816 {
6817     ChessMove moveType;
6818     ChessSquare pup;
6819     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6820
6821     /* Check if the user is playing in turn.  This is complicated because we
6822        let the user "pick up" a piece before it is his turn.  So the piece he
6823        tried to pick up may have been captured by the time he puts it down!
6824        Therefore we use the color the user is supposed to be playing in this
6825        test, not the color of the piece that is currently on the starting
6826        square---except in EditGame mode, where the user is playing both
6827        sides; fortunately there the capture race can't happen.  (It can
6828        now happen in IcsExamining mode, but that's just too bad.  The user
6829        will get a somewhat confusing message in that case.)
6830        */
6831
6832     switch (gameMode) {
6833       case AnalyzeFile:
6834       case TwoMachinesPlay:
6835       case EndOfGame:
6836       case IcsObserving:
6837       case IcsIdle:
6838         /* We switched into a game mode where moves are not accepted,
6839            perhaps while the mouse button was down. */
6840         return;
6841
6842       case MachinePlaysWhite:
6843         /* User is moving for Black */
6844         if (WhiteOnMove(currentMove)) {
6845             DisplayMoveError(_("It is White's turn"));
6846             return;
6847         }
6848         break;
6849
6850       case MachinePlaysBlack:
6851         /* User is moving for White */
6852         if (!WhiteOnMove(currentMove)) {
6853             DisplayMoveError(_("It is Black's turn"));
6854             return;
6855         }
6856         break;
6857
6858       case PlayFromGameFile:
6859             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6860       case EditGame:
6861       case IcsExamining:
6862       case BeginningOfGame:
6863       case AnalyzeMode:
6864       case Training:
6865         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6866         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6867             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6868             /* User is moving for Black */
6869             if (WhiteOnMove(currentMove)) {
6870                 DisplayMoveError(_("It is White's turn"));
6871                 return;
6872             }
6873         } else {
6874             /* User is moving for White */
6875             if (!WhiteOnMove(currentMove)) {
6876                 DisplayMoveError(_("It is Black's turn"));
6877                 return;
6878             }
6879         }
6880         break;
6881
6882       case IcsPlayingBlack:
6883         /* User is moving for Black */
6884         if (WhiteOnMove(currentMove)) {
6885             if (!appData.premove) {
6886                 DisplayMoveError(_("It is White's turn"));
6887             } else if (toX >= 0 && toY >= 0) {
6888                 premoveToX = toX;
6889                 premoveToY = toY;
6890                 premoveFromX = fromX;
6891                 premoveFromY = fromY;
6892                 premovePromoChar = promoChar;
6893                 gotPremove = 1;
6894                 if (appData.debugMode)
6895                     fprintf(debugFP, "Got premove: fromX %d,"
6896                             "fromY %d, toX %d, toY %d\n",
6897                             fromX, fromY, toX, toY);
6898             }
6899             return;
6900         }
6901         break;
6902
6903       case IcsPlayingWhite:
6904         /* User is moving for White */
6905         if (!WhiteOnMove(currentMove)) {
6906             if (!appData.premove) {
6907                 DisplayMoveError(_("It is Black's turn"));
6908             } else if (toX >= 0 && toY >= 0) {
6909                 premoveToX = toX;
6910                 premoveToY = toY;
6911                 premoveFromX = fromX;
6912                 premoveFromY = fromY;
6913                 premovePromoChar = promoChar;
6914                 gotPremove = 1;
6915                 if (appData.debugMode)
6916                     fprintf(debugFP, "Got premove: fromX %d,"
6917                             "fromY %d, toX %d, toY %d\n",
6918                             fromX, fromY, toX, toY);
6919             }
6920             return;
6921         }
6922         break;
6923
6924       default:
6925         break;
6926
6927       case EditPosition:
6928         /* EditPosition, empty square, or different color piece;
6929            click-click move is possible */
6930         if (toX == -2 || toY == -2) {
6931             boards[0][fromY][fromX] = EmptySquare;
6932             DrawPosition(FALSE, boards[currentMove]);
6933             return;
6934         } else if (toX >= 0 && toY >= 0) {
6935             boards[0][toY][toX] = boards[0][fromY][fromX];
6936             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6937                 if(boards[0][fromY][0] != EmptySquare) {
6938                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6939                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6940                 }
6941             } else
6942             if(fromX == BOARD_RGHT+1) {
6943                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6944                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6945                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6946                 }
6947             } else
6948             boards[0][fromY][fromX] = gatingPiece;
6949             DrawPosition(FALSE, boards[currentMove]);
6950             return;
6951         }
6952         return;
6953     }
6954
6955     if(toX < 0 || toY < 0) return;
6956     pup = boards[currentMove][toY][toX];
6957
6958     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6959     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6960          if( pup != EmptySquare ) return;
6961          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6962            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6963                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6964            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6965            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6966            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6967            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6968          fromY = DROP_RANK;
6969     }
6970
6971     /* [HGM] always test for legality, to get promotion info */
6972     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6973                                          fromY, fromX, toY, toX, promoChar);
6974
6975     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6976
6977     /* [HGM] but possibly ignore an IllegalMove result */
6978     if (appData.testLegality) {
6979         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6980             DisplayMoveError(_("Illegal move"));
6981             return;
6982         }
6983     }
6984
6985     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6986         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6987              ClearPremoveHighlights(); // was included
6988         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6989         return;
6990     }
6991
6992     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6993 }
6994
6995 /* Common tail of UserMoveEvent and DropMenuEvent */
6996 int
6997 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6998 {
6999     char *bookHit = 0;
7000
7001     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7002         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7003         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7004         if(WhiteOnMove(currentMove)) {
7005             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7006         } else {
7007             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7008         }
7009     }
7010
7011     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7012        move type in caller when we know the move is a legal promotion */
7013     if(moveType == NormalMove && promoChar)
7014         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7015
7016     /* [HGM] <popupFix> The following if has been moved here from
7017        UserMoveEvent(). Because it seemed to belong here (why not allow
7018        piece drops in training games?), and because it can only be
7019        performed after it is known to what we promote. */
7020     if (gameMode == Training) {
7021       /* compare the move played on the board to the next move in the
7022        * game. If they match, display the move and the opponent's response.
7023        * If they don't match, display an error message.
7024        */
7025       int saveAnimate;
7026       Board testBoard;
7027       CopyBoard(testBoard, boards[currentMove]);
7028       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7029
7030       if (CompareBoards(testBoard, boards[currentMove+1])) {
7031         ForwardInner(currentMove+1);
7032
7033         /* Autoplay the opponent's response.
7034          * if appData.animate was TRUE when Training mode was entered,
7035          * the response will be animated.
7036          */
7037         saveAnimate = appData.animate;
7038         appData.animate = animateTraining;
7039         ForwardInner(currentMove+1);
7040         appData.animate = saveAnimate;
7041
7042         /* check for the end of the game */
7043         if (currentMove >= forwardMostMove) {
7044           gameMode = PlayFromGameFile;
7045           ModeHighlight();
7046           SetTrainingModeOff();
7047           DisplayInformation(_("End of game"));
7048         }
7049       } else {
7050         DisplayError(_("Incorrect move"), 0);
7051       }
7052       return 1;
7053     }
7054
7055   /* Ok, now we know that the move is good, so we can kill
7056      the previous line in Analysis Mode */
7057   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7058                                 && currentMove < forwardMostMove) {
7059     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7060     else forwardMostMove = currentMove;
7061   }
7062
7063   ClearMap();
7064
7065   /* If we need the chess program but it's dead, restart it */
7066   ResurrectChessProgram();
7067
7068   /* A user move restarts a paused game*/
7069   if (pausing)
7070     PauseEvent();
7071
7072   thinkOutput[0] = NULLCHAR;
7073
7074   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7075
7076   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7077     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7078     return 1;
7079   }
7080
7081   if (gameMode == BeginningOfGame) {
7082     if (appData.noChessProgram) {
7083       gameMode = EditGame;
7084       SetGameInfo();
7085     } else {
7086       char buf[MSG_SIZ];
7087       gameMode = MachinePlaysBlack;
7088       StartClocks();
7089       SetGameInfo();
7090       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7091       DisplayTitle(buf);
7092       if (first.sendName) {
7093         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7094         SendToProgram(buf, &first);
7095       }
7096       StartClocks();
7097     }
7098     ModeHighlight();
7099   }
7100
7101   /* Relay move to ICS or chess engine */
7102   if (appData.icsActive) {
7103     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7104         gameMode == IcsExamining) {
7105       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7106         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7107         SendToICS("draw ");
7108         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7109       }
7110       // also send plain move, in case ICS does not understand atomic claims
7111       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7112       ics_user_moved = 1;
7113     }
7114   } else {
7115     if (first.sendTime && (gameMode == BeginningOfGame ||
7116                            gameMode == MachinePlaysWhite ||
7117                            gameMode == MachinePlaysBlack)) {
7118       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7119     }
7120     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7121          // [HGM] book: if program might be playing, let it use book
7122         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7123         first.maybeThinking = TRUE;
7124     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7125         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7126         SendBoard(&first, currentMove+1);
7127         if(second.analyzing) {
7128             if(!second.useSetboard) SendToProgram("undo\n", &second);
7129             SendBoard(&second, currentMove+1);
7130         }
7131     } else {
7132         SendMoveToProgram(forwardMostMove-1, &first);
7133         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7134     }
7135     if (currentMove == cmailOldMove + 1) {
7136       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7137     }
7138   }
7139
7140   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7141
7142   switch (gameMode) {
7143   case EditGame:
7144     if(appData.testLegality)
7145     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7146     case MT_NONE:
7147     case MT_CHECK:
7148       break;
7149     case MT_CHECKMATE:
7150     case MT_STAINMATE:
7151       if (WhiteOnMove(currentMove)) {
7152         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7153       } else {
7154         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7155       }
7156       break;
7157     case MT_STALEMATE:
7158       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7159       break;
7160     }
7161     break;
7162
7163   case MachinePlaysBlack:
7164   case MachinePlaysWhite:
7165     /* disable certain menu options while machine is thinking */
7166     SetMachineThinkingEnables();
7167     break;
7168
7169   default:
7170     break;
7171   }
7172
7173   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7174   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7175
7176   if(bookHit) { // [HGM] book: simulate book reply
7177         static char bookMove[MSG_SIZ]; // a bit generous?
7178
7179         programStats.nodes = programStats.depth = programStats.time =
7180         programStats.score = programStats.got_only_move = 0;
7181         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7182
7183         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7184         strcat(bookMove, bookHit);
7185         HandleMachineMove(bookMove, &first);
7186   }
7187   return 1;
7188 }
7189
7190 void
7191 MarkByFEN(char *fen)
7192 {
7193         int r, f;
7194         if(!appData.markers || !appData.highlightDragging) return;
7195         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7196         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7197         while(*fen) {
7198             int s = 0;
7199             marker[r][f] = 0;
7200             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7201             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7202             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7203             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7204             if(*fen == 'T') marker[r][f++] = 0; else
7205             if(*fen == 'Y') marker[r][f++] = 1; else
7206             if(*fen == 'G') marker[r][f++] = 3; else
7207             if(*fen == 'B') marker[r][f++] = 4; else
7208             if(*fen == 'C') marker[r][f++] = 5; else
7209             if(*fen == 'M') marker[r][f++] = 6; else
7210             if(*fen == 'W') marker[r][f++] = 7; else
7211             if(*fen == 'D') marker[r][f++] = 8; else
7212             if(*fen == 'R') marker[r][f++] = 2; else {
7213                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7214               f += s; fen -= s>0;
7215             }
7216             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7217             if(r < 0) break;
7218             fen++;
7219         }
7220         DrawPosition(TRUE, NULL);
7221 }
7222
7223 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7224
7225 void
7226 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7227 {
7228     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7229     Markers *m = (Markers *) closure;
7230     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7231         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7232                          || kind == WhiteCapturesEnPassant
7233                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7234     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7235 }
7236
7237 static int hoverSavedValid;
7238
7239 void
7240 MarkTargetSquares (int clear)
7241 {
7242   int x, y, sum=0;
7243   if(clear) { // no reason to ever suppress clearing
7244     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7245     hoverSavedValid = 0;
7246     if(!sum) return; // nothing was cleared,no redraw needed
7247   } else {
7248     int capt = 0;
7249     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7250        !appData.testLegality || gameMode == EditPosition) return;
7251     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7252     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7253       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7254       if(capt)
7255       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7256     }
7257   }
7258   DrawPosition(FALSE, NULL);
7259 }
7260
7261 int
7262 Explode (Board board, int fromX, int fromY, int toX, int toY)
7263 {
7264     if(gameInfo.variant == VariantAtomic &&
7265        (board[toY][toX] != EmptySquare ||                     // capture?
7266         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7267                          board[fromY][fromX] == BlackPawn   )
7268       )) {
7269         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7270         return TRUE;
7271     }
7272     return FALSE;
7273 }
7274
7275 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7276
7277 int
7278 CanPromote (ChessSquare piece, int y)
7279 {
7280         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7281         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7282         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7283         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7284            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7285            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7286          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7287         return (piece == BlackPawn && y <= zone ||
7288                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7289                 piece == BlackLance && y == 1 ||
7290                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7291 }
7292
7293 void
7294 HoverEvent (int xPix, int yPix, int x, int y)
7295 {
7296         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7297         int r, f;
7298         if(!first.highlight) return;
7299         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7300         if(x == oldX && y == oldY) return; // only do something if we enter new square
7301         oldFromX = fromX; oldFromY = fromY;
7302         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7303           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7304             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7305           hoverSavedValid = 1;
7306         } else if(oldX != x || oldY != y) {
7307           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7308           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7309           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7310             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7311           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7312             char buf[MSG_SIZ];
7313             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7314             SendToProgram(buf, &first);
7315           }
7316           oldX = x; oldY = y;
7317 //        SetHighlights(fromX, fromY, x, y);
7318         }
7319 }
7320
7321 void ReportClick(char *action, int x, int y)
7322 {
7323         char buf[MSG_SIZ]; // Inform engine of what user does
7324         int r, f;
7325         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7326           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7327         if(!first.highlight || gameMode == EditPosition) return;
7328         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7329         SendToProgram(buf, &first);
7330 }
7331
7332 void
7333 LeftClick (ClickType clickType, int xPix, int yPix)
7334 {
7335     int x, y;
7336     Boolean saveAnimate;
7337     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7338     char promoChoice = NULLCHAR;
7339     ChessSquare piece;
7340     static TimeMark lastClickTime, prevClickTime;
7341
7342     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7343
7344     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7345
7346     if (clickType == Press) ErrorPopDown();
7347     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7348
7349     x = EventToSquare(xPix, BOARD_WIDTH);
7350     y = EventToSquare(yPix, BOARD_HEIGHT);
7351     if (!flipView && y >= 0) {
7352         y = BOARD_HEIGHT - 1 - y;
7353     }
7354     if (flipView && x >= 0) {
7355         x = BOARD_WIDTH - 1 - x;
7356     }
7357
7358     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7359         defaultPromoChoice = promoSweep;
7360         promoSweep = EmptySquare;   // terminate sweep
7361         promoDefaultAltered = TRUE;
7362         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7363     }
7364
7365     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7366         if(clickType == Release) return; // ignore upclick of click-click destination
7367         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7368         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7369         if(gameInfo.holdingsWidth &&
7370                 (WhiteOnMove(currentMove)
7371                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7372                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7373             // click in right holdings, for determining promotion piece
7374             ChessSquare p = boards[currentMove][y][x];
7375             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7376             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7377             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7378                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7379                 fromX = fromY = -1;
7380                 return;
7381             }
7382         }
7383         DrawPosition(FALSE, boards[currentMove]);
7384         return;
7385     }
7386
7387     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7388     if(clickType == Press
7389             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7390               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7391               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7392         return;
7393
7394     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7395         // could be static click on premove from-square: abort premove
7396         gotPremove = 0;
7397         ClearPremoveHighlights();
7398     }
7399
7400     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7401         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7402
7403     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7404         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7405                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7406         defaultPromoChoice = DefaultPromoChoice(side);
7407     }
7408
7409     autoQueen = appData.alwaysPromoteToQueen;
7410
7411     if (fromX == -1) {
7412       int originalY = y;
7413       gatingPiece = EmptySquare;
7414       if (clickType != Press) {
7415         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7416             DragPieceEnd(xPix, yPix); dragging = 0;
7417             DrawPosition(FALSE, NULL);
7418         }
7419         return;
7420       }
7421       doubleClick = FALSE;
7422       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7423         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7424       }
7425       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7426       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7427          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7428          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7429             /* First square */
7430             if (OKToStartUserMove(fromX, fromY)) {
7431                 second = 0;
7432                 ReportClick("lift", x, y);
7433                 MarkTargetSquares(0);
7434                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7435                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7436                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7437                     promoSweep = defaultPromoChoice;
7438                     selectFlag = 0; lastX = xPix; lastY = yPix;
7439                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7440                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7441                 }
7442                 if (appData.highlightDragging) {
7443                     SetHighlights(fromX, fromY, -1, -1);
7444                 } else {
7445                     ClearHighlights();
7446                 }
7447             } else fromX = fromY = -1;
7448             return;
7449         }
7450     }
7451
7452     /* fromX != -1 */
7453     if (clickType == Press && gameMode != EditPosition) {
7454         ChessSquare fromP;
7455         ChessSquare toP;
7456         int frc;
7457
7458         // ignore off-board to clicks
7459         if(y < 0 || x < 0) return;
7460
7461         /* Check if clicking again on the same color piece */
7462         fromP = boards[currentMove][fromY][fromX];
7463         toP = boards[currentMove][y][x];
7464         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7465         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7466            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7467              WhitePawn <= toP && toP <= WhiteKing &&
7468              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7469              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7470             (BlackPawn <= fromP && fromP <= BlackKing &&
7471              BlackPawn <= toP && toP <= BlackKing &&
7472              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7473              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7474             /* Clicked again on same color piece -- changed his mind */
7475             second = (x == fromX && y == fromY);
7476             killX = killY = -1;
7477             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7478                 second = FALSE; // first double-click rather than scond click
7479                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7480             }
7481             promoDefaultAltered = FALSE;
7482             MarkTargetSquares(1);
7483            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7484             if (appData.highlightDragging) {
7485                 SetHighlights(x, y, -1, -1);
7486             } else {
7487                 ClearHighlights();
7488             }
7489             if (OKToStartUserMove(x, y)) {
7490                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7491                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7492                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7493                  gatingPiece = boards[currentMove][fromY][fromX];
7494                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7495                 fromX = x;
7496                 fromY = y; dragging = 1;
7497                 ReportClick("lift", x, y);
7498                 MarkTargetSquares(0);
7499                 DragPieceBegin(xPix, yPix, FALSE);
7500                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7501                     promoSweep = defaultPromoChoice;
7502                     selectFlag = 0; lastX = xPix; lastY = yPix;
7503                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7504                 }
7505             }
7506            }
7507            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7508            second = FALSE;
7509         }
7510         // ignore clicks on holdings
7511         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7512     }
7513
7514     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7515         DragPieceEnd(xPix, yPix); dragging = 0;
7516         if(clearFlag) {
7517             // a deferred attempt to click-click move an empty square on top of a piece
7518             boards[currentMove][y][x] = EmptySquare;
7519             ClearHighlights();
7520             DrawPosition(FALSE, boards[currentMove]);
7521             fromX = fromY = -1; clearFlag = 0;
7522             return;
7523         }
7524         if (appData.animateDragging) {
7525             /* Undo animation damage if any */
7526             DrawPosition(FALSE, NULL);
7527         }
7528         if (second || sweepSelecting) {
7529             /* Second up/down in same square; just abort move */
7530             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7531             second = sweepSelecting = 0;
7532             fromX = fromY = -1;
7533             gatingPiece = EmptySquare;
7534             MarkTargetSquares(1);
7535             ClearHighlights();
7536             gotPremove = 0;
7537             ClearPremoveHighlights();
7538         } else {
7539             /* First upclick in same square; start click-click mode */
7540             SetHighlights(x, y, -1, -1);
7541         }
7542         return;
7543     }
7544
7545     clearFlag = 0;
7546
7547     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7548         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7549         DisplayMessage(_("only marked squares are legal"),"");
7550         DrawPosition(TRUE, NULL);
7551         return; // ignore to-click
7552     }
7553
7554     /* we now have a different from- and (possibly off-board) to-square */
7555     /* Completed move */
7556     if(!sweepSelecting) {
7557         toX = x;
7558         toY = y;
7559     }
7560
7561     piece = boards[currentMove][fromY][fromX];
7562
7563     saveAnimate = appData.animate;
7564     if (clickType == Press) {
7565         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7566         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7567             // must be Edit Position mode with empty-square selected
7568             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7569             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7570             return;
7571         }
7572         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7573             return;
7574         }
7575         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7576             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7577         } else
7578         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7579         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7580           if(appData.sweepSelect) {
7581             promoSweep = defaultPromoChoice;
7582             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7583             selectFlag = 0; lastX = xPix; lastY = yPix;
7584             Sweep(0); // Pawn that is going to promote: preview promotion piece
7585             sweepSelecting = 1;
7586             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7587             MarkTargetSquares(1);
7588           }
7589           return; // promo popup appears on up-click
7590         }
7591         /* Finish clickclick move */
7592         if (appData.animate || appData.highlightLastMove) {
7593             SetHighlights(fromX, fromY, toX, toY);
7594         } else {
7595             ClearHighlights();
7596         }
7597     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7598         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7599         if (appData.animate || appData.highlightLastMove) {
7600             SetHighlights(fromX, fromY, toX, toY);
7601         } else {
7602             ClearHighlights();
7603         }
7604     } else {
7605 #if 0
7606 // [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
7607         /* Finish drag move */
7608         if (appData.highlightLastMove) {
7609             SetHighlights(fromX, fromY, toX, toY);
7610         } else {
7611             ClearHighlights();
7612         }
7613 #endif
7614         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7615         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7616           dragging *= 2;            // flag button-less dragging if we are dragging
7617           MarkTargetSquares(1);
7618           if(x == killX && y == killY) killX = killY = -1; else {
7619             killX = x; killY = y;     //remeber this square as intermediate
7620             ReportClick("put", x, y); // and inform engine
7621             ReportClick("lift", x, y);
7622             MarkTargetSquares(0);
7623             return;
7624           }
7625         }
7626         DragPieceEnd(xPix, yPix); dragging = 0;
7627         /* Don't animate move and drag both */
7628         appData.animate = FALSE;
7629     }
7630
7631     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7632     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7633         ChessSquare piece = boards[currentMove][fromY][fromX];
7634         if(gameMode == EditPosition && piece != EmptySquare &&
7635            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7636             int n;
7637
7638             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7639                 n = PieceToNumber(piece - (int)BlackPawn);
7640                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7641                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7642                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7643             } else
7644             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7645                 n = PieceToNumber(piece);
7646                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7647                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7648                 boards[currentMove][n][BOARD_WIDTH-2]++;
7649             }
7650             boards[currentMove][fromY][fromX] = EmptySquare;
7651         }
7652         ClearHighlights();
7653         fromX = fromY = -1;
7654         MarkTargetSquares(1);
7655         DrawPosition(TRUE, boards[currentMove]);
7656         return;
7657     }
7658
7659     // off-board moves should not be highlighted
7660     if(x < 0 || y < 0) ClearHighlights();
7661     else ReportClick("put", x, y);
7662
7663     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7664
7665     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7666         SetHighlights(fromX, fromY, toX, toY);
7667         MarkTargetSquares(1);
7668         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7669             // [HGM] super: promotion to captured piece selected from holdings
7670             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7671             promotionChoice = TRUE;
7672             // kludge follows to temporarily execute move on display, without promoting yet
7673             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7674             boards[currentMove][toY][toX] = p;
7675             DrawPosition(FALSE, boards[currentMove]);
7676             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7677             boards[currentMove][toY][toX] = q;
7678             DisplayMessage("Click in holdings to choose piece", "");
7679             return;
7680         }
7681         PromotionPopUp(promoChoice);
7682     } else {
7683         int oldMove = currentMove;
7684         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7685         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7686         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7687         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7688            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7689             DrawPosition(TRUE, boards[currentMove]);
7690         MarkTargetSquares(1);
7691         fromX = fromY = -1;
7692     }
7693     appData.animate = saveAnimate;
7694     if (appData.animate || appData.animateDragging) {
7695         /* Undo animation damage if needed */
7696         DrawPosition(FALSE, NULL);
7697     }
7698 }
7699
7700 int
7701 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7702 {   // front-end-free part taken out of PieceMenuPopup
7703     int whichMenu; int xSqr, ySqr;
7704
7705     if(seekGraphUp) { // [HGM] seekgraph
7706         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7707         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7708         return -2;
7709     }
7710
7711     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7712          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7713         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7714         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7715         if(action == Press)   {
7716             originalFlip = flipView;
7717             flipView = !flipView; // temporarily flip board to see game from partners perspective
7718             DrawPosition(TRUE, partnerBoard);
7719             DisplayMessage(partnerStatus, "");
7720             partnerUp = TRUE;
7721         } else if(action == Release) {
7722             flipView = originalFlip;
7723             DrawPosition(TRUE, boards[currentMove]);
7724             partnerUp = FALSE;
7725         }
7726         return -2;
7727     }
7728
7729     xSqr = EventToSquare(x, BOARD_WIDTH);
7730     ySqr = EventToSquare(y, BOARD_HEIGHT);
7731     if (action == Release) {
7732         if(pieceSweep != EmptySquare) {
7733             EditPositionMenuEvent(pieceSweep, toX, toY);
7734             pieceSweep = EmptySquare;
7735         } else UnLoadPV(); // [HGM] pv
7736     }
7737     if (action != Press) return -2; // return code to be ignored
7738     switch (gameMode) {
7739       case IcsExamining:
7740         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7741       case EditPosition:
7742         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7743         if (xSqr < 0 || ySqr < 0) return -1;
7744         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7745         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7746         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7747         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7748         NextPiece(0);
7749         return 2; // grab
7750       case IcsObserving:
7751         if(!appData.icsEngineAnalyze) return -1;
7752       case IcsPlayingWhite:
7753       case IcsPlayingBlack:
7754         if(!appData.zippyPlay) goto noZip;
7755       case AnalyzeMode:
7756       case AnalyzeFile:
7757       case MachinePlaysWhite:
7758       case MachinePlaysBlack:
7759       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7760         if (!appData.dropMenu) {
7761           LoadPV(x, y);
7762           return 2; // flag front-end to grab mouse events
7763         }
7764         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7765            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7766       case EditGame:
7767       noZip:
7768         if (xSqr < 0 || ySqr < 0) return -1;
7769         if (!appData.dropMenu || appData.testLegality &&
7770             gameInfo.variant != VariantBughouse &&
7771             gameInfo.variant != VariantCrazyhouse) return -1;
7772         whichMenu = 1; // drop menu
7773         break;
7774       default:
7775         return -1;
7776     }
7777
7778     if (((*fromX = xSqr) < 0) ||
7779         ((*fromY = ySqr) < 0)) {
7780         *fromX = *fromY = -1;
7781         return -1;
7782     }
7783     if (flipView)
7784       *fromX = BOARD_WIDTH - 1 - *fromX;
7785     else
7786       *fromY = BOARD_HEIGHT - 1 - *fromY;
7787
7788     return whichMenu;
7789 }
7790
7791 void
7792 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7793 {
7794 //    char * hint = lastHint;
7795     FrontEndProgramStats stats;
7796
7797     stats.which = cps == &first ? 0 : 1;
7798     stats.depth = cpstats->depth;
7799     stats.nodes = cpstats->nodes;
7800     stats.score = cpstats->score;
7801     stats.time = cpstats->time;
7802     stats.pv = cpstats->movelist;
7803     stats.hint = lastHint;
7804     stats.an_move_index = 0;
7805     stats.an_move_count = 0;
7806
7807     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7808         stats.hint = cpstats->move_name;
7809         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7810         stats.an_move_count = cpstats->nr_moves;
7811     }
7812
7813     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
7814
7815     SetProgramStats( &stats );
7816 }
7817
7818 void
7819 ClearEngineOutputPane (int which)
7820 {
7821     static FrontEndProgramStats dummyStats;
7822     dummyStats.which = which;
7823     dummyStats.pv = "#";
7824     SetProgramStats( &dummyStats );
7825 }
7826
7827 #define MAXPLAYERS 500
7828
7829 char *
7830 TourneyStandings (int display)
7831 {
7832     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7833     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7834     char result, *p, *names[MAXPLAYERS];
7835
7836     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7837         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7838     names[0] = p = strdup(appData.participants);
7839     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7840
7841     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7842
7843     while(result = appData.results[nr]) {
7844         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7845         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7846         wScore = bScore = 0;
7847         switch(result) {
7848           case '+': wScore = 2; break;
7849           case '-': bScore = 2; break;
7850           case '=': wScore = bScore = 1; break;
7851           case ' ':
7852           case '*': return strdup("busy"); // tourney not finished
7853         }
7854         score[w] += wScore;
7855         score[b] += bScore;
7856         games[w]++;
7857         games[b]++;
7858         nr++;
7859     }
7860     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7861     for(w=0; w<nPlayers; w++) {
7862         bScore = -1;
7863         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7864         ranking[w] = b; points[w] = bScore; score[b] = -2;
7865     }
7866     p = malloc(nPlayers*34+1);
7867     for(w=0; w<nPlayers && w<display; w++)
7868         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7869     free(names[0]);
7870     return p;
7871 }
7872
7873 void
7874 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7875 {       // count all piece types
7876         int p, f, r;
7877         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7878         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7879         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7880                 p = board[r][f];
7881                 pCnt[p]++;
7882                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7883                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7884                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7885                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7886                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7887                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7888         }
7889 }
7890
7891 int
7892 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7893 {
7894         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7895         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7896
7897         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7898         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7899         if(myPawns == 2 && nMine == 3) // KPP
7900             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7901         if(myPawns == 1 && nMine == 2) // KP
7902             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7903         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7904             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7905         if(myPawns) return FALSE;
7906         if(pCnt[WhiteRook+side])
7907             return pCnt[BlackRook-side] ||
7908                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7909                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7910                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7911         if(pCnt[WhiteCannon+side]) {
7912             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7913             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7914         }
7915         if(pCnt[WhiteKnight+side])
7916             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7917         return FALSE;
7918 }
7919
7920 int
7921 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7922 {
7923         VariantClass v = gameInfo.variant;
7924
7925         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7926         if(v == VariantShatranj) return TRUE; // always winnable through baring
7927         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7928         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7929
7930         if(v == VariantXiangqi) {
7931                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7932
7933                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7934                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7935                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7936                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7937                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7938                 if(stale) // we have at least one last-rank P plus perhaps C
7939                     return majors // KPKX
7940                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7941                 else // KCA*E*
7942                     return pCnt[WhiteFerz+side] // KCAK
7943                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7944                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7945                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7946
7947         } else if(v == VariantKnightmate) {
7948                 if(nMine == 1) return FALSE;
7949                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7950         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7951                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7952
7953                 if(nMine == 1) return FALSE; // bare King
7954                 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
7955                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7956                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7957                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7958                 if(pCnt[WhiteKnight+side])
7959                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7960                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7961                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7962                 if(nBishops)
7963                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7964                 if(pCnt[WhiteAlfil+side])
7965                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7966                 if(pCnt[WhiteWazir+side])
7967                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7968         }
7969
7970         return TRUE;
7971 }
7972
7973 int
7974 CompareWithRights (Board b1, Board b2)
7975 {
7976     int rights = 0;
7977     if(!CompareBoards(b1, b2)) return FALSE;
7978     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7979     /* compare castling rights */
7980     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7981            rights++; /* King lost rights, while rook still had them */
7982     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7983         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7984            rights++; /* but at least one rook lost them */
7985     }
7986     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7987            rights++;
7988     if( b1[CASTLING][5] != NoRights ) {
7989         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7990            rights++;
7991     }
7992     return rights == 0;
7993 }
7994
7995 int
7996 Adjudicate (ChessProgramState *cps)
7997 {       // [HGM] some adjudications useful with buggy engines
7998         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7999         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8000         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8001         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8002         int k, drop, count = 0; static int bare = 1;
8003         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8004         Boolean canAdjudicate = !appData.icsActive;
8005
8006         // most tests only when we understand the game, i.e. legality-checking on
8007             if( appData.testLegality )
8008             {   /* [HGM] Some more adjudications for obstinate engines */
8009                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8010                 static int moveCount = 6;
8011                 ChessMove result;
8012                 char *reason = NULL;
8013
8014                 /* Count what is on board. */
8015                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8016
8017                 /* Some material-based adjudications that have to be made before stalemate test */
8018                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8019                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8020                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8021                      if(canAdjudicate && appData.checkMates) {
8022                          if(engineOpponent)
8023                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8024                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8025                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8026                          return 1;
8027                      }
8028                 }
8029
8030                 /* Bare King in Shatranj (loses) or Losers (wins) */
8031                 if( nrW == 1 || nrB == 1) {
8032                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8033                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8034                      if(canAdjudicate && appData.checkMates) {
8035                          if(engineOpponent)
8036                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8037                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8038                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8039                          return 1;
8040                      }
8041                   } else
8042                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8043                   {    /* bare King */
8044                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8045                         if(canAdjudicate && appData.checkMates) {
8046                             /* but only adjudicate if adjudication enabled */
8047                             if(engineOpponent)
8048                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8049                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8050                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8051                             return 1;
8052                         }
8053                   }
8054                 } else bare = 1;
8055
8056
8057             // don't wait for engine to announce game end if we can judge ourselves
8058             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8059               case MT_CHECK:
8060                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8061                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8062                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8063                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8064                             checkCnt++;
8065                         if(checkCnt >= 2) {
8066                             reason = "Xboard adjudication: 3rd check";
8067                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8068                             break;
8069                         }
8070                     }
8071                 }
8072               case MT_NONE:
8073               default:
8074                 break;
8075               case MT_STEALMATE:
8076               case MT_STALEMATE:
8077               case MT_STAINMATE:
8078                 reason = "Xboard adjudication: Stalemate";
8079                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8080                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8081                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8082                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8083                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8084                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8085                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8086                                                                         EP_CHECKMATE : EP_WINS);
8087                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8088                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8089                 }
8090                 break;
8091               case MT_CHECKMATE:
8092                 reason = "Xboard adjudication: Checkmate";
8093                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8094                 if(gameInfo.variant == VariantShogi) {
8095                     if(forwardMostMove > backwardMostMove
8096                        && moveList[forwardMostMove-1][1] == '@'
8097                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8098                         reason = "XBoard adjudication: pawn-drop mate";
8099                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8100                     }
8101                 }
8102                 break;
8103             }
8104
8105                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8106                     case EP_STALEMATE:
8107                         result = GameIsDrawn; break;
8108                     case EP_CHECKMATE:
8109                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8110                     case EP_WINS:
8111                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8112                     default:
8113                         result = EndOfFile;
8114                 }
8115                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8116                     if(engineOpponent)
8117                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8118                     GameEnds( result, reason, GE_XBOARD );
8119                     return 1;
8120                 }
8121
8122                 /* Next absolutely insufficient mating material. */
8123                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8124                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8125                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8126
8127                      /* always flag draws, for judging claims */
8128                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8129
8130                      if(canAdjudicate && appData.materialDraws) {
8131                          /* but only adjudicate them if adjudication enabled */
8132                          if(engineOpponent) {
8133                            SendToProgram("force\n", engineOpponent); // suppress reply
8134                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8135                          }
8136                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8137                          return 1;
8138                      }
8139                 }
8140
8141                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8142                 if(gameInfo.variant == VariantXiangqi ?
8143                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8144                  : nrW + nrB == 4 &&
8145                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8146                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8147                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8148                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8149                    ) ) {
8150                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8151                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8152                           if(engineOpponent) {
8153                             SendToProgram("force\n", engineOpponent); // suppress reply
8154                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8155                           }
8156                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8157                           return 1;
8158                      }
8159                 } else moveCount = 6;
8160             }
8161
8162         // Repetition draws and 50-move rule can be applied independently of legality testing
8163
8164                 /* Check for rep-draws */
8165                 count = 0;
8166                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8167                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8168                 for(k = forwardMostMove-2;
8169                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8170                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8171                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8172                     k-=2)
8173                 {   int rights=0;
8174                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8175                         /* compare castling rights */
8176                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8177                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8178                                 rights++; /* King lost rights, while rook still had them */
8179                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8180                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8181                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8182                                    rights++; /* but at least one rook lost them */
8183                         }
8184                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8185                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8186                                 rights++;
8187                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8188                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8189                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8190                                    rights++;
8191                         }
8192                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8193                             && appData.drawRepeats > 1) {
8194                              /* adjudicate after user-specified nr of repeats */
8195                              int result = GameIsDrawn;
8196                              char *details = "XBoard adjudication: repetition draw";
8197                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8198                                 // [HGM] xiangqi: check for forbidden perpetuals
8199                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8200                                 for(m=forwardMostMove; m>k; m-=2) {
8201                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8202                                         ourPerpetual = 0; // the current mover did not always check
8203                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8204                                         hisPerpetual = 0; // the opponent did not always check
8205                                 }
8206                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8207                                                                         ourPerpetual, hisPerpetual);
8208                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8209                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8210                                     details = "Xboard adjudication: perpetual checking";
8211                                 } else
8212                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8213                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8214                                 } else
8215                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8216                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8217                                         result = BlackWins;
8218                                         details = "Xboard adjudication: repetition";
8219                                     }
8220                                 } else // it must be XQ
8221                                 // Now check for perpetual chases
8222                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8223                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8224                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8225                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8226                                         static char resdet[MSG_SIZ];
8227                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8228                                         details = resdet;
8229                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8230                                     } else
8231                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8232                                         break; // Abort repetition-checking loop.
8233                                 }
8234                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8235                              }
8236                              if(engineOpponent) {
8237                                SendToProgram("force\n", engineOpponent); // suppress reply
8238                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8239                              }
8240                              GameEnds( result, details, GE_XBOARD );
8241                              return 1;
8242                         }
8243                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8244                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8245                     }
8246                 }
8247
8248                 /* Now we test for 50-move draws. Determine ply count */
8249                 count = forwardMostMove;
8250                 /* look for last irreversble move */
8251                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8252                     count--;
8253                 /* if we hit starting position, add initial plies */
8254                 if( count == backwardMostMove )
8255                     count -= initialRulePlies;
8256                 count = forwardMostMove - count;
8257                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8258                         // adjust reversible move counter for checks in Xiangqi
8259                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8260                         if(i < backwardMostMove) i = backwardMostMove;
8261                         while(i <= forwardMostMove) {
8262                                 lastCheck = inCheck; // check evasion does not count
8263                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8264                                 if(inCheck || lastCheck) count--; // check does not count
8265                                 i++;
8266                         }
8267                 }
8268                 if( count >= 100)
8269                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8270                          /* this is used to judge if draw claims are legal */
8271                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8272                          if(engineOpponent) {
8273                            SendToProgram("force\n", engineOpponent); // suppress reply
8274                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8275                          }
8276                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8277                          return 1;
8278                 }
8279
8280                 /* if draw offer is pending, treat it as a draw claim
8281                  * when draw condition present, to allow engines a way to
8282                  * claim draws before making their move to avoid a race
8283                  * condition occurring after their move
8284                  */
8285                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8286                          char *p = NULL;
8287                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8288                              p = "Draw claim: 50-move rule";
8289                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8290                              p = "Draw claim: 3-fold repetition";
8291                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8292                              p = "Draw claim: insufficient mating material";
8293                          if( p != NULL && canAdjudicate) {
8294                              if(engineOpponent) {
8295                                SendToProgram("force\n", engineOpponent); // suppress reply
8296                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8297                              }
8298                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8299                              return 1;
8300                          }
8301                 }
8302
8303                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8304                     if(engineOpponent) {
8305                       SendToProgram("force\n", engineOpponent); // suppress reply
8306                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8307                     }
8308                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8309                     return 1;
8310                 }
8311         return 0;
8312 }
8313
8314 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8315 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8316 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8317
8318 static int
8319 BitbaseProbe ()
8320 {
8321     int pieces[10], squares[10], cnt=0, r, f, res;
8322     static int loaded;
8323     static PPROBE_EGBB probeBB;
8324     if(!appData.testLegality) return 10;
8325     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8326     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8327     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8328     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8329         ChessSquare piece = boards[forwardMostMove][r][f];
8330         int black = (piece >= BlackPawn);
8331         int type = piece - black*BlackPawn;
8332         if(piece == EmptySquare) continue;
8333         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8334         if(type == WhiteKing) type = WhiteQueen + 1;
8335         type = egbbCode[type];
8336         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8337         pieces[cnt] = type + black*6;
8338         if(++cnt > 5) return 11;
8339     }
8340     pieces[cnt] = squares[cnt] = 0;
8341     // probe EGBB
8342     if(loaded == 2) return 13; // loading failed before
8343     if(loaded == 0) {
8344         loaded = 2; // prepare for failure
8345         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8346         HMODULE lib;
8347         PLOAD_EGBB loadBB;
8348         if(!path) return 13; // no egbb installed
8349         strncpy(buf, path + 8, MSG_SIZ);
8350         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8351         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8352         lib = LoadLibrary(buf);
8353         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8354         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8355         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8356         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8357         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8358         loaded = 1; // success!
8359     }
8360     res = probeBB(forwardMostMove & 1, pieces, squares);
8361     return res > 0 ? 1 : res < 0 ? -1 : 0;
8362 }
8363
8364 char *
8365 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8366 {   // [HGM] book: this routine intercepts moves to simulate book replies
8367     char *bookHit = NULL;
8368
8369     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8370         char buf[MSG_SIZ];
8371         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8372         SendToProgram(buf, cps);
8373     }
8374     //first determine if the incoming move brings opponent into his book
8375     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8376         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8377     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8378     if(bookHit != NULL && !cps->bookSuspend) {
8379         // make sure opponent is not going to reply after receiving move to book position
8380         SendToProgram("force\n", cps);
8381         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8382     }
8383     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8384     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8385     // now arrange restart after book miss
8386     if(bookHit) {
8387         // after a book hit we never send 'go', and the code after the call to this routine
8388         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8389         char buf[MSG_SIZ], *move = bookHit;
8390         if(cps->useSAN) {
8391             int fromX, fromY, toX, toY;
8392             char promoChar;
8393             ChessMove moveType;
8394             move = buf + 30;
8395             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8396                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8397                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8398                                     PosFlags(forwardMostMove),
8399                                     fromY, fromX, toY, toX, promoChar, move);
8400             } else {
8401                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8402                 bookHit = NULL;
8403             }
8404         }
8405         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8406         SendToProgram(buf, cps);
8407         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8408     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8409         SendToProgram("go\n", cps);
8410         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8411     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8412         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8413             SendToProgram("go\n", cps);
8414         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8415     }
8416     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8417 }
8418
8419 int
8420 LoadError (char *errmess, ChessProgramState *cps)
8421 {   // unloads engine and switches back to -ncp mode if it was first
8422     if(cps->initDone) return FALSE;
8423     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8424     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8425     cps->pr = NoProc;
8426     if(cps == &first) {
8427         appData.noChessProgram = TRUE;
8428         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8429         gameMode = BeginningOfGame; ModeHighlight();
8430         SetNCPMode();
8431     }
8432     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8433     DisplayMessage("", ""); // erase waiting message
8434     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8435     return TRUE;
8436 }
8437
8438 char *savedMessage;
8439 ChessProgramState *savedState;
8440 void
8441 DeferredBookMove (void)
8442 {
8443         if(savedState->lastPing != savedState->lastPong)
8444                     ScheduleDelayedEvent(DeferredBookMove, 10);
8445         else
8446         HandleMachineMove(savedMessage, savedState);
8447 }
8448
8449 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8450 static ChessProgramState *stalledEngine;
8451 static char stashedInputMove[MSG_SIZ];
8452
8453 void
8454 HandleMachineMove (char *message, ChessProgramState *cps)
8455 {
8456     static char firstLeg[20];
8457     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8458     char realname[MSG_SIZ];
8459     int fromX, fromY, toX, toY;
8460     ChessMove moveType;
8461     char promoChar, roar;
8462     char *p, *pv=buf1;
8463     int machineWhite, oldError;
8464     char *bookHit;
8465
8466     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8467         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8468         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8469             DisplayError(_("Invalid pairing from pairing engine"), 0);
8470             return;
8471         }
8472         pairingReceived = 1;
8473         NextMatchGame();
8474         return; // Skim the pairing messages here.
8475     }
8476
8477     oldError = cps->userError; cps->userError = 0;
8478
8479 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8480     /*
8481      * Kludge to ignore BEL characters
8482      */
8483     while (*message == '\007') message++;
8484
8485     /*
8486      * [HGM] engine debug message: ignore lines starting with '#' character
8487      */
8488     if(cps->debug && *message == '#') return;
8489
8490     /*
8491      * Look for book output
8492      */
8493     if (cps == &first && bookRequested) {
8494         if (message[0] == '\t' || message[0] == ' ') {
8495             /* Part of the book output is here; append it */
8496             strcat(bookOutput, message);
8497             strcat(bookOutput, "  \n");
8498             return;
8499         } else if (bookOutput[0] != NULLCHAR) {
8500             /* All of book output has arrived; display it */
8501             char *p = bookOutput;
8502             while (*p != NULLCHAR) {
8503                 if (*p == '\t') *p = ' ';
8504                 p++;
8505             }
8506             DisplayInformation(bookOutput);
8507             bookRequested = FALSE;
8508             /* Fall through to parse the current output */
8509         }
8510     }
8511
8512     /*
8513      * Look for machine move.
8514      */
8515     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8516         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8517     {
8518         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8519             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8520             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8521             stalledEngine = cps;
8522             if(appData.ponderNextMove) { // bring opponent out of ponder
8523                 if(gameMode == TwoMachinesPlay) {
8524                     if(cps->other->pause)
8525                         PauseEngine(cps->other);
8526                     else
8527                         SendToProgram("easy\n", cps->other);
8528                 }
8529             }
8530             StopClocks();
8531             return;
8532         }
8533
8534         /* This method is only useful on engines that support ping */
8535         if (cps->lastPing != cps->lastPong) {
8536           if (gameMode == BeginningOfGame) {
8537             /* Extra move from before last new; ignore */
8538             if (appData.debugMode) {
8539                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8540             }
8541           } else {
8542             if (appData.debugMode) {
8543                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8544                         cps->which, gameMode);
8545             }
8546
8547             SendToProgram("undo\n", cps);
8548           }
8549           return;
8550         }
8551
8552         switch (gameMode) {
8553           case BeginningOfGame:
8554             /* Extra move from before last reset; ignore */
8555             if (appData.debugMode) {
8556                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8557             }
8558             return;
8559
8560           case EndOfGame:
8561           case IcsIdle:
8562           default:
8563             /* Extra move after we tried to stop.  The mode test is
8564                not a reliable way of detecting this problem, but it's
8565                the best we can do on engines that don't support ping.
8566             */
8567             if (appData.debugMode) {
8568                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8569                         cps->which, gameMode);
8570             }
8571             SendToProgram("undo\n", cps);
8572             return;
8573
8574           case MachinePlaysWhite:
8575           case IcsPlayingWhite:
8576             machineWhite = TRUE;
8577             break;
8578
8579           case MachinePlaysBlack:
8580           case IcsPlayingBlack:
8581             machineWhite = FALSE;
8582             break;
8583
8584           case TwoMachinesPlay:
8585             machineWhite = (cps->twoMachinesColor[0] == 'w');
8586             break;
8587         }
8588         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8589             if (appData.debugMode) {
8590                 fprintf(debugFP,
8591                         "Ignoring move out of turn by %s, gameMode %d"
8592                         ", forwardMost %d\n",
8593                         cps->which, gameMode, forwardMostMove);
8594             }
8595             return;
8596         }
8597
8598         if(cps->alphaRank) AlphaRank(machineMove, 4);
8599
8600         // [HGM] lion: (some very limited) support for Alien protocol
8601         killX = killY = -1;
8602         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8603             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8604             return;
8605         } else if(firstLeg[0]) { // there was a previous leg;
8606             // only support case where same piece makes two step (and don't even test that!)
8607             char buf[20], *p = machineMove+1, *q = buf+1, f;
8608             safeStrCpy(buf, machineMove, 20);
8609             while(isdigit(*q)) q++; // find start of to-square
8610             safeStrCpy(machineMove, firstLeg, 20);
8611             while(isdigit(*p)) p++;
8612             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8613             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8614             firstLeg[0] = NULLCHAR;
8615         }
8616
8617         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8618                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8619             /* Machine move could not be parsed; ignore it. */
8620           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8621                     machineMove, _(cps->which));
8622             DisplayMoveError(buf1);
8623             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8624                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8625             if (gameMode == TwoMachinesPlay) {
8626               GameEnds(machineWhite ? BlackWins : WhiteWins,
8627                        buf1, GE_XBOARD);
8628             }
8629             return;
8630         }
8631
8632         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8633         /* So we have to redo legality test with true e.p. status here,  */
8634         /* to make sure an illegal e.p. capture does not slip through,   */
8635         /* to cause a forfeit on a justified illegal-move complaint      */
8636         /* of the opponent.                                              */
8637         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8638            ChessMove moveType;
8639            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8640                              fromY, fromX, toY, toX, promoChar);
8641             if(moveType == IllegalMove) {
8642               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8643                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8644                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8645                            buf1, GE_XBOARD);
8646                 return;
8647            } else if(!appData.fischerCastling)
8648            /* [HGM] Kludge to handle engines that send FRC-style castling
8649               when they shouldn't (like TSCP-Gothic) */
8650            switch(moveType) {
8651              case WhiteASideCastleFR:
8652              case BlackASideCastleFR:
8653                toX+=2;
8654                currentMoveString[2]++;
8655                break;
8656              case WhiteHSideCastleFR:
8657              case BlackHSideCastleFR:
8658                toX--;
8659                currentMoveString[2]--;
8660                break;
8661              default: ; // nothing to do, but suppresses warning of pedantic compilers
8662            }
8663         }
8664         hintRequested = FALSE;
8665         lastHint[0] = NULLCHAR;
8666         bookRequested = FALSE;
8667         /* Program may be pondering now */
8668         cps->maybeThinking = TRUE;
8669         if (cps->sendTime == 2) cps->sendTime = 1;
8670         if (cps->offeredDraw) cps->offeredDraw--;
8671
8672         /* [AS] Save move info*/
8673         pvInfoList[ forwardMostMove ].score = programStats.score;
8674         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8675         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8676
8677         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8678
8679         /* Test suites abort the 'game' after one move */
8680         if(*appData.finger) {
8681            static FILE *f;
8682            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8683            if(!f) f = fopen(appData.finger, "w");
8684            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8685            else { DisplayFatalError("Bad output file", errno, 0); return; }
8686            free(fen);
8687            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8688         }
8689
8690         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8691         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8692             int count = 0;
8693
8694             while( count < adjudicateLossPlies ) {
8695                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8696
8697                 if( count & 1 ) {
8698                     score = -score; /* Flip score for winning side */
8699                 }
8700
8701                 if( score > adjudicateLossThreshold ) {
8702                     break;
8703                 }
8704
8705                 count++;
8706             }
8707
8708             if( count >= adjudicateLossPlies ) {
8709                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8710
8711                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8712                     "Xboard adjudication",
8713                     GE_XBOARD );
8714
8715                 return;
8716             }
8717         }
8718
8719         if(Adjudicate(cps)) {
8720             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8721             return; // [HGM] adjudicate: for all automatic game ends
8722         }
8723
8724 #if ZIPPY
8725         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8726             first.initDone) {
8727           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8728                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8729                 SendToICS("draw ");
8730                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8731           }
8732           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8733           ics_user_moved = 1;
8734           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8735                 char buf[3*MSG_SIZ];
8736
8737                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8738                         programStats.score / 100.,
8739                         programStats.depth,
8740                         programStats.time / 100.,
8741                         (unsigned int)programStats.nodes,
8742                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8743                         programStats.movelist);
8744                 SendToICS(buf);
8745 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8746           }
8747         }
8748 #endif
8749
8750         /* [AS] Clear stats for next move */
8751         ClearProgramStats();
8752         thinkOutput[0] = NULLCHAR;
8753         hiddenThinkOutputState = 0;
8754
8755         bookHit = NULL;
8756         if (gameMode == TwoMachinesPlay) {
8757             /* [HGM] relaying draw offers moved to after reception of move */
8758             /* and interpreting offer as claim if it brings draw condition */
8759             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8760                 SendToProgram("draw\n", cps->other);
8761             }
8762             if (cps->other->sendTime) {
8763                 SendTimeRemaining(cps->other,
8764                                   cps->other->twoMachinesColor[0] == 'w');
8765             }
8766             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8767             if (firstMove && !bookHit) {
8768                 firstMove = FALSE;
8769                 if (cps->other->useColors) {
8770                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8771                 }
8772                 SendToProgram("go\n", cps->other);
8773             }
8774             cps->other->maybeThinking = TRUE;
8775         }
8776
8777         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8778
8779         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8780
8781         if (!pausing && appData.ringBellAfterMoves) {
8782             if(!roar) RingBell();
8783         }
8784
8785         /*
8786          * Reenable menu items that were disabled while
8787          * machine was thinking
8788          */
8789         if (gameMode != TwoMachinesPlay)
8790             SetUserThinkingEnables();
8791
8792         // [HGM] book: after book hit opponent has received move and is now in force mode
8793         // force the book reply into it, and then fake that it outputted this move by jumping
8794         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8795         if(bookHit) {
8796                 static char bookMove[MSG_SIZ]; // a bit generous?
8797
8798                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8799                 strcat(bookMove, bookHit);
8800                 message = bookMove;
8801                 cps = cps->other;
8802                 programStats.nodes = programStats.depth = programStats.time =
8803                 programStats.score = programStats.got_only_move = 0;
8804                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8805
8806                 if(cps->lastPing != cps->lastPong) {
8807                     savedMessage = message; // args for deferred call
8808                     savedState = cps;
8809                     ScheduleDelayedEvent(DeferredBookMove, 10);
8810                     return;
8811                 }
8812                 goto FakeBookMove;
8813         }
8814
8815         return;
8816     }
8817
8818     /* Set special modes for chess engines.  Later something general
8819      *  could be added here; for now there is just one kludge feature,
8820      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8821      *  when "xboard" is given as an interactive command.
8822      */
8823     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8824         cps->useSigint = FALSE;
8825         cps->useSigterm = FALSE;
8826     }
8827     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8828       ParseFeatures(message+8, cps);
8829       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8830     }
8831
8832     if (!strncmp(message, "setup ", 6) && 
8833         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8834           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8835                                         ) { // [HGM] allow first engine to define opening position
8836       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8837       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8838       *buf = NULLCHAR;
8839       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8840       if(startedFromSetupPosition) return;
8841       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8842       if(dummy >= 3) {
8843         while(message[s] && message[s++] != ' ');
8844         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8845            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8846             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8847             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8848           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8849           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8850         }
8851       }
8852       ParseFEN(boards[0], &dummy, message+s, FALSE);
8853       DrawPosition(TRUE, boards[0]);
8854       startedFromSetupPosition = TRUE;
8855       return;
8856     }
8857     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8858      * want this, I was asked to put it in, and obliged.
8859      */
8860     if (!strncmp(message, "setboard ", 9)) {
8861         Board initial_position;
8862
8863         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8864
8865         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8866             DisplayError(_("Bad FEN received from engine"), 0);
8867             return ;
8868         } else {
8869            Reset(TRUE, FALSE);
8870            CopyBoard(boards[0], initial_position);
8871            initialRulePlies = FENrulePlies;
8872            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8873            else gameMode = MachinePlaysBlack;
8874            DrawPosition(FALSE, boards[currentMove]);
8875         }
8876         return;
8877     }
8878
8879     /*
8880      * Look for communication commands
8881      */
8882     if (!strncmp(message, "telluser ", 9)) {
8883         if(message[9] == '\\' && message[10] == '\\')
8884             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8885         PlayTellSound();
8886         DisplayNote(message + 9);
8887         return;
8888     }
8889     if (!strncmp(message, "tellusererror ", 14)) {
8890         cps->userError = 1;
8891         if(message[14] == '\\' && message[15] == '\\')
8892             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8893         PlayTellSound();
8894         DisplayError(message + 14, 0);
8895         return;
8896     }
8897     if (!strncmp(message, "tellopponent ", 13)) {
8898       if (appData.icsActive) {
8899         if (loggedOn) {
8900           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8901           SendToICS(buf1);
8902         }
8903       } else {
8904         DisplayNote(message + 13);
8905       }
8906       return;
8907     }
8908     if (!strncmp(message, "tellothers ", 11)) {
8909       if (appData.icsActive) {
8910         if (loggedOn) {
8911           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8912           SendToICS(buf1);
8913         }
8914       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8915       return;
8916     }
8917     if (!strncmp(message, "tellall ", 8)) {
8918       if (appData.icsActive) {
8919         if (loggedOn) {
8920           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8921           SendToICS(buf1);
8922         }
8923       } else {
8924         DisplayNote(message + 8);
8925       }
8926       return;
8927     }
8928     if (strncmp(message, "warning", 7) == 0) {
8929         /* Undocumented feature, use tellusererror in new code */
8930         DisplayError(message, 0);
8931         return;
8932     }
8933     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8934         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8935         strcat(realname, " query");
8936         AskQuestion(realname, buf2, buf1, cps->pr);
8937         return;
8938     }
8939     /* Commands from the engine directly to ICS.  We don't allow these to be
8940      *  sent until we are logged on. Crafty kibitzes have been known to
8941      *  interfere with the login process.
8942      */
8943     if (loggedOn) {
8944         if (!strncmp(message, "tellics ", 8)) {
8945             SendToICS(message + 8);
8946             SendToICS("\n");
8947             return;
8948         }
8949         if (!strncmp(message, "tellicsnoalias ", 15)) {
8950             SendToICS(ics_prefix);
8951             SendToICS(message + 15);
8952             SendToICS("\n");
8953             return;
8954         }
8955         /* The following are for backward compatibility only */
8956         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8957             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8958             SendToICS(ics_prefix);
8959             SendToICS(message);
8960             SendToICS("\n");
8961             return;
8962         }
8963     }
8964     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8965         if(initPing == cps->lastPong) {
8966             if(gameInfo.variant == VariantUnknown) {
8967                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8968                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8969                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8970             }
8971             initPing = -1;
8972         }
8973         return;
8974     }
8975     if(!strncmp(message, "highlight ", 10)) {
8976         if(appData.testLegality && appData.markers) return;
8977         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8978         return;
8979     }
8980     if(!strncmp(message, "click ", 6)) {
8981         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8982         if(appData.testLegality || !appData.oneClick) return;
8983         sscanf(message+6, "%c%d%c", &f, &y, &c);
8984         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8985         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8986         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8987         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8988         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8989         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8990             LeftClick(Release, lastLeftX, lastLeftY);
8991         controlKey  = (c == ',');
8992         LeftClick(Press, x, y);
8993         LeftClick(Release, x, y);
8994         first.highlight = f;
8995         return;
8996     }
8997     /*
8998      * If the move is illegal, cancel it and redraw the board.
8999      * Also deal with other error cases.  Matching is rather loose
9000      * here to accommodate engines written before the spec.
9001      */
9002     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9003         strncmp(message, "Error", 5) == 0) {
9004         if (StrStr(message, "name") ||
9005             StrStr(message, "rating") || StrStr(message, "?") ||
9006             StrStr(message, "result") || StrStr(message, "board") ||
9007             StrStr(message, "bk") || StrStr(message, "computer") ||
9008             StrStr(message, "variant") || StrStr(message, "hint") ||
9009             StrStr(message, "random") || StrStr(message, "depth") ||
9010             StrStr(message, "accepted")) {
9011             return;
9012         }
9013         if (StrStr(message, "protover")) {
9014           /* Program is responding to input, so it's apparently done
9015              initializing, and this error message indicates it is
9016              protocol version 1.  So we don't need to wait any longer
9017              for it to initialize and send feature commands. */
9018           FeatureDone(cps, 1);
9019           cps->protocolVersion = 1;
9020           return;
9021         }
9022         cps->maybeThinking = FALSE;
9023
9024         if (StrStr(message, "draw")) {
9025             /* Program doesn't have "draw" command */
9026             cps->sendDrawOffers = 0;
9027             return;
9028         }
9029         if (cps->sendTime != 1 &&
9030             (StrStr(message, "time") || StrStr(message, "otim"))) {
9031           /* Program apparently doesn't have "time" or "otim" command */
9032           cps->sendTime = 0;
9033           return;
9034         }
9035         if (StrStr(message, "analyze")) {
9036             cps->analysisSupport = FALSE;
9037             cps->analyzing = FALSE;
9038 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9039             EditGameEvent(); // [HGM] try to preserve loaded game
9040             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9041             DisplayError(buf2, 0);
9042             return;
9043         }
9044         if (StrStr(message, "(no matching move)st")) {
9045           /* Special kludge for GNU Chess 4 only */
9046           cps->stKludge = TRUE;
9047           SendTimeControl(cps, movesPerSession, timeControl,
9048                           timeIncrement, appData.searchDepth,
9049                           searchTime);
9050           return;
9051         }
9052         if (StrStr(message, "(no matching move)sd")) {
9053           /* Special kludge for GNU Chess 4 only */
9054           cps->sdKludge = TRUE;
9055           SendTimeControl(cps, movesPerSession, timeControl,
9056                           timeIncrement, appData.searchDepth,
9057                           searchTime);
9058           return;
9059         }
9060         if (!StrStr(message, "llegal")) {
9061             return;
9062         }
9063         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9064             gameMode == IcsIdle) return;
9065         if (forwardMostMove <= backwardMostMove) return;
9066         if (pausing) PauseEvent();
9067       if(appData.forceIllegal) {
9068             // [HGM] illegal: machine refused move; force position after move into it
9069           SendToProgram("force\n", cps);
9070           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9071                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9072                 // when black is to move, while there might be nothing on a2 or black
9073                 // might already have the move. So send the board as if white has the move.
9074                 // But first we must change the stm of the engine, as it refused the last move
9075                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9076                 if(WhiteOnMove(forwardMostMove)) {
9077                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9078                     SendBoard(cps, forwardMostMove); // kludgeless board
9079                 } else {
9080                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9081                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9082                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9083                 }
9084           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9085             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9086                  gameMode == TwoMachinesPlay)
9087               SendToProgram("go\n", cps);
9088             return;
9089       } else
9090         if (gameMode == PlayFromGameFile) {
9091             /* Stop reading this game file */
9092             gameMode = EditGame;
9093             ModeHighlight();
9094         }
9095         /* [HGM] illegal-move claim should forfeit game when Xboard */
9096         /* only passes fully legal moves                            */
9097         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9098             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9099                                 "False illegal-move claim", GE_XBOARD );
9100             return; // do not take back move we tested as valid
9101         }
9102         currentMove = forwardMostMove-1;
9103         DisplayMove(currentMove-1); /* before DisplayMoveError */
9104         SwitchClocks(forwardMostMove-1); // [HGM] race
9105         DisplayBothClocks();
9106         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9107                 parseList[currentMove], _(cps->which));
9108         DisplayMoveError(buf1);
9109         DrawPosition(FALSE, boards[currentMove]);
9110
9111         SetUserThinkingEnables();
9112         return;
9113     }
9114     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9115         /* Program has a broken "time" command that
9116            outputs a string not ending in newline.
9117            Don't use it. */
9118         cps->sendTime = 0;
9119     }
9120
9121     /*
9122      * If chess program startup fails, exit with an error message.
9123      * Attempts to recover here are futile. [HGM] Well, we try anyway
9124      */
9125     if ((StrStr(message, "unknown host") != NULL)
9126         || (StrStr(message, "No remote directory") != NULL)
9127         || (StrStr(message, "not found") != NULL)
9128         || (StrStr(message, "No such file") != NULL)
9129         || (StrStr(message, "can't alloc") != NULL)
9130         || (StrStr(message, "Permission denied") != NULL)) {
9131
9132         cps->maybeThinking = FALSE;
9133         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9134                 _(cps->which), cps->program, cps->host, message);
9135         RemoveInputSource(cps->isr);
9136         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9137             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9138             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9139         }
9140         return;
9141     }
9142
9143     /*
9144      * Look for hint output
9145      */
9146     if (sscanf(message, "Hint: %s", buf1) == 1) {
9147         if (cps == &first && hintRequested) {
9148             hintRequested = FALSE;
9149             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9150                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9151                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9152                                     PosFlags(forwardMostMove),
9153                                     fromY, fromX, toY, toX, promoChar, buf1);
9154                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9155                 DisplayInformation(buf2);
9156             } else {
9157                 /* Hint move could not be parsed!? */
9158               snprintf(buf2, sizeof(buf2),
9159                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9160                         buf1, _(cps->which));
9161                 DisplayError(buf2, 0);
9162             }
9163         } else {
9164           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9165         }
9166         return;
9167     }
9168
9169     /*
9170      * Ignore other messages if game is not in progress
9171      */
9172     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9173         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9174
9175     /*
9176      * look for win, lose, draw, or draw offer
9177      */
9178     if (strncmp(message, "1-0", 3) == 0) {
9179         char *p, *q, *r = "";
9180         p = strchr(message, '{');
9181         if (p) {
9182             q = strchr(p, '}');
9183             if (q) {
9184                 *q = NULLCHAR;
9185                 r = p + 1;
9186             }
9187         }
9188         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9189         return;
9190     } else if (strncmp(message, "0-1", 3) == 0) {
9191         char *p, *q, *r = "";
9192         p = strchr(message, '{');
9193         if (p) {
9194             q = strchr(p, '}');
9195             if (q) {
9196                 *q = NULLCHAR;
9197                 r = p + 1;
9198             }
9199         }
9200         /* Kludge for Arasan 4.1 bug */
9201         if (strcmp(r, "Black resigns") == 0) {
9202             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9203             return;
9204         }
9205         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9206         return;
9207     } else if (strncmp(message, "1/2", 3) == 0) {
9208         char *p, *q, *r = "";
9209         p = strchr(message, '{');
9210         if (p) {
9211             q = strchr(p, '}');
9212             if (q) {
9213                 *q = NULLCHAR;
9214                 r = p + 1;
9215             }
9216         }
9217
9218         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9219         return;
9220
9221     } else if (strncmp(message, "White resign", 12) == 0) {
9222         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9223         return;
9224     } else if (strncmp(message, "Black resign", 12) == 0) {
9225         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9226         return;
9227     } else if (strncmp(message, "White matches", 13) == 0 ||
9228                strncmp(message, "Black matches", 13) == 0   ) {
9229         /* [HGM] ignore GNUShogi noises */
9230         return;
9231     } else if (strncmp(message, "White", 5) == 0 &&
9232                message[5] != '(' &&
9233                StrStr(message, "Black") == NULL) {
9234         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9235         return;
9236     } else if (strncmp(message, "Black", 5) == 0 &&
9237                message[5] != '(') {
9238         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9239         return;
9240     } else if (strcmp(message, "resign") == 0 ||
9241                strcmp(message, "computer resigns") == 0) {
9242         switch (gameMode) {
9243           case MachinePlaysBlack:
9244           case IcsPlayingBlack:
9245             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9246             break;
9247           case MachinePlaysWhite:
9248           case IcsPlayingWhite:
9249             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9250             break;
9251           case TwoMachinesPlay:
9252             if (cps->twoMachinesColor[0] == 'w')
9253               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9254             else
9255               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9256             break;
9257           default:
9258             /* can't happen */
9259             break;
9260         }
9261         return;
9262     } else if (strncmp(message, "opponent mates", 14) == 0) {
9263         switch (gameMode) {
9264           case MachinePlaysBlack:
9265           case IcsPlayingBlack:
9266             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9267             break;
9268           case MachinePlaysWhite:
9269           case IcsPlayingWhite:
9270             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9271             break;
9272           case TwoMachinesPlay:
9273             if (cps->twoMachinesColor[0] == 'w')
9274               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9275             else
9276               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9277             break;
9278           default:
9279             /* can't happen */
9280             break;
9281         }
9282         return;
9283     } else if (strncmp(message, "computer mates", 14) == 0) {
9284         switch (gameMode) {
9285           case MachinePlaysBlack:
9286           case IcsPlayingBlack:
9287             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9288             break;
9289           case MachinePlaysWhite:
9290           case IcsPlayingWhite:
9291             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9292             break;
9293           case TwoMachinesPlay:
9294             if (cps->twoMachinesColor[0] == 'w')
9295               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9296             else
9297               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9298             break;
9299           default:
9300             /* can't happen */
9301             break;
9302         }
9303         return;
9304     } else if (strncmp(message, "checkmate", 9) == 0) {
9305         if (WhiteOnMove(forwardMostMove)) {
9306             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9307         } else {
9308             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9309         }
9310         return;
9311     } else if (strstr(message, "Draw") != NULL ||
9312                strstr(message, "game is a draw") != NULL) {
9313         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9314         return;
9315     } else if (strstr(message, "offer") != NULL &&
9316                strstr(message, "draw") != NULL) {
9317 #if ZIPPY
9318         if (appData.zippyPlay && first.initDone) {
9319             /* Relay offer to ICS */
9320             SendToICS(ics_prefix);
9321             SendToICS("draw\n");
9322         }
9323 #endif
9324         cps->offeredDraw = 2; /* valid until this engine moves twice */
9325         if (gameMode == TwoMachinesPlay) {
9326             if (cps->other->offeredDraw) {
9327                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9328             /* [HGM] in two-machine mode we delay relaying draw offer      */
9329             /* until after we also have move, to see if it is really claim */
9330             }
9331         } else if (gameMode == MachinePlaysWhite ||
9332                    gameMode == MachinePlaysBlack) {
9333           if (userOfferedDraw) {
9334             DisplayInformation(_("Machine accepts your draw offer"));
9335             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9336           } else {
9337             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9338           }
9339         }
9340     }
9341
9342
9343     /*
9344      * Look for thinking output
9345      */
9346     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9347           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9348                                 ) {
9349         int plylev, mvleft, mvtot, curscore, time;
9350         char mvname[MOVE_LEN];
9351         u64 nodes; // [DM]
9352         char plyext;
9353         int ignore = FALSE;
9354         int prefixHint = FALSE;
9355         mvname[0] = NULLCHAR;
9356
9357         switch (gameMode) {
9358           case MachinePlaysBlack:
9359           case IcsPlayingBlack:
9360             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9361             break;
9362           case MachinePlaysWhite:
9363           case IcsPlayingWhite:
9364             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9365             break;
9366           case AnalyzeMode:
9367           case AnalyzeFile:
9368             break;
9369           case IcsObserving: /* [DM] icsEngineAnalyze */
9370             if (!appData.icsEngineAnalyze) ignore = TRUE;
9371             break;
9372           case TwoMachinesPlay:
9373             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9374                 ignore = TRUE;
9375             }
9376             break;
9377           default:
9378             ignore = TRUE;
9379             break;
9380         }
9381
9382         if (!ignore) {
9383             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9384             buf1[0] = NULLCHAR;
9385             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9386                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9387
9388                 if (plyext != ' ' && plyext != '\t') {
9389                     time *= 100;
9390                 }
9391
9392                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9393                 if( cps->scoreIsAbsolute &&
9394                     ( gameMode == MachinePlaysBlack ||
9395                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9396                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9397                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9398                      !WhiteOnMove(currentMove)
9399                     ) )
9400                 {
9401                     curscore = -curscore;
9402                 }
9403
9404                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9405
9406                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9407                         char buf[MSG_SIZ];
9408                         FILE *f;
9409                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9410                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9411                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9412                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9413                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9414                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9415                                 fclose(f);
9416                         }
9417                         else
9418                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9419                           DisplayError(_("failed writing PV"), 0);
9420                 }
9421
9422                 tempStats.depth = plylev;
9423                 tempStats.nodes = nodes;
9424                 tempStats.time = time;
9425                 tempStats.score = curscore;
9426                 tempStats.got_only_move = 0;
9427
9428                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9429                         int ticklen;
9430
9431                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9432                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9433                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9434                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9435                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9436                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9437                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9438                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9439                 }
9440
9441                 /* Buffer overflow protection */
9442                 if (pv[0] != NULLCHAR) {
9443                     if (strlen(pv) >= sizeof(tempStats.movelist)
9444                         && appData.debugMode) {
9445                         fprintf(debugFP,
9446                                 "PV is too long; using the first %u bytes.\n",
9447                                 (unsigned) sizeof(tempStats.movelist) - 1);
9448                     }
9449
9450                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9451                 } else {
9452                     sprintf(tempStats.movelist, " no PV\n");
9453                 }
9454
9455                 if (tempStats.seen_stat) {
9456                     tempStats.ok_to_send = 1;
9457                 }
9458
9459                 if (strchr(tempStats.movelist, '(') != NULL) {
9460                     tempStats.line_is_book = 1;
9461                     tempStats.nr_moves = 0;
9462                     tempStats.moves_left = 0;
9463                 } else {
9464                     tempStats.line_is_book = 0;
9465                 }
9466
9467                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9468                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9469
9470                 SendProgramStatsToFrontend( cps, &tempStats );
9471
9472                 /*
9473                     [AS] Protect the thinkOutput buffer from overflow... this
9474                     is only useful if buf1 hasn't overflowed first!
9475                 */
9476                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9477                          plylev,
9478                          (gameMode == TwoMachinesPlay ?
9479                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9480                          ((double) curscore) / 100.0,
9481                          prefixHint ? lastHint : "",
9482                          prefixHint ? " " : "" );
9483
9484                 if( buf1[0] != NULLCHAR ) {
9485                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9486
9487                     if( strlen(pv) > max_len ) {
9488                         if( appData.debugMode) {
9489                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9490                         }
9491                         pv[max_len+1] = '\0';
9492                     }
9493
9494                     strcat( thinkOutput, pv);
9495                 }
9496
9497                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9498                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9499                     DisplayMove(currentMove - 1);
9500                 }
9501                 return;
9502
9503             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9504                 /* crafty (9.25+) says "(only move) <move>"
9505                  * if there is only 1 legal move
9506                  */
9507                 sscanf(p, "(only move) %s", buf1);
9508                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9509                 sprintf(programStats.movelist, "%s (only move)", buf1);
9510                 programStats.depth = 1;
9511                 programStats.nr_moves = 1;
9512                 programStats.moves_left = 1;
9513                 programStats.nodes = 1;
9514                 programStats.time = 1;
9515                 programStats.got_only_move = 1;
9516
9517                 /* Not really, but we also use this member to
9518                    mean "line isn't going to change" (Crafty
9519                    isn't searching, so stats won't change) */
9520                 programStats.line_is_book = 1;
9521
9522                 SendProgramStatsToFrontend( cps, &programStats );
9523
9524                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9525                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9526                     DisplayMove(currentMove - 1);
9527                 }
9528                 return;
9529             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9530                               &time, &nodes, &plylev, &mvleft,
9531                               &mvtot, mvname) >= 5) {
9532                 /* The stat01: line is from Crafty (9.29+) in response
9533                    to the "." command */
9534                 programStats.seen_stat = 1;
9535                 cps->maybeThinking = TRUE;
9536
9537                 if (programStats.got_only_move || !appData.periodicUpdates)
9538                   return;
9539
9540                 programStats.depth = plylev;
9541                 programStats.time = time;
9542                 programStats.nodes = nodes;
9543                 programStats.moves_left = mvleft;
9544                 programStats.nr_moves = mvtot;
9545                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9546                 programStats.ok_to_send = 1;
9547                 programStats.movelist[0] = '\0';
9548
9549                 SendProgramStatsToFrontend( cps, &programStats );
9550
9551                 return;
9552
9553             } else if (strncmp(message,"++",2) == 0) {
9554                 /* Crafty 9.29+ outputs this */
9555                 programStats.got_fail = 2;
9556                 return;
9557
9558             } else if (strncmp(message,"--",2) == 0) {
9559                 /* Crafty 9.29+ outputs this */
9560                 programStats.got_fail = 1;
9561                 return;
9562
9563             } else if (thinkOutput[0] != NULLCHAR &&
9564                        strncmp(message, "    ", 4) == 0) {
9565                 unsigned message_len;
9566
9567                 p = message;
9568                 while (*p && *p == ' ') p++;
9569
9570                 message_len = strlen( p );
9571
9572                 /* [AS] Avoid buffer overflow */
9573                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9574                     strcat(thinkOutput, " ");
9575                     strcat(thinkOutput, p);
9576                 }
9577
9578                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9579                     strcat(programStats.movelist, " ");
9580                     strcat(programStats.movelist, p);
9581                 }
9582
9583                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9584                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9585                     DisplayMove(currentMove - 1);
9586                 }
9587                 return;
9588             }
9589         }
9590         else {
9591             buf1[0] = NULLCHAR;
9592
9593             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9594                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9595             {
9596                 ChessProgramStats cpstats;
9597
9598                 if (plyext != ' ' && plyext != '\t') {
9599                     time *= 100;
9600                 }
9601
9602                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9603                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9604                     curscore = -curscore;
9605                 }
9606
9607                 cpstats.depth = plylev;
9608                 cpstats.nodes = nodes;
9609                 cpstats.time = time;
9610                 cpstats.score = curscore;
9611                 cpstats.got_only_move = 0;
9612                 cpstats.movelist[0] = '\0';
9613
9614                 if (buf1[0] != NULLCHAR) {
9615                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9616                 }
9617
9618                 cpstats.ok_to_send = 0;
9619                 cpstats.line_is_book = 0;
9620                 cpstats.nr_moves = 0;
9621                 cpstats.moves_left = 0;
9622
9623                 SendProgramStatsToFrontend( cps, &cpstats );
9624             }
9625         }
9626     }
9627 }
9628
9629
9630 /* Parse a game score from the character string "game", and
9631    record it as the history of the current game.  The game
9632    score is NOT assumed to start from the standard position.
9633    The display is not updated in any way.
9634    */
9635 void
9636 ParseGameHistory (char *game)
9637 {
9638     ChessMove moveType;
9639     int fromX, fromY, toX, toY, boardIndex;
9640     char promoChar;
9641     char *p, *q;
9642     char buf[MSG_SIZ];
9643
9644     if (appData.debugMode)
9645       fprintf(debugFP, "Parsing game history: %s\n", game);
9646
9647     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9648     gameInfo.site = StrSave(appData.icsHost);
9649     gameInfo.date = PGNDate();
9650     gameInfo.round = StrSave("-");
9651
9652     /* Parse out names of players */
9653     while (*game == ' ') game++;
9654     p = buf;
9655     while (*game != ' ') *p++ = *game++;
9656     *p = NULLCHAR;
9657     gameInfo.white = StrSave(buf);
9658     while (*game == ' ') game++;
9659     p = buf;
9660     while (*game != ' ' && *game != '\n') *p++ = *game++;
9661     *p = NULLCHAR;
9662     gameInfo.black = StrSave(buf);
9663
9664     /* Parse moves */
9665     boardIndex = blackPlaysFirst ? 1 : 0;
9666     yynewstr(game);
9667     for (;;) {
9668         yyboardindex = boardIndex;
9669         moveType = (ChessMove) Myylex();
9670         switch (moveType) {
9671           case IllegalMove:             /* maybe suicide chess, etc. */
9672   if (appData.debugMode) {
9673     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9674     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9675     setbuf(debugFP, NULL);
9676   }
9677           case WhitePromotion:
9678           case BlackPromotion:
9679           case WhiteNonPromotion:
9680           case BlackNonPromotion:
9681           case NormalMove:
9682           case FirstLeg:
9683           case WhiteCapturesEnPassant:
9684           case BlackCapturesEnPassant:
9685           case WhiteKingSideCastle:
9686           case WhiteQueenSideCastle:
9687           case BlackKingSideCastle:
9688           case BlackQueenSideCastle:
9689           case WhiteKingSideCastleWild:
9690           case WhiteQueenSideCastleWild:
9691           case BlackKingSideCastleWild:
9692           case BlackQueenSideCastleWild:
9693           /* PUSH Fabien */
9694           case WhiteHSideCastleFR:
9695           case WhiteASideCastleFR:
9696           case BlackHSideCastleFR:
9697           case BlackASideCastleFR:
9698           /* POP Fabien */
9699             fromX = currentMoveString[0] - AAA;
9700             fromY = currentMoveString[1] - ONE;
9701             toX = currentMoveString[2] - AAA;
9702             toY = currentMoveString[3] - ONE;
9703             promoChar = currentMoveString[4];
9704             break;
9705           case WhiteDrop:
9706           case BlackDrop:
9707             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9708             fromX = moveType == WhiteDrop ?
9709               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9710             (int) CharToPiece(ToLower(currentMoveString[0]));
9711             fromY = DROP_RANK;
9712             toX = currentMoveString[2] - AAA;
9713             toY = currentMoveString[3] - ONE;
9714             promoChar = NULLCHAR;
9715             break;
9716           case AmbiguousMove:
9717             /* bug? */
9718             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9719   if (appData.debugMode) {
9720     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9721     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9722     setbuf(debugFP, NULL);
9723   }
9724             DisplayError(buf, 0);
9725             return;
9726           case ImpossibleMove:
9727             /* bug? */
9728             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9729   if (appData.debugMode) {
9730     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9731     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9732     setbuf(debugFP, NULL);
9733   }
9734             DisplayError(buf, 0);
9735             return;
9736           case EndOfFile:
9737             if (boardIndex < backwardMostMove) {
9738                 /* Oops, gap.  How did that happen? */
9739                 DisplayError(_("Gap in move list"), 0);
9740                 return;
9741             }
9742             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9743             if (boardIndex > forwardMostMove) {
9744                 forwardMostMove = boardIndex;
9745             }
9746             return;
9747           case ElapsedTime:
9748             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9749                 strcat(parseList[boardIndex-1], " ");
9750                 strcat(parseList[boardIndex-1], yy_text);
9751             }
9752             continue;
9753           case Comment:
9754           case PGNTag:
9755           case NAG:
9756           default:
9757             /* ignore */
9758             continue;
9759           case WhiteWins:
9760           case BlackWins:
9761           case GameIsDrawn:
9762           case GameUnfinished:
9763             if (gameMode == IcsExamining) {
9764                 if (boardIndex < backwardMostMove) {
9765                     /* Oops, gap.  How did that happen? */
9766                     return;
9767                 }
9768                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9769                 return;
9770             }
9771             gameInfo.result = moveType;
9772             p = strchr(yy_text, '{');
9773             if (p == NULL) p = strchr(yy_text, '(');
9774             if (p == NULL) {
9775                 p = yy_text;
9776                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9777             } else {
9778                 q = strchr(p, *p == '{' ? '}' : ')');
9779                 if (q != NULL) *q = NULLCHAR;
9780                 p++;
9781             }
9782             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9783             gameInfo.resultDetails = StrSave(p);
9784             continue;
9785         }
9786         if (boardIndex >= forwardMostMove &&
9787             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9788             backwardMostMove = blackPlaysFirst ? 1 : 0;
9789             return;
9790         }
9791         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9792                                  fromY, fromX, toY, toX, promoChar,
9793                                  parseList[boardIndex]);
9794         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9795         /* currentMoveString is set as a side-effect of yylex */
9796         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9797         strcat(moveList[boardIndex], "\n");
9798         boardIndex++;
9799         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9800         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9801           case MT_NONE:
9802           case MT_STALEMATE:
9803           default:
9804             break;
9805           case MT_CHECK:
9806             if(!IS_SHOGI(gameInfo.variant))
9807                 strcat(parseList[boardIndex - 1], "+");
9808             break;
9809           case MT_CHECKMATE:
9810           case MT_STAINMATE:
9811             strcat(parseList[boardIndex - 1], "#");
9812             break;
9813         }
9814     }
9815 }
9816
9817
9818 /* Apply a move to the given board  */
9819 void
9820 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9821 {
9822   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9823   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9824
9825     /* [HGM] compute & store e.p. status and castling rights for new position */
9826     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9827
9828       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9829       oldEP = (signed char)board[EP_STATUS];
9830       board[EP_STATUS] = EP_NONE;
9831
9832   if (fromY == DROP_RANK) {
9833         /* must be first */
9834         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9835             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9836             return;
9837         }
9838         piece = board[toY][toX] = (ChessSquare) fromX;
9839   } else {
9840 //      ChessSquare victim;
9841       int i;
9842
9843       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9844 //           victim = board[killY][killX],
9845            board[killY][killX] = EmptySquare,
9846            board[EP_STATUS] = EP_CAPTURE;
9847
9848       if( board[toY][toX] != EmptySquare ) {
9849            board[EP_STATUS] = EP_CAPTURE;
9850            if( (fromX != toX || fromY != toY) && // not igui!
9851                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9852                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9853                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9854            }
9855       }
9856
9857       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9858            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9859                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9860       } else
9861       if( board[fromY][fromX] == WhitePawn ) {
9862            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9863                board[EP_STATUS] = EP_PAWN_MOVE;
9864            if( toY-fromY==2) {
9865                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9866                         gameInfo.variant != VariantBerolina || toX < fromX)
9867                       board[EP_STATUS] = toX | berolina;
9868                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9869                         gameInfo.variant != VariantBerolina || toX > fromX)
9870                       board[EP_STATUS] = toX;
9871            }
9872       } else
9873       if( board[fromY][fromX] == BlackPawn ) {
9874            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9875                board[EP_STATUS] = EP_PAWN_MOVE;
9876            if( toY-fromY== -2) {
9877                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9878                         gameInfo.variant != VariantBerolina || toX < fromX)
9879                       board[EP_STATUS] = toX | berolina;
9880                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9881                         gameInfo.variant != VariantBerolina || toX > fromX)
9882                       board[EP_STATUS] = toX;
9883            }
9884        }
9885
9886        for(i=0; i<nrCastlingRights; i++) {
9887            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9888               board[CASTLING][i] == toX   && castlingRank[i] == toY
9889              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9890        }
9891
9892        if(gameInfo.variant == VariantSChess) { // update virginity
9893            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9894            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9895            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9896            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9897        }
9898
9899      if (fromX == toX && fromY == toY) return;
9900
9901      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9902      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9903      if(gameInfo.variant == VariantKnightmate)
9904          king += (int) WhiteUnicorn - (int) WhiteKing;
9905
9906     /* Code added by Tord: */
9907     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9908     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9909         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9910       board[fromY][fromX] = EmptySquare;
9911       board[toY][toX] = EmptySquare;
9912       if((toX > fromX) != (piece == WhiteRook)) {
9913         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9914       } else {
9915         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9916       }
9917     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9918                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9919       board[fromY][fromX] = EmptySquare;
9920       board[toY][toX] = EmptySquare;
9921       if((toX > fromX) != (piece == BlackRook)) {
9922         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9923       } else {
9924         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9925       }
9926     /* End of code added by Tord */
9927
9928     } else if (board[fromY][fromX] == king
9929         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9930         && toY == fromY && toX > fromX+1) {
9931         board[fromY][fromX] = EmptySquare;
9932         board[toY][toX] = king;
9933         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9934         board[fromY][BOARD_RGHT-1] = EmptySquare;
9935     } else if (board[fromY][fromX] == king
9936         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9937                && toY == fromY && toX < fromX-1) {
9938         board[fromY][fromX] = EmptySquare;
9939         board[toY][toX] = king;
9940         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9941         board[fromY][BOARD_LEFT] = EmptySquare;
9942     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9943                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9944                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9945                ) {
9946         /* white pawn promotion */
9947         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9948         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9949             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9950         board[fromY][fromX] = EmptySquare;
9951     } else if ((fromY >= BOARD_HEIGHT>>1)
9952                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9953                && (toX != fromX)
9954                && gameInfo.variant != VariantXiangqi
9955                && gameInfo.variant != VariantBerolina
9956                && (board[fromY][fromX] == WhitePawn)
9957                && (board[toY][toX] == EmptySquare)) {
9958         board[fromY][fromX] = EmptySquare;
9959         board[toY][toX] = WhitePawn;
9960         captured = board[toY - 1][toX];
9961         board[toY - 1][toX] = EmptySquare;
9962     } else if ((fromY == BOARD_HEIGHT-4)
9963                && (toX == fromX)
9964                && gameInfo.variant == VariantBerolina
9965                && (board[fromY][fromX] == WhitePawn)
9966                && (board[toY][toX] == EmptySquare)) {
9967         board[fromY][fromX] = EmptySquare;
9968         board[toY][toX] = WhitePawn;
9969         if(oldEP & EP_BEROLIN_A) {
9970                 captured = board[fromY][fromX-1];
9971                 board[fromY][fromX-1] = EmptySquare;
9972         }else{  captured = board[fromY][fromX+1];
9973                 board[fromY][fromX+1] = EmptySquare;
9974         }
9975     } else if (board[fromY][fromX] == king
9976         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9977                && toY == fromY && toX > fromX+1) {
9978         board[fromY][fromX] = EmptySquare;
9979         board[toY][toX] = king;
9980         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9981         board[fromY][BOARD_RGHT-1] = EmptySquare;
9982     } else if (board[fromY][fromX] == king
9983         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9984                && toY == fromY && toX < fromX-1) {
9985         board[fromY][fromX] = EmptySquare;
9986         board[toY][toX] = king;
9987         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9988         board[fromY][BOARD_LEFT] = EmptySquare;
9989     } else if (fromY == 7 && fromX == 3
9990                && board[fromY][fromX] == BlackKing
9991                && toY == 7 && toX == 5) {
9992         board[fromY][fromX] = EmptySquare;
9993         board[toY][toX] = BlackKing;
9994         board[fromY][7] = EmptySquare;
9995         board[toY][4] = BlackRook;
9996     } else if (fromY == 7 && fromX == 3
9997                && board[fromY][fromX] == BlackKing
9998                && toY == 7 && toX == 1) {
9999         board[fromY][fromX] = EmptySquare;
10000         board[toY][toX] = BlackKing;
10001         board[fromY][0] = EmptySquare;
10002         board[toY][2] = BlackRook;
10003     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10004                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10005                && toY < promoRank && promoChar
10006                ) {
10007         /* black pawn promotion */
10008         board[toY][toX] = CharToPiece(ToLower(promoChar));
10009         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10010             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10011         board[fromY][fromX] = EmptySquare;
10012     } else if ((fromY < BOARD_HEIGHT>>1)
10013                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10014                && (toX != fromX)
10015                && gameInfo.variant != VariantXiangqi
10016                && gameInfo.variant != VariantBerolina
10017                && (board[fromY][fromX] == BlackPawn)
10018                && (board[toY][toX] == EmptySquare)) {
10019         board[fromY][fromX] = EmptySquare;
10020         board[toY][toX] = BlackPawn;
10021         captured = board[toY + 1][toX];
10022         board[toY + 1][toX] = EmptySquare;
10023     } else if ((fromY == 3)
10024                && (toX == fromX)
10025                && gameInfo.variant == VariantBerolina
10026                && (board[fromY][fromX] == BlackPawn)
10027                && (board[toY][toX] == EmptySquare)) {
10028         board[fromY][fromX] = EmptySquare;
10029         board[toY][toX] = BlackPawn;
10030         if(oldEP & EP_BEROLIN_A) {
10031                 captured = board[fromY][fromX-1];
10032                 board[fromY][fromX-1] = EmptySquare;
10033         }else{  captured = board[fromY][fromX+1];
10034                 board[fromY][fromX+1] = EmptySquare;
10035         }
10036     } else {
10037         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10038         board[fromY][fromX] = EmptySquare;
10039         board[toY][toX] = piece;
10040     }
10041   }
10042
10043     if (gameInfo.holdingsWidth != 0) {
10044
10045       /* !!A lot more code needs to be written to support holdings  */
10046       /* [HGM] OK, so I have written it. Holdings are stored in the */
10047       /* penultimate board files, so they are automaticlly stored   */
10048       /* in the game history.                                       */
10049       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10050                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10051         /* Delete from holdings, by decreasing count */
10052         /* and erasing image if necessary            */
10053         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10054         if(p < (int) BlackPawn) { /* white drop */
10055              p -= (int)WhitePawn;
10056                  p = PieceToNumber((ChessSquare)p);
10057              if(p >= gameInfo.holdingsSize) p = 0;
10058              if(--board[p][BOARD_WIDTH-2] <= 0)
10059                   board[p][BOARD_WIDTH-1] = EmptySquare;
10060              if((int)board[p][BOARD_WIDTH-2] < 0)
10061                         board[p][BOARD_WIDTH-2] = 0;
10062         } else {                  /* black drop */
10063              p -= (int)BlackPawn;
10064                  p = PieceToNumber((ChessSquare)p);
10065              if(p >= gameInfo.holdingsSize) p = 0;
10066              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10067                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10068              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10069                         board[BOARD_HEIGHT-1-p][1] = 0;
10070         }
10071       }
10072       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10073           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10074         /* [HGM] holdings: Add to holdings, if holdings exist */
10075         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10076                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10077                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10078         }
10079         p = (int) captured;
10080         if (p >= (int) BlackPawn) {
10081           p -= (int)BlackPawn;
10082           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10083                   /* in Shogi restore piece to its original  first */
10084                   captured = (ChessSquare) (DEMOTED captured);
10085                   p = DEMOTED p;
10086           }
10087           p = PieceToNumber((ChessSquare)p);
10088           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10089           board[p][BOARD_WIDTH-2]++;
10090           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10091         } else {
10092           p -= (int)WhitePawn;
10093           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10094                   captured = (ChessSquare) (DEMOTED captured);
10095                   p = DEMOTED p;
10096           }
10097           p = PieceToNumber((ChessSquare)p);
10098           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10099           board[BOARD_HEIGHT-1-p][1]++;
10100           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10101         }
10102       }
10103     } else if (gameInfo.variant == VariantAtomic) {
10104       if (captured != EmptySquare) {
10105         int y, x;
10106         for (y = toY-1; y <= toY+1; y++) {
10107           for (x = toX-1; x <= toX+1; x++) {
10108             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10109                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10110               board[y][x] = EmptySquare;
10111             }
10112           }
10113         }
10114         board[toY][toX] = EmptySquare;
10115       }
10116     }
10117
10118     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10119         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10120     } else
10121     if(promoChar == '+') {
10122         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10123         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10124         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10125           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10126     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10127         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10128         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10129            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10130         board[toY][toX] = newPiece;
10131     }
10132     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10133                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10134         // [HGM] superchess: take promotion piece out of holdings
10135         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10136         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10137             if(!--board[k][BOARD_WIDTH-2])
10138                 board[k][BOARD_WIDTH-1] = EmptySquare;
10139         } else {
10140             if(!--board[BOARD_HEIGHT-1-k][1])
10141                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10142         }
10143     }
10144 }
10145
10146 /* Updates forwardMostMove */
10147 void
10148 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10149 {
10150     int x = toX, y = toY;
10151     char *s = parseList[forwardMostMove];
10152     ChessSquare p = boards[forwardMostMove][toY][toX];
10153 //    forwardMostMove++; // [HGM] bare: moved downstream
10154
10155     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10156     (void) CoordsToAlgebraic(boards[forwardMostMove],
10157                              PosFlags(forwardMostMove),
10158                              fromY, fromX, y, x, promoChar,
10159                              s);
10160     if(killX >= 0 && killY >= 0)
10161         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10162
10163     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10164         int timeLeft; static int lastLoadFlag=0; int king, piece;
10165         piece = boards[forwardMostMove][fromY][fromX];
10166         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10167         if(gameInfo.variant == VariantKnightmate)
10168             king += (int) WhiteUnicorn - (int) WhiteKing;
10169         if(forwardMostMove == 0) {
10170             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10171                 fprintf(serverMoves, "%s;", UserName());
10172             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10173                 fprintf(serverMoves, "%s;", second.tidy);
10174             fprintf(serverMoves, "%s;", first.tidy);
10175             if(gameMode == MachinePlaysWhite)
10176                 fprintf(serverMoves, "%s;", UserName());
10177             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10178                 fprintf(serverMoves, "%s;", second.tidy);
10179         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10180         lastLoadFlag = loadFlag;
10181         // print base move
10182         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10183         // print castling suffix
10184         if( toY == fromY && piece == king ) {
10185             if(toX-fromX > 1)
10186                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10187             if(fromX-toX >1)
10188                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10189         }
10190         // e.p. suffix
10191         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10192              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10193              boards[forwardMostMove][toY][toX] == EmptySquare
10194              && fromX != toX && fromY != toY)
10195                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10196         // promotion suffix
10197         if(promoChar != NULLCHAR) {
10198             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10199                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10200                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10201             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10202         }
10203         if(!loadFlag) {
10204                 char buf[MOVE_LEN*2], *p; int len;
10205             fprintf(serverMoves, "/%d/%d",
10206                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10207             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10208             else                      timeLeft = blackTimeRemaining/1000;
10209             fprintf(serverMoves, "/%d", timeLeft);
10210                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10211                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10212                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10213                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10214             fprintf(serverMoves, "/%s", buf);
10215         }
10216         fflush(serverMoves);
10217     }
10218
10219     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10220         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10221       return;
10222     }
10223     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10224     if (commentList[forwardMostMove+1] != NULL) {
10225         free(commentList[forwardMostMove+1]);
10226         commentList[forwardMostMove+1] = NULL;
10227     }
10228     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10229     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10230     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10231     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10232     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10233     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10234     adjustedClock = FALSE;
10235     gameInfo.result = GameUnfinished;
10236     if (gameInfo.resultDetails != NULL) {
10237         free(gameInfo.resultDetails);
10238         gameInfo.resultDetails = NULL;
10239     }
10240     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10241                               moveList[forwardMostMove - 1]);
10242     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10243       case MT_NONE:
10244       case MT_STALEMATE:
10245       default:
10246         break;
10247       case MT_CHECK:
10248         if(!IS_SHOGI(gameInfo.variant))
10249             strcat(parseList[forwardMostMove - 1], "+");
10250         break;
10251       case MT_CHECKMATE:
10252       case MT_STAINMATE:
10253         strcat(parseList[forwardMostMove - 1], "#");
10254         break;
10255     }
10256 }
10257
10258 /* Updates currentMove if not pausing */
10259 void
10260 ShowMove (int fromX, int fromY, int toX, int toY)
10261 {
10262     int instant = (gameMode == PlayFromGameFile) ?
10263         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10264     if(appData.noGUI) return;
10265     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10266         if (!instant) {
10267             if (forwardMostMove == currentMove + 1) {
10268                 AnimateMove(boards[forwardMostMove - 1],
10269                             fromX, fromY, toX, toY);
10270             }
10271         }
10272         currentMove = forwardMostMove;
10273     }
10274
10275     killX = killY = -1; // [HGM] lion: used up
10276
10277     if (instant) return;
10278
10279     DisplayMove(currentMove - 1);
10280     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10281             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10282                 SetHighlights(fromX, fromY, toX, toY);
10283             }
10284     }
10285     DrawPosition(FALSE, boards[currentMove]);
10286     DisplayBothClocks();
10287     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10288 }
10289
10290 void
10291 SendEgtPath (ChessProgramState *cps)
10292 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10293         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10294
10295         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10296
10297         while(*p) {
10298             char c, *q = name+1, *r, *s;
10299
10300             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10301             while(*p && *p != ',') *q++ = *p++;
10302             *q++ = ':'; *q = 0;
10303             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10304                 strcmp(name, ",nalimov:") == 0 ) {
10305                 // take nalimov path from the menu-changeable option first, if it is defined
10306               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10307                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10308             } else
10309             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10310                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10311                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10312                 s = r = StrStr(s, ":") + 1; // beginning of path info
10313                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10314                 c = *r; *r = 0;             // temporarily null-terminate path info
10315                     *--q = 0;               // strip of trailig ':' from name
10316                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10317                 *r = c;
10318                 SendToProgram(buf,cps);     // send egtbpath command for this format
10319             }
10320             if(*p == ',') p++; // read away comma to position for next format name
10321         }
10322 }
10323
10324 static int
10325 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10326 {
10327       int width = 8, height = 8, holdings = 0;             // most common sizes
10328       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10329       // correct the deviations default for each variant
10330       if( v == VariantXiangqi ) width = 9,  height = 10;
10331       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10332       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10333       if( v == VariantCapablanca || v == VariantCapaRandom ||
10334           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10335                                 width = 10;
10336       if( v == VariantCourier ) width = 12;
10337       if( v == VariantSuper )                            holdings = 8;
10338       if( v == VariantGreat )   width = 10,              holdings = 8;
10339       if( v == VariantSChess )                           holdings = 7;
10340       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10341       if( v == VariantChuChess) width = 10, height = 10;
10342       if( v == VariantChu )     width = 12, height = 12;
10343       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10344              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10345              holdingsSize >= 0 && holdingsSize != holdings;
10346 }
10347
10348 char variantError[MSG_SIZ];
10349
10350 char *
10351 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10352 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10353       char *p, *variant = VariantName(v);
10354       static char b[MSG_SIZ];
10355       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10356            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10357                                                holdingsSize, variant); // cook up sized variant name
10358            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10359            if(StrStr(list, b) == NULL) {
10360                // specific sized variant not known, check if general sizing allowed
10361                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10362                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10363                             boardWidth, boardHeight, holdingsSize, engine);
10364                    return NULL;
10365                }
10366                /* [HGM] here we really should compare with the maximum supported board size */
10367            }
10368       } else snprintf(b, MSG_SIZ,"%s", variant);
10369       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10370       p = StrStr(list, b);
10371       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10372       if(p == NULL) {
10373           // occurs not at all in list, or only as sub-string
10374           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10375           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10376               int l = strlen(variantError);
10377               char *q;
10378               while(p != list && p[-1] != ',') p--;
10379               q = strchr(p, ',');
10380               if(q) *q = NULLCHAR;
10381               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10382               if(q) *q= ',';
10383           }
10384           return NULL;
10385       }
10386       return b;
10387 }
10388
10389 void
10390 InitChessProgram (ChessProgramState *cps, int setup)
10391 /* setup needed to setup FRC opening position */
10392 {
10393     char buf[MSG_SIZ], *b;
10394     if (appData.noChessProgram) return;
10395     hintRequested = FALSE;
10396     bookRequested = FALSE;
10397
10398     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10399     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10400     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10401     if(cps->memSize) { /* [HGM] memory */
10402       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10403         SendToProgram(buf, cps);
10404     }
10405     SendEgtPath(cps); /* [HGM] EGT */
10406     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10407       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10408         SendToProgram(buf, cps);
10409     }
10410
10411     SendToProgram(cps->initString, cps);
10412     if (gameInfo.variant != VariantNormal &&
10413         gameInfo.variant != VariantLoadable
10414         /* [HGM] also send variant if board size non-standard */
10415         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10416
10417       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10418                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10419       if (b == NULL) {
10420         DisplayFatalError(variantError, 0, 1);
10421         return;
10422       }
10423
10424       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10425       SendToProgram(buf, cps);
10426     }
10427     currentlyInitializedVariant = gameInfo.variant;
10428
10429     /* [HGM] send opening position in FRC to first engine */
10430     if(setup) {
10431           SendToProgram("force\n", cps);
10432           SendBoard(cps, 0);
10433           /* engine is now in force mode! Set flag to wake it up after first move. */
10434           setboardSpoiledMachineBlack = 1;
10435     }
10436
10437     if (cps->sendICS) {
10438       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10439       SendToProgram(buf, cps);
10440     }
10441     cps->maybeThinking = FALSE;
10442     cps->offeredDraw = 0;
10443     if (!appData.icsActive) {
10444         SendTimeControl(cps, movesPerSession, timeControl,
10445                         timeIncrement, appData.searchDepth,
10446                         searchTime);
10447     }
10448     if (appData.showThinking
10449         // [HGM] thinking: four options require thinking output to be sent
10450         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10451                                 ) {
10452         SendToProgram("post\n", cps);
10453     }
10454     SendToProgram("hard\n", cps);
10455     if (!appData.ponderNextMove) {
10456         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10457            it without being sure what state we are in first.  "hard"
10458            is not a toggle, so that one is OK.
10459          */
10460         SendToProgram("easy\n", cps);
10461     }
10462     if (cps->usePing) {
10463       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10464       SendToProgram(buf, cps);
10465     }
10466     cps->initDone = TRUE;
10467     ClearEngineOutputPane(cps == &second);
10468 }
10469
10470
10471 void
10472 ResendOptions (ChessProgramState *cps)
10473 { // send the stored value of the options
10474   int i;
10475   char buf[MSG_SIZ];
10476   Option *opt = cps->option;
10477   for(i=0; i<cps->nrOptions; i++, opt++) {
10478       switch(opt->type) {
10479         case Spin:
10480         case Slider:
10481         case CheckBox:
10482             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10483           break;
10484         case ComboBox:
10485           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10486           break;
10487         default:
10488             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10489           break;
10490         case Button:
10491         case SaveButton:
10492           continue;
10493       }
10494       SendToProgram(buf, cps);
10495   }
10496 }
10497
10498 void
10499 StartChessProgram (ChessProgramState *cps)
10500 {
10501     char buf[MSG_SIZ];
10502     int err;
10503
10504     if (appData.noChessProgram) return;
10505     cps->initDone = FALSE;
10506
10507     if (strcmp(cps->host, "localhost") == 0) {
10508         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10509     } else if (*appData.remoteShell == NULLCHAR) {
10510         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10511     } else {
10512         if (*appData.remoteUser == NULLCHAR) {
10513           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10514                     cps->program);
10515         } else {
10516           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10517                     cps->host, appData.remoteUser, cps->program);
10518         }
10519         err = StartChildProcess(buf, "", &cps->pr);
10520     }
10521
10522     if (err != 0) {
10523       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10524         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10525         if(cps != &first) return;
10526         appData.noChessProgram = TRUE;
10527         ThawUI();
10528         SetNCPMode();
10529 //      DisplayFatalError(buf, err, 1);
10530 //      cps->pr = NoProc;
10531 //      cps->isr = NULL;
10532         return;
10533     }
10534
10535     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10536     if (cps->protocolVersion > 1) {
10537       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10538       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10539         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10540         cps->comboCnt = 0;  //                and values of combo boxes
10541       }
10542       SendToProgram(buf, cps);
10543       if(cps->reload) ResendOptions(cps);
10544     } else {
10545       SendToProgram("xboard\n", cps);
10546     }
10547 }
10548
10549 void
10550 TwoMachinesEventIfReady P((void))
10551 {
10552   static int curMess = 0;
10553   if (first.lastPing != first.lastPong) {
10554     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10555     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10556     return;
10557   }
10558   if (second.lastPing != second.lastPong) {
10559     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10560     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10561     return;
10562   }
10563   DisplayMessage("", ""); curMess = 0;
10564   TwoMachinesEvent();
10565 }
10566
10567 char *
10568 MakeName (char *template)
10569 {
10570     time_t clock;
10571     struct tm *tm;
10572     static char buf[MSG_SIZ];
10573     char *p = buf;
10574     int i;
10575
10576     clock = time((time_t *)NULL);
10577     tm = localtime(&clock);
10578
10579     while(*p++ = *template++) if(p[-1] == '%') {
10580         switch(*template++) {
10581           case 0:   *p = 0; return buf;
10582           case 'Y': i = tm->tm_year+1900; break;
10583           case 'y': i = tm->tm_year-100; break;
10584           case 'M': i = tm->tm_mon+1; break;
10585           case 'd': i = tm->tm_mday; break;
10586           case 'h': i = tm->tm_hour; break;
10587           case 'm': i = tm->tm_min; break;
10588           case 's': i = tm->tm_sec; break;
10589           default:  i = 0;
10590         }
10591         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10592     }
10593     return buf;
10594 }
10595
10596 int
10597 CountPlayers (char *p)
10598 {
10599     int n = 0;
10600     while(p = strchr(p, '\n')) p++, n++; // count participants
10601     return n;
10602 }
10603
10604 FILE *
10605 WriteTourneyFile (char *results, FILE *f)
10606 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10607     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10608     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10609         // create a file with tournament description
10610         fprintf(f, "-participants {%s}\n", appData.participants);
10611         fprintf(f, "-seedBase %d\n", appData.seedBase);
10612         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10613         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10614         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10615         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10616         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10617         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10618         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10619         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10620         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10621         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10622         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10623         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10624         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10625         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10626         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10627         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10628         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10629         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10630         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10631         fprintf(f, "-smpCores %d\n", appData.smpCores);
10632         if(searchTime > 0)
10633                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10634         else {
10635                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10636                 fprintf(f, "-tc %s\n", appData.timeControl);
10637                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10638         }
10639         fprintf(f, "-results \"%s\"\n", results);
10640     }
10641     return f;
10642 }
10643
10644 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10645
10646 void
10647 Substitute (char *participants, int expunge)
10648 {
10649     int i, changed, changes=0, nPlayers=0;
10650     char *p, *q, *r, buf[MSG_SIZ];
10651     if(participants == NULL) return;
10652     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10653     r = p = participants; q = appData.participants;
10654     while(*p && *p == *q) {
10655         if(*p == '\n') r = p+1, nPlayers++;
10656         p++; q++;
10657     }
10658     if(*p) { // difference
10659         while(*p && *p++ != '\n');
10660         while(*q && *q++ != '\n');
10661       changed = nPlayers;
10662         changes = 1 + (strcmp(p, q) != 0);
10663     }
10664     if(changes == 1) { // a single engine mnemonic was changed
10665         q = r; while(*q) nPlayers += (*q++ == '\n');
10666         p = buf; while(*r && (*p = *r++) != '\n') p++;
10667         *p = NULLCHAR;
10668         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10669         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10670         if(mnemonic[i]) { // The substitute is valid
10671             FILE *f;
10672             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10673                 flock(fileno(f), LOCK_EX);
10674                 ParseArgsFromFile(f);
10675                 fseek(f, 0, SEEK_SET);
10676                 FREE(appData.participants); appData.participants = participants;
10677                 if(expunge) { // erase results of replaced engine
10678                     int len = strlen(appData.results), w, b, dummy;
10679                     for(i=0; i<len; i++) {
10680                         Pairing(i, nPlayers, &w, &b, &dummy);
10681                         if((w == changed || b == changed) && appData.results[i] == '*') {
10682                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10683                             fclose(f);
10684                             return;
10685                         }
10686                     }
10687                     for(i=0; i<len; i++) {
10688                         Pairing(i, nPlayers, &w, &b, &dummy);
10689                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10690                     }
10691                 }
10692                 WriteTourneyFile(appData.results, f);
10693                 fclose(f); // release lock
10694                 return;
10695             }
10696         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10697     }
10698     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10699     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10700     free(participants);
10701     return;
10702 }
10703
10704 int
10705 CheckPlayers (char *participants)
10706 {
10707         int i;
10708         char buf[MSG_SIZ], *p;
10709         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10710         while(p = strchr(participants, '\n')) {
10711             *p = NULLCHAR;
10712             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10713             if(!mnemonic[i]) {
10714                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10715                 *p = '\n';
10716                 DisplayError(buf, 0);
10717                 return 1;
10718             }
10719             *p = '\n';
10720             participants = p + 1;
10721         }
10722         return 0;
10723 }
10724
10725 int
10726 CreateTourney (char *name)
10727 {
10728         FILE *f;
10729         if(matchMode && strcmp(name, appData.tourneyFile)) {
10730              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10731         }
10732         if(name[0] == NULLCHAR) {
10733             if(appData.participants[0])
10734                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10735             return 0;
10736         }
10737         f = fopen(name, "r");
10738         if(f) { // file exists
10739             ASSIGN(appData.tourneyFile, name);
10740             ParseArgsFromFile(f); // parse it
10741         } else {
10742             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10743             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10744                 DisplayError(_("Not enough participants"), 0);
10745                 return 0;
10746             }
10747             if(CheckPlayers(appData.participants)) return 0;
10748             ASSIGN(appData.tourneyFile, name);
10749             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10750             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10751         }
10752         fclose(f);
10753         appData.noChessProgram = FALSE;
10754         appData.clockMode = TRUE;
10755         SetGNUMode();
10756         return 1;
10757 }
10758
10759 int
10760 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10761 {
10762     char buf[MSG_SIZ], *p, *q;
10763     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10764     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10765     skip = !all && group[0]; // if group requested, we start in skip mode
10766     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10767         p = names; q = buf; header = 0;
10768         while(*p && *p != '\n') *q++ = *p++;
10769         *q = 0;
10770         if(*p == '\n') p++;
10771         if(buf[0] == '#') {
10772             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10773             depth++; // we must be entering a new group
10774             if(all) continue; // suppress printing group headers when complete list requested
10775             header = 1;
10776             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10777         }
10778         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10779         if(engineList[i]) free(engineList[i]);
10780         engineList[i] = strdup(buf);
10781         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10782         if(engineMnemonic[i]) free(engineMnemonic[i]);
10783         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10784             strcat(buf, " (");
10785             sscanf(q + 8, "%s", buf + strlen(buf));
10786             strcat(buf, ")");
10787         }
10788         engineMnemonic[i] = strdup(buf);
10789         i++;
10790     }
10791     engineList[i] = engineMnemonic[i] = NULL;
10792     return i;
10793 }
10794
10795 // following implemented as macro to avoid type limitations
10796 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10797
10798 void
10799 SwapEngines (int n)
10800 {   // swap settings for first engine and other engine (so far only some selected options)
10801     int h;
10802     char *p;
10803     if(n == 0) return;
10804     SWAP(directory, p)
10805     SWAP(chessProgram, p)
10806     SWAP(isUCI, h)
10807     SWAP(hasOwnBookUCI, h)
10808     SWAP(protocolVersion, h)
10809     SWAP(reuse, h)
10810     SWAP(scoreIsAbsolute, h)
10811     SWAP(timeOdds, h)
10812     SWAP(logo, p)
10813     SWAP(pgnName, p)
10814     SWAP(pvSAN, h)
10815     SWAP(engOptions, p)
10816     SWAP(engInitString, p)
10817     SWAP(computerString, p)
10818     SWAP(features, p)
10819     SWAP(fenOverride, p)
10820     SWAP(NPS, h)
10821     SWAP(accumulateTC, h)
10822     SWAP(drawDepth, h)
10823     SWAP(host, p)
10824 }
10825
10826 int
10827 GetEngineLine (char *s, int n)
10828 {
10829     int i;
10830     char buf[MSG_SIZ];
10831     extern char *icsNames;
10832     if(!s || !*s) return 0;
10833     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10834     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10835     if(!mnemonic[i]) return 0;
10836     if(n == 11) return 1; // just testing if there was a match
10837     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10838     if(n == 1) SwapEngines(n);
10839     ParseArgsFromString(buf);
10840     if(n == 1) SwapEngines(n);
10841     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10842         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10843         ParseArgsFromString(buf);
10844     }
10845     return 1;
10846 }
10847
10848 int
10849 SetPlayer (int player, char *p)
10850 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10851     int i;
10852     char buf[MSG_SIZ], *engineName;
10853     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10854     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10855     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10856     if(mnemonic[i]) {
10857         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10858         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10859         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10860         ParseArgsFromString(buf);
10861     } else { // no engine with this nickname is installed!
10862         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10863         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10864         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10865         ModeHighlight();
10866         DisplayError(buf, 0);
10867         return 0;
10868     }
10869     free(engineName);
10870     return i;
10871 }
10872
10873 char *recentEngines;
10874
10875 void
10876 RecentEngineEvent (int nr)
10877 {
10878     int n;
10879 //    SwapEngines(1); // bump first to second
10880 //    ReplaceEngine(&second, 1); // and load it there
10881     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10882     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10883     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10884         ReplaceEngine(&first, 0);
10885         FloatToFront(&appData.recentEngineList, command[n]);
10886     }
10887 }
10888
10889 int
10890 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10891 {   // determine players from game number
10892     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10893
10894     if(appData.tourneyType == 0) {
10895         roundsPerCycle = (nPlayers - 1) | 1;
10896         pairingsPerRound = nPlayers / 2;
10897     } else if(appData.tourneyType > 0) {
10898         roundsPerCycle = nPlayers - appData.tourneyType;
10899         pairingsPerRound = appData.tourneyType;
10900     }
10901     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10902     gamesPerCycle = gamesPerRound * roundsPerCycle;
10903     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10904     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10905     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10906     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10907     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10908     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10909
10910     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10911     if(appData.roundSync) *syncInterval = gamesPerRound;
10912
10913     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10914
10915     if(appData.tourneyType == 0) {
10916         if(curPairing == (nPlayers-1)/2 ) {
10917             *whitePlayer = curRound;
10918             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10919         } else {
10920             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10921             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10922             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10923             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10924         }
10925     } else if(appData.tourneyType > 1) {
10926         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10927         *whitePlayer = curRound + appData.tourneyType;
10928     } else if(appData.tourneyType > 0) {
10929         *whitePlayer = curPairing;
10930         *blackPlayer = curRound + appData.tourneyType;
10931     }
10932
10933     // take care of white/black alternation per round.
10934     // For cycles and games this is already taken care of by default, derived from matchGame!
10935     return curRound & 1;
10936 }
10937
10938 int
10939 NextTourneyGame (int nr, int *swapColors)
10940 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10941     char *p, *q;
10942     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10943     FILE *tf;
10944     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10945     tf = fopen(appData.tourneyFile, "r");
10946     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10947     ParseArgsFromFile(tf); fclose(tf);
10948     InitTimeControls(); // TC might be altered from tourney file
10949
10950     nPlayers = CountPlayers(appData.participants); // count participants
10951     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10952     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10953
10954     if(syncInterval) {
10955         p = q = appData.results;
10956         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10957         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10958             DisplayMessage(_("Waiting for other game(s)"),"");
10959             waitingForGame = TRUE;
10960             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10961             return 0;
10962         }
10963         waitingForGame = FALSE;
10964     }
10965
10966     if(appData.tourneyType < 0) {
10967         if(nr>=0 && !pairingReceived) {
10968             char buf[1<<16];
10969             if(pairing.pr == NoProc) {
10970                 if(!appData.pairingEngine[0]) {
10971                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10972                     return 0;
10973                 }
10974                 StartChessProgram(&pairing); // starts the pairing engine
10975             }
10976             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10977             SendToProgram(buf, &pairing);
10978             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10979             SendToProgram(buf, &pairing);
10980             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10981         }
10982         pairingReceived = 0;                              // ... so we continue here
10983         *swapColors = 0;
10984         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10985         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10986         matchGame = 1; roundNr = nr / syncInterval + 1;
10987     }
10988
10989     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10990
10991     // redefine engines, engine dir, etc.
10992     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10993     if(first.pr == NoProc) {
10994       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10995       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10996     }
10997     if(second.pr == NoProc) {
10998       SwapEngines(1);
10999       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11000       SwapEngines(1);         // and make that valid for second engine by swapping
11001       InitEngine(&second, 1);
11002     }
11003     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11004     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11005     return OK;
11006 }
11007
11008 void
11009 NextMatchGame ()
11010 {   // performs game initialization that does not invoke engines, and then tries to start the game
11011     int res, firstWhite, swapColors = 0;
11012     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11013     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
11014         char buf[MSG_SIZ];
11015         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11016         if(strcmp(buf, currentDebugFile)) { // name has changed
11017             FILE *f = fopen(buf, "w");
11018             if(f) { // if opening the new file failed, just keep using the old one
11019                 ASSIGN(currentDebugFile, buf);
11020                 fclose(debugFP);
11021                 debugFP = f;
11022             }
11023             if(appData.serverFileName) {
11024                 if(serverFP) fclose(serverFP);
11025                 serverFP = fopen(appData.serverFileName, "w");
11026                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11027                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11028             }
11029         }
11030     }
11031     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11032     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11033     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11034     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11035     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11036     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11037     Reset(FALSE, first.pr != NoProc);
11038     res = LoadGameOrPosition(matchGame); // setup game
11039     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11040     if(!res) return; // abort when bad game/pos file
11041     TwoMachinesEvent();
11042 }
11043
11044 void
11045 UserAdjudicationEvent (int result)
11046 {
11047     ChessMove gameResult = GameIsDrawn;
11048
11049     if( result > 0 ) {
11050         gameResult = WhiteWins;
11051     }
11052     else if( result < 0 ) {
11053         gameResult = BlackWins;
11054     }
11055
11056     if( gameMode == TwoMachinesPlay ) {
11057         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11058     }
11059 }
11060
11061
11062 // [HGM] save: calculate checksum of game to make games easily identifiable
11063 int
11064 StringCheckSum (char *s)
11065 {
11066         int i = 0;
11067         if(s==NULL) return 0;
11068         while(*s) i = i*259 + *s++;
11069         return i;
11070 }
11071
11072 int
11073 GameCheckSum ()
11074 {
11075         int i, sum=0;
11076         for(i=backwardMostMove; i<forwardMostMove; i++) {
11077                 sum += pvInfoList[i].depth;
11078                 sum += StringCheckSum(parseList[i]);
11079                 sum += StringCheckSum(commentList[i]);
11080                 sum *= 261;
11081         }
11082         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11083         return sum + StringCheckSum(commentList[i]);
11084 } // end of save patch
11085
11086 void
11087 GameEnds (ChessMove result, char *resultDetails, int whosays)
11088 {
11089     GameMode nextGameMode;
11090     int isIcsGame;
11091     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11092
11093     if(endingGame) return; /* [HGM] crash: forbid recursion */
11094     endingGame = 1;
11095     if(twoBoards) { // [HGM] dual: switch back to one board
11096         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11097         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11098     }
11099     if (appData.debugMode) {
11100       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11101               result, resultDetails ? resultDetails : "(null)", whosays);
11102     }
11103
11104     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11105
11106     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11107
11108     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11109         /* If we are playing on ICS, the server decides when the
11110            game is over, but the engine can offer to draw, claim
11111            a draw, or resign.
11112          */
11113 #if ZIPPY
11114         if (appData.zippyPlay && first.initDone) {
11115             if (result == GameIsDrawn) {
11116                 /* In case draw still needs to be claimed */
11117                 SendToICS(ics_prefix);
11118                 SendToICS("draw\n");
11119             } else if (StrCaseStr(resultDetails, "resign")) {
11120                 SendToICS(ics_prefix);
11121                 SendToICS("resign\n");
11122             }
11123         }
11124 #endif
11125         endingGame = 0; /* [HGM] crash */
11126         return;
11127     }
11128
11129     /* If we're loading the game from a file, stop */
11130     if (whosays == GE_FILE) {
11131       (void) StopLoadGameTimer();
11132       gameFileFP = NULL;
11133     }
11134
11135     /* Cancel draw offers */
11136     first.offeredDraw = second.offeredDraw = 0;
11137
11138     /* If this is an ICS game, only ICS can really say it's done;
11139        if not, anyone can. */
11140     isIcsGame = (gameMode == IcsPlayingWhite ||
11141                  gameMode == IcsPlayingBlack ||
11142                  gameMode == IcsObserving    ||
11143                  gameMode == IcsExamining);
11144
11145     if (!isIcsGame || whosays == GE_ICS) {
11146         /* OK -- not an ICS game, or ICS said it was done */
11147         StopClocks();
11148         if (!isIcsGame && !appData.noChessProgram)
11149           SetUserThinkingEnables();
11150
11151         /* [HGM] if a machine claims the game end we verify this claim */
11152         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11153             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11154                 char claimer;
11155                 ChessMove trueResult = (ChessMove) -1;
11156
11157                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11158                                             first.twoMachinesColor[0] :
11159                                             second.twoMachinesColor[0] ;
11160
11161                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11162                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11163                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11164                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11165                 } else
11166                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11167                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11168                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11169                 } else
11170                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11171                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11172                 }
11173
11174                 // now verify win claims, but not in drop games, as we don't understand those yet
11175                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11176                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11177                     (result == WhiteWins && claimer == 'w' ||
11178                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11179                       if (appData.debugMode) {
11180                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11181                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11182                       }
11183                       if(result != trueResult) {
11184                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11185                               result = claimer == 'w' ? BlackWins : WhiteWins;
11186                               resultDetails = buf;
11187                       }
11188                 } else
11189                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11190                     && (forwardMostMove <= backwardMostMove ||
11191                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11192                         (claimer=='b')==(forwardMostMove&1))
11193                                                                                   ) {
11194                       /* [HGM] verify: draws that were not flagged are false claims */
11195                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11196                       result = claimer == 'w' ? BlackWins : WhiteWins;
11197                       resultDetails = buf;
11198                 }
11199                 /* (Claiming a loss is accepted no questions asked!) */
11200             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11201                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11202                 result = GameUnfinished;
11203                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11204             }
11205             /* [HGM] bare: don't allow bare King to win */
11206             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11207                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11208                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11209                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11210                && result != GameIsDrawn)
11211             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11212                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11213                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11214                         if(p >= 0 && p <= (int)WhiteKing) k++;
11215                 }
11216                 if (appData.debugMode) {
11217                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11218                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11219                 }
11220                 if(k <= 1) {
11221                         result = GameIsDrawn;
11222                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11223                         resultDetails = buf;
11224                 }
11225             }
11226         }
11227
11228
11229         if(serverMoves != NULL && !loadFlag) { char c = '=';
11230             if(result==WhiteWins) c = '+';
11231             if(result==BlackWins) c = '-';
11232             if(resultDetails != NULL)
11233                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11234         }
11235         if (resultDetails != NULL) {
11236             gameInfo.result = result;
11237             gameInfo.resultDetails = StrSave(resultDetails);
11238
11239             /* display last move only if game was not loaded from file */
11240             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11241                 DisplayMove(currentMove - 1);
11242
11243             if (forwardMostMove != 0) {
11244                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11245                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11246                                                                 ) {
11247                     if (*appData.saveGameFile != NULLCHAR) {
11248                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11249                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11250                         else
11251                         SaveGameToFile(appData.saveGameFile, TRUE);
11252                     } else if (appData.autoSaveGames) {
11253                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11254                     }
11255                     if (*appData.savePositionFile != NULLCHAR) {
11256                         SavePositionToFile(appData.savePositionFile);
11257                     }
11258                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11259                 }
11260             }
11261
11262             /* Tell program how game ended in case it is learning */
11263             /* [HGM] Moved this to after saving the PGN, just in case */
11264             /* engine died and we got here through time loss. In that */
11265             /* case we will get a fatal error writing the pipe, which */
11266             /* would otherwise lose us the PGN.                       */
11267             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11268             /* output during GameEnds should never be fatal anymore   */
11269             if (gameMode == MachinePlaysWhite ||
11270                 gameMode == MachinePlaysBlack ||
11271                 gameMode == TwoMachinesPlay ||
11272                 gameMode == IcsPlayingWhite ||
11273                 gameMode == IcsPlayingBlack ||
11274                 gameMode == BeginningOfGame) {
11275                 char buf[MSG_SIZ];
11276                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11277                         resultDetails);
11278                 if (first.pr != NoProc) {
11279                     SendToProgram(buf, &first);
11280                 }
11281                 if (second.pr != NoProc &&
11282                     gameMode == TwoMachinesPlay) {
11283                     SendToProgram(buf, &second);
11284                 }
11285             }
11286         }
11287
11288         if (appData.icsActive) {
11289             if (appData.quietPlay &&
11290                 (gameMode == IcsPlayingWhite ||
11291                  gameMode == IcsPlayingBlack)) {
11292                 SendToICS(ics_prefix);
11293                 SendToICS("set shout 1\n");
11294             }
11295             nextGameMode = IcsIdle;
11296             ics_user_moved = FALSE;
11297             /* clean up premove.  It's ugly when the game has ended and the
11298              * premove highlights are still on the board.
11299              */
11300             if (gotPremove) {
11301               gotPremove = FALSE;
11302               ClearPremoveHighlights();
11303               DrawPosition(FALSE, boards[currentMove]);
11304             }
11305             if (whosays == GE_ICS) {
11306                 switch (result) {
11307                 case WhiteWins:
11308                     if (gameMode == IcsPlayingWhite)
11309                         PlayIcsWinSound();
11310                     else if(gameMode == IcsPlayingBlack)
11311                         PlayIcsLossSound();
11312                     break;
11313                 case BlackWins:
11314                     if (gameMode == IcsPlayingBlack)
11315                         PlayIcsWinSound();
11316                     else if(gameMode == IcsPlayingWhite)
11317                         PlayIcsLossSound();
11318                     break;
11319                 case GameIsDrawn:
11320                     PlayIcsDrawSound();
11321                     break;
11322                 default:
11323                     PlayIcsUnfinishedSound();
11324                 }
11325             }
11326             if(appData.quitNext) { ExitEvent(0); return; }
11327         } else if (gameMode == EditGame ||
11328                    gameMode == PlayFromGameFile ||
11329                    gameMode == AnalyzeMode ||
11330                    gameMode == AnalyzeFile) {
11331             nextGameMode = gameMode;
11332         } else {
11333             nextGameMode = EndOfGame;
11334         }
11335         pausing = FALSE;
11336         ModeHighlight();
11337     } else {
11338         nextGameMode = gameMode;
11339     }
11340
11341     if (appData.noChessProgram) {
11342         gameMode = nextGameMode;
11343         ModeHighlight();
11344         endingGame = 0; /* [HGM] crash */
11345         return;
11346     }
11347
11348     if (first.reuse) {
11349         /* Put first chess program into idle state */
11350         if (first.pr != NoProc &&
11351             (gameMode == MachinePlaysWhite ||
11352              gameMode == MachinePlaysBlack ||
11353              gameMode == TwoMachinesPlay ||
11354              gameMode == IcsPlayingWhite ||
11355              gameMode == IcsPlayingBlack ||
11356              gameMode == BeginningOfGame)) {
11357             SendToProgram("force\n", &first);
11358             if (first.usePing) {
11359               char buf[MSG_SIZ];
11360               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11361               SendToProgram(buf, &first);
11362             }
11363         }
11364     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11365         /* Kill off first chess program */
11366         if (first.isr != NULL)
11367           RemoveInputSource(first.isr);
11368         first.isr = NULL;
11369
11370         if (first.pr != NoProc) {
11371             ExitAnalyzeMode();
11372             DoSleep( appData.delayBeforeQuit );
11373             SendToProgram("quit\n", &first);
11374             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11375             first.reload = TRUE;
11376         }
11377         first.pr = NoProc;
11378     }
11379     if (second.reuse) {
11380         /* Put second chess program into idle state */
11381         if (second.pr != NoProc &&
11382             gameMode == TwoMachinesPlay) {
11383             SendToProgram("force\n", &second);
11384             if (second.usePing) {
11385               char buf[MSG_SIZ];
11386               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11387               SendToProgram(buf, &second);
11388             }
11389         }
11390     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11391         /* Kill off second chess program */
11392         if (second.isr != NULL)
11393           RemoveInputSource(second.isr);
11394         second.isr = NULL;
11395
11396         if (second.pr != NoProc) {
11397             DoSleep( appData.delayBeforeQuit );
11398             SendToProgram("quit\n", &second);
11399             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11400             second.reload = TRUE;
11401         }
11402         second.pr = NoProc;
11403     }
11404
11405     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11406         char resChar = '=';
11407         switch (result) {
11408         case WhiteWins:
11409           resChar = '+';
11410           if (first.twoMachinesColor[0] == 'w') {
11411             first.matchWins++;
11412           } else {
11413             second.matchWins++;
11414           }
11415           break;
11416         case BlackWins:
11417           resChar = '-';
11418           if (first.twoMachinesColor[0] == 'b') {
11419             first.matchWins++;
11420           } else {
11421             second.matchWins++;
11422           }
11423           break;
11424         case GameUnfinished:
11425           resChar = ' ';
11426         default:
11427           break;
11428         }
11429
11430         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11431         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11432             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11433             ReserveGame(nextGame, resChar); // sets nextGame
11434             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11435             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11436         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11437
11438         if (nextGame <= appData.matchGames && !abortMatch) {
11439             gameMode = nextGameMode;
11440             matchGame = nextGame; // this will be overruled in tourney mode!
11441             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11442             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11443             endingGame = 0; /* [HGM] crash */
11444             return;
11445         } else {
11446             gameMode = nextGameMode;
11447             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11448                      first.tidy, second.tidy,
11449                      first.matchWins, second.matchWins,
11450                      appData.matchGames - (first.matchWins + second.matchWins));
11451             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11452             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11453             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11454             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11455                 first.twoMachinesColor = "black\n";
11456                 second.twoMachinesColor = "white\n";
11457             } else {
11458                 first.twoMachinesColor = "white\n";
11459                 second.twoMachinesColor = "black\n";
11460             }
11461         }
11462     }
11463     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11464         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11465       ExitAnalyzeMode();
11466     gameMode = nextGameMode;
11467     ModeHighlight();
11468     endingGame = 0;  /* [HGM] crash */
11469     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11470         if(matchMode == TRUE) { // match through command line: exit with or without popup
11471             if(ranking) {
11472                 ToNrEvent(forwardMostMove);
11473                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11474                 else ExitEvent(0);
11475             } else DisplayFatalError(buf, 0, 0);
11476         } else { // match through menu; just stop, with or without popup
11477             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11478             ModeHighlight();
11479             if(ranking){
11480                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11481             } else DisplayNote(buf);
11482       }
11483       if(ranking) free(ranking);
11484     }
11485 }
11486
11487 /* Assumes program was just initialized (initString sent).
11488    Leaves program in force mode. */
11489 void
11490 FeedMovesToProgram (ChessProgramState *cps, int upto)
11491 {
11492     int i;
11493
11494     if (appData.debugMode)
11495       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11496               startedFromSetupPosition ? "position and " : "",
11497               backwardMostMove, upto, cps->which);
11498     if(currentlyInitializedVariant != gameInfo.variant) {
11499       char buf[MSG_SIZ];
11500         // [HGM] variantswitch: make engine aware of new variant
11501         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11502                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11503                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11504         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11505         SendToProgram(buf, cps);
11506         currentlyInitializedVariant = gameInfo.variant;
11507     }
11508     SendToProgram("force\n", cps);
11509     if (startedFromSetupPosition) {
11510         SendBoard(cps, backwardMostMove);
11511     if (appData.debugMode) {
11512         fprintf(debugFP, "feedMoves\n");
11513     }
11514     }
11515     for (i = backwardMostMove; i < upto; i++) {
11516         SendMoveToProgram(i, cps);
11517     }
11518 }
11519
11520
11521 int
11522 ResurrectChessProgram ()
11523 {
11524      /* The chess program may have exited.
11525         If so, restart it and feed it all the moves made so far. */
11526     static int doInit = 0;
11527
11528     if (appData.noChessProgram) return 1;
11529
11530     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11531         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11532         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11533         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11534     } else {
11535         if (first.pr != NoProc) return 1;
11536         StartChessProgram(&first);
11537     }
11538     InitChessProgram(&first, FALSE);
11539     FeedMovesToProgram(&first, currentMove);
11540
11541     if (!first.sendTime) {
11542         /* can't tell gnuchess what its clock should read,
11543            so we bow to its notion. */
11544         ResetClocks();
11545         timeRemaining[0][currentMove] = whiteTimeRemaining;
11546         timeRemaining[1][currentMove] = blackTimeRemaining;
11547     }
11548
11549     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11550                 appData.icsEngineAnalyze) && first.analysisSupport) {
11551       SendToProgram("analyze\n", &first);
11552       first.analyzing = TRUE;
11553     }
11554     return 1;
11555 }
11556
11557 /*
11558  * Button procedures
11559  */
11560 void
11561 Reset (int redraw, int init)
11562 {
11563     int i;
11564
11565     if (appData.debugMode) {
11566         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11567                 redraw, init, gameMode);
11568     }
11569     CleanupTail(); // [HGM] vari: delete any stored variations
11570     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11571     pausing = pauseExamInvalid = FALSE;
11572     startedFromSetupPosition = blackPlaysFirst = FALSE;
11573     firstMove = TRUE;
11574     whiteFlag = blackFlag = FALSE;
11575     userOfferedDraw = FALSE;
11576     hintRequested = bookRequested = FALSE;
11577     first.maybeThinking = FALSE;
11578     second.maybeThinking = FALSE;
11579     first.bookSuspend = FALSE; // [HGM] book
11580     second.bookSuspend = FALSE;
11581     thinkOutput[0] = NULLCHAR;
11582     lastHint[0] = NULLCHAR;
11583     ClearGameInfo(&gameInfo);
11584     gameInfo.variant = StringToVariant(appData.variant);
11585     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11586     ics_user_moved = ics_clock_paused = FALSE;
11587     ics_getting_history = H_FALSE;
11588     ics_gamenum = -1;
11589     white_holding[0] = black_holding[0] = NULLCHAR;
11590     ClearProgramStats();
11591     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11592
11593     ResetFrontEnd();
11594     ClearHighlights();
11595     flipView = appData.flipView;
11596     ClearPremoveHighlights();
11597     gotPremove = FALSE;
11598     alarmSounded = FALSE;
11599     killX = killY = -1; // [HGM] lion
11600
11601     GameEnds(EndOfFile, NULL, GE_PLAYER);
11602     if(appData.serverMovesName != NULL) {
11603         /* [HGM] prepare to make moves file for broadcasting */
11604         clock_t t = clock();
11605         if(serverMoves != NULL) fclose(serverMoves);
11606         serverMoves = fopen(appData.serverMovesName, "r");
11607         if(serverMoves != NULL) {
11608             fclose(serverMoves);
11609             /* delay 15 sec before overwriting, so all clients can see end */
11610             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11611         }
11612         serverMoves = fopen(appData.serverMovesName, "w");
11613     }
11614
11615     ExitAnalyzeMode();
11616     gameMode = BeginningOfGame;
11617     ModeHighlight();
11618     if(appData.icsActive) gameInfo.variant = VariantNormal;
11619     currentMove = forwardMostMove = backwardMostMove = 0;
11620     MarkTargetSquares(1);
11621     InitPosition(redraw);
11622     for (i = 0; i < MAX_MOVES; i++) {
11623         if (commentList[i] != NULL) {
11624             free(commentList[i]);
11625             commentList[i] = NULL;
11626         }
11627     }
11628     ResetClocks();
11629     timeRemaining[0][0] = whiteTimeRemaining;
11630     timeRemaining[1][0] = blackTimeRemaining;
11631
11632     if (first.pr == NoProc) {
11633         StartChessProgram(&first);
11634     }
11635     if (init) {
11636             InitChessProgram(&first, startedFromSetupPosition);
11637     }
11638     DisplayTitle("");
11639     DisplayMessage("", "");
11640     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11641     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11642     ClearMap();        // [HGM] exclude: invalidate map
11643 }
11644
11645 void
11646 AutoPlayGameLoop ()
11647 {
11648     for (;;) {
11649         if (!AutoPlayOneMove())
11650           return;
11651         if (matchMode || appData.timeDelay == 0)
11652           continue;
11653         if (appData.timeDelay < 0)
11654           return;
11655         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11656         break;
11657     }
11658 }
11659
11660 void
11661 AnalyzeNextGame()
11662 {
11663     ReloadGame(1); // next game
11664 }
11665
11666 int
11667 AutoPlayOneMove ()
11668 {
11669     int fromX, fromY, toX, toY;
11670
11671     if (appData.debugMode) {
11672       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11673     }
11674
11675     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11676       return FALSE;
11677
11678     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11679       pvInfoList[currentMove].depth = programStats.depth;
11680       pvInfoList[currentMove].score = programStats.score;
11681       pvInfoList[currentMove].time  = 0;
11682       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11683       else { // append analysis of final position as comment
11684         char buf[MSG_SIZ];
11685         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11686         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11687       }
11688       programStats.depth = 0;
11689     }
11690
11691     if (currentMove >= forwardMostMove) {
11692       if(gameMode == AnalyzeFile) {
11693           if(appData.loadGameIndex == -1) {
11694             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11695           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11696           } else {
11697           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11698         }
11699       }
11700 //      gameMode = EndOfGame;
11701 //      ModeHighlight();
11702
11703       /* [AS] Clear current move marker at the end of a game */
11704       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11705
11706       return FALSE;
11707     }
11708
11709     toX = moveList[currentMove][2] - AAA;
11710     toY = moveList[currentMove][3] - ONE;
11711
11712     if (moveList[currentMove][1] == '@') {
11713         if (appData.highlightLastMove) {
11714             SetHighlights(-1, -1, toX, toY);
11715         }
11716     } else {
11717         fromX = moveList[currentMove][0] - AAA;
11718         fromY = moveList[currentMove][1] - ONE;
11719
11720         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11721
11722         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11723
11724         if (appData.highlightLastMove) {
11725             SetHighlights(fromX, fromY, toX, toY);
11726         }
11727     }
11728     DisplayMove(currentMove);
11729     SendMoveToProgram(currentMove++, &first);
11730     DisplayBothClocks();
11731     DrawPosition(FALSE, boards[currentMove]);
11732     // [HGM] PV info: always display, routine tests if empty
11733     DisplayComment(currentMove - 1, commentList[currentMove]);
11734     return TRUE;
11735 }
11736
11737
11738 int
11739 LoadGameOneMove (ChessMove readAhead)
11740 {
11741     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11742     char promoChar = NULLCHAR;
11743     ChessMove moveType;
11744     char move[MSG_SIZ];
11745     char *p, *q;
11746
11747     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11748         gameMode != AnalyzeMode && gameMode != Training) {
11749         gameFileFP = NULL;
11750         return FALSE;
11751     }
11752
11753     yyboardindex = forwardMostMove;
11754     if (readAhead != EndOfFile) {
11755       moveType = readAhead;
11756     } else {
11757       if (gameFileFP == NULL)
11758           return FALSE;
11759       moveType = (ChessMove) Myylex();
11760     }
11761
11762     done = FALSE;
11763     switch (moveType) {
11764       case Comment:
11765         if (appData.debugMode)
11766           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11767         p = yy_text;
11768
11769         /* append the comment but don't display it */
11770         AppendComment(currentMove, p, FALSE);
11771         return TRUE;
11772
11773       case WhiteCapturesEnPassant:
11774       case BlackCapturesEnPassant:
11775       case WhitePromotion:
11776       case BlackPromotion:
11777       case WhiteNonPromotion:
11778       case BlackNonPromotion:
11779       case NormalMove:
11780       case FirstLeg:
11781       case WhiteKingSideCastle:
11782       case WhiteQueenSideCastle:
11783       case BlackKingSideCastle:
11784       case BlackQueenSideCastle:
11785       case WhiteKingSideCastleWild:
11786       case WhiteQueenSideCastleWild:
11787       case BlackKingSideCastleWild:
11788       case BlackQueenSideCastleWild:
11789       /* PUSH Fabien */
11790       case WhiteHSideCastleFR:
11791       case WhiteASideCastleFR:
11792       case BlackHSideCastleFR:
11793       case BlackASideCastleFR:
11794       /* POP Fabien */
11795         if (appData.debugMode)
11796           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11797         fromX = currentMoveString[0] - AAA;
11798         fromY = currentMoveString[1] - ONE;
11799         toX = currentMoveString[2] - AAA;
11800         toY = currentMoveString[3] - ONE;
11801         promoChar = currentMoveString[4];
11802         if(promoChar == ';') promoChar = NULLCHAR;
11803         break;
11804
11805       case WhiteDrop:
11806       case BlackDrop:
11807         if (appData.debugMode)
11808           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11809         fromX = moveType == WhiteDrop ?
11810           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11811         (int) CharToPiece(ToLower(currentMoveString[0]));
11812         fromY = DROP_RANK;
11813         toX = currentMoveString[2] - AAA;
11814         toY = currentMoveString[3] - ONE;
11815         break;
11816
11817       case WhiteWins:
11818       case BlackWins:
11819       case GameIsDrawn:
11820       case GameUnfinished:
11821         if (appData.debugMode)
11822           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11823         p = strchr(yy_text, '{');
11824         if (p == NULL) p = strchr(yy_text, '(');
11825         if (p == NULL) {
11826             p = yy_text;
11827             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11828         } else {
11829             q = strchr(p, *p == '{' ? '}' : ')');
11830             if (q != NULL) *q = NULLCHAR;
11831             p++;
11832         }
11833         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11834         GameEnds(moveType, p, GE_FILE);
11835         done = TRUE;
11836         if (cmailMsgLoaded) {
11837             ClearHighlights();
11838             flipView = WhiteOnMove(currentMove);
11839             if (moveType == GameUnfinished) flipView = !flipView;
11840             if (appData.debugMode)
11841               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11842         }
11843         break;
11844
11845       case EndOfFile:
11846         if (appData.debugMode)
11847           fprintf(debugFP, "Parser hit end of file\n");
11848         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11849           case MT_NONE:
11850           case MT_CHECK:
11851             break;
11852           case MT_CHECKMATE:
11853           case MT_STAINMATE:
11854             if (WhiteOnMove(currentMove)) {
11855                 GameEnds(BlackWins, "Black mates", GE_FILE);
11856             } else {
11857                 GameEnds(WhiteWins, "White mates", GE_FILE);
11858             }
11859             break;
11860           case MT_STALEMATE:
11861             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11862             break;
11863         }
11864         done = TRUE;
11865         break;
11866
11867       case MoveNumberOne:
11868         if (lastLoadGameStart == GNUChessGame) {
11869             /* GNUChessGames have numbers, but they aren't move numbers */
11870             if (appData.debugMode)
11871               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11872                       yy_text, (int) moveType);
11873             return LoadGameOneMove(EndOfFile); /* tail recursion */
11874         }
11875         /* else fall thru */
11876
11877       case XBoardGame:
11878       case GNUChessGame:
11879       case PGNTag:
11880         /* Reached start of next game in file */
11881         if (appData.debugMode)
11882           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11883         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11884           case MT_NONE:
11885           case MT_CHECK:
11886             break;
11887           case MT_CHECKMATE:
11888           case MT_STAINMATE:
11889             if (WhiteOnMove(currentMove)) {
11890                 GameEnds(BlackWins, "Black mates", GE_FILE);
11891             } else {
11892                 GameEnds(WhiteWins, "White mates", GE_FILE);
11893             }
11894             break;
11895           case MT_STALEMATE:
11896             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11897             break;
11898         }
11899         done = TRUE;
11900         break;
11901
11902       case PositionDiagram:     /* should not happen; ignore */
11903       case ElapsedTime:         /* ignore */
11904       case NAG:                 /* ignore */
11905         if (appData.debugMode)
11906           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11907                   yy_text, (int) moveType);
11908         return LoadGameOneMove(EndOfFile); /* tail recursion */
11909
11910       case IllegalMove:
11911         if (appData.testLegality) {
11912             if (appData.debugMode)
11913               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11914             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11915                     (forwardMostMove / 2) + 1,
11916                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11917             DisplayError(move, 0);
11918             done = TRUE;
11919         } else {
11920             if (appData.debugMode)
11921               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11922                       yy_text, currentMoveString);
11923             fromX = currentMoveString[0] - AAA;
11924             fromY = currentMoveString[1] - ONE;
11925             toX = currentMoveString[2] - AAA;
11926             toY = currentMoveString[3] - ONE;
11927             promoChar = currentMoveString[4];
11928         }
11929         break;
11930
11931       case AmbiguousMove:
11932         if (appData.debugMode)
11933           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11934         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11935                 (forwardMostMove / 2) + 1,
11936                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11937         DisplayError(move, 0);
11938         done = TRUE;
11939         break;
11940
11941       default:
11942       case ImpossibleMove:
11943         if (appData.debugMode)
11944           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11945         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11946                 (forwardMostMove / 2) + 1,
11947                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11948         DisplayError(move, 0);
11949         done = TRUE;
11950         break;
11951     }
11952
11953     if (done) {
11954         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11955             DrawPosition(FALSE, boards[currentMove]);
11956             DisplayBothClocks();
11957             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11958               DisplayComment(currentMove - 1, commentList[currentMove]);
11959         }
11960         (void) StopLoadGameTimer();
11961         gameFileFP = NULL;
11962         cmailOldMove = forwardMostMove;
11963         return FALSE;
11964     } else {
11965         /* currentMoveString is set as a side-effect of yylex */
11966
11967         thinkOutput[0] = NULLCHAR;
11968         MakeMove(fromX, fromY, toX, toY, promoChar);
11969         killX = killY = -1; // [HGM] lion: used up
11970         currentMove = forwardMostMove;
11971         return TRUE;
11972     }
11973 }
11974
11975 /* Load the nth game from the given file */
11976 int
11977 LoadGameFromFile (char *filename, int n, char *title, int useList)
11978 {
11979     FILE *f;
11980     char buf[MSG_SIZ];
11981
11982     if (strcmp(filename, "-") == 0) {
11983         f = stdin;
11984         title = "stdin";
11985     } else {
11986         f = fopen(filename, "rb");
11987         if (f == NULL) {
11988           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11989             DisplayError(buf, errno);
11990             return FALSE;
11991         }
11992     }
11993     if (fseek(f, 0, 0) == -1) {
11994         /* f is not seekable; probably a pipe */
11995         useList = FALSE;
11996     }
11997     if (useList && n == 0) {
11998         int error = GameListBuild(f);
11999         if (error) {
12000             DisplayError(_("Cannot build game list"), error);
12001         } else if (!ListEmpty(&gameList) &&
12002                    ((ListGame *) gameList.tailPred)->number > 1) {
12003             GameListPopUp(f, title);
12004             return TRUE;
12005         }
12006         GameListDestroy();
12007         n = 1;
12008     }
12009     if (n == 0) n = 1;
12010     return LoadGame(f, n, title, FALSE);
12011 }
12012
12013
12014 void
12015 MakeRegisteredMove ()
12016 {
12017     int fromX, fromY, toX, toY;
12018     char promoChar;
12019     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12020         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12021           case CMAIL_MOVE:
12022           case CMAIL_DRAW:
12023             if (appData.debugMode)
12024               fprintf(debugFP, "Restoring %s for game %d\n",
12025                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12026
12027             thinkOutput[0] = NULLCHAR;
12028             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12029             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12030             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12031             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12032             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12033             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12034             MakeMove(fromX, fromY, toX, toY, promoChar);
12035             ShowMove(fromX, fromY, toX, toY);
12036
12037             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12038               case MT_NONE:
12039               case MT_CHECK:
12040                 break;
12041
12042               case MT_CHECKMATE:
12043               case MT_STAINMATE:
12044                 if (WhiteOnMove(currentMove)) {
12045                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12046                 } else {
12047                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12048                 }
12049                 break;
12050
12051               case MT_STALEMATE:
12052                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12053                 break;
12054             }
12055
12056             break;
12057
12058           case CMAIL_RESIGN:
12059             if (WhiteOnMove(currentMove)) {
12060                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12061             } else {
12062                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12063             }
12064             break;
12065
12066           case CMAIL_ACCEPT:
12067             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12068             break;
12069
12070           default:
12071             break;
12072         }
12073     }
12074
12075     return;
12076 }
12077
12078 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12079 int
12080 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12081 {
12082     int retVal;
12083
12084     if (gameNumber > nCmailGames) {
12085         DisplayError(_("No more games in this message"), 0);
12086         return FALSE;
12087     }
12088     if (f == lastLoadGameFP) {
12089         int offset = gameNumber - lastLoadGameNumber;
12090         if (offset == 0) {
12091             cmailMsg[0] = NULLCHAR;
12092             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12093                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12094                 nCmailMovesRegistered--;
12095             }
12096             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12097             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12098                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12099             }
12100         } else {
12101             if (! RegisterMove()) return FALSE;
12102         }
12103     }
12104
12105     retVal = LoadGame(f, gameNumber, title, useList);
12106
12107     /* Make move registered during previous look at this game, if any */
12108     MakeRegisteredMove();
12109
12110     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12111         commentList[currentMove]
12112           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12113         DisplayComment(currentMove - 1, commentList[currentMove]);
12114     }
12115
12116     return retVal;
12117 }
12118
12119 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12120 int
12121 ReloadGame (int offset)
12122 {
12123     int gameNumber = lastLoadGameNumber + offset;
12124     if (lastLoadGameFP == NULL) {
12125         DisplayError(_("No game has been loaded yet"), 0);
12126         return FALSE;
12127     }
12128     if (gameNumber <= 0) {
12129         DisplayError(_("Can't back up any further"), 0);
12130         return FALSE;
12131     }
12132     if (cmailMsgLoaded) {
12133         return CmailLoadGame(lastLoadGameFP, gameNumber,
12134                              lastLoadGameTitle, lastLoadGameUseList);
12135     } else {
12136         return LoadGame(lastLoadGameFP, gameNumber,
12137                         lastLoadGameTitle, lastLoadGameUseList);
12138     }
12139 }
12140
12141 int keys[EmptySquare+1];
12142
12143 int
12144 PositionMatches (Board b1, Board b2)
12145 {
12146     int r, f, sum=0;
12147     switch(appData.searchMode) {
12148         case 1: return CompareWithRights(b1, b2);
12149         case 2:
12150             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12151                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12152             }
12153             return TRUE;
12154         case 3:
12155             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12156               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12157                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12158             }
12159             return sum==0;
12160         case 4:
12161             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12163             }
12164             return sum==0;
12165     }
12166     return TRUE;
12167 }
12168
12169 #define Q_PROMO  4
12170 #define Q_EP     3
12171 #define Q_BCASTL 2
12172 #define Q_WCASTL 1
12173
12174 int pieceList[256], quickBoard[256];
12175 ChessSquare pieceType[256] = { EmptySquare };
12176 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12177 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12178 int soughtTotal, turn;
12179 Boolean epOK, flipSearch;
12180
12181 typedef struct {
12182     unsigned char piece, to;
12183 } Move;
12184
12185 #define DSIZE (250000)
12186
12187 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12188 Move *moveDatabase = initialSpace;
12189 unsigned int movePtr, dataSize = DSIZE;
12190
12191 int
12192 MakePieceList (Board board, int *counts)
12193 {
12194     int r, f, n=Q_PROMO, total=0;
12195     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12196     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12197         int sq = f + (r<<4);
12198         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12199             quickBoard[sq] = ++n;
12200             pieceList[n] = sq;
12201             pieceType[n] = board[r][f];
12202             counts[board[r][f]]++;
12203             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12204             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12205             total++;
12206         }
12207     }
12208     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12209     return total;
12210 }
12211
12212 void
12213 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12214 {
12215     int sq = fromX + (fromY<<4);
12216     int piece = quickBoard[sq], rook;
12217     quickBoard[sq] = 0;
12218     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12219     if(piece == pieceList[1] && fromY == toY) {
12220       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12221         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12222         moveDatabase[movePtr++].piece = Q_WCASTL;
12223         quickBoard[sq] = piece;
12224         piece = quickBoard[from]; quickBoard[from] = 0;
12225         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12226       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12227         quickBoard[sq] = 0; // remove Rook
12228         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12229         moveDatabase[movePtr++].piece = Q_WCASTL;
12230         quickBoard[sq] = pieceList[1]; // put King
12231         piece = rook;
12232         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12233       }
12234     } else
12235     if(piece == pieceList[2] && fromY == toY) {
12236       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12237         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12238         moveDatabase[movePtr++].piece = Q_BCASTL;
12239         quickBoard[sq] = piece;
12240         piece = quickBoard[from]; quickBoard[from] = 0;
12241         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12242       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12243         quickBoard[sq] = 0; // remove Rook
12244         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12245         moveDatabase[movePtr++].piece = Q_BCASTL;
12246         quickBoard[sq] = pieceList[2]; // put King
12247         piece = rook;
12248         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12249       }
12250     } else
12251     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12252         quickBoard[(fromY<<4)+toX] = 0;
12253         moveDatabase[movePtr].piece = Q_EP;
12254         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12255         moveDatabase[movePtr].to = sq;
12256     } else
12257     if(promoPiece != pieceType[piece]) {
12258         moveDatabase[movePtr++].piece = Q_PROMO;
12259         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12260     }
12261     moveDatabase[movePtr].piece = piece;
12262     quickBoard[sq] = piece;
12263     movePtr++;
12264 }
12265
12266 int
12267 PackGame (Board board)
12268 {
12269     Move *newSpace = NULL;
12270     moveDatabase[movePtr].piece = 0; // terminate previous game
12271     if(movePtr > dataSize) {
12272         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12273         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12274         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12275         if(newSpace) {
12276             int i;
12277             Move *p = moveDatabase, *q = newSpace;
12278             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12279             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12280             moveDatabase = newSpace;
12281         } else { // calloc failed, we must be out of memory. Too bad...
12282             dataSize = 0; // prevent calloc events for all subsequent games
12283             return 0;     // and signal this one isn't cached
12284         }
12285     }
12286     movePtr++;
12287     MakePieceList(board, counts);
12288     return movePtr;
12289 }
12290
12291 int
12292 QuickCompare (Board board, int *minCounts, int *maxCounts)
12293 {   // compare according to search mode
12294     int r, f;
12295     switch(appData.searchMode)
12296     {
12297       case 1: // exact position match
12298         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12299         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12300             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12301         }
12302         break;
12303       case 2: // can have extra material on empty squares
12304         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12305             if(board[r][f] == EmptySquare) continue;
12306             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12307         }
12308         break;
12309       case 3: // material with exact Pawn structure
12310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12312             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12313         } // fall through to material comparison
12314       case 4: // exact material
12315         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12316         break;
12317       case 6: // material range with given imbalance
12318         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12319         // fall through to range comparison
12320       case 5: // material range
12321         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12322     }
12323     return TRUE;
12324 }
12325
12326 int
12327 QuickScan (Board board, Move *move)
12328 {   // reconstruct game,and compare all positions in it
12329     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12330     do {
12331         int piece = move->piece;
12332         int to = move->to, from = pieceList[piece];
12333         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12334           if(!piece) return -1;
12335           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12336             piece = (++move)->piece;
12337             from = pieceList[piece];
12338             counts[pieceType[piece]]--;
12339             pieceType[piece] = (ChessSquare) move->to;
12340             counts[move->to]++;
12341           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12342             counts[pieceType[quickBoard[to]]]--;
12343             quickBoard[to] = 0; total--;
12344             move++;
12345             continue;
12346           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12347             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12348             from  = pieceList[piece]; // so this must be King
12349             quickBoard[from] = 0;
12350             pieceList[piece] = to;
12351             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12352             quickBoard[from] = 0; // rook
12353             quickBoard[to] = piece;
12354             to = move->to; piece = move->piece;
12355             goto aftercastle;
12356           }
12357         }
12358         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12359         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12360         quickBoard[from] = 0;
12361       aftercastle:
12362         quickBoard[to] = piece;
12363         pieceList[piece] = to;
12364         cnt++; turn ^= 3;
12365         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12366            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12367            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12368                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12369           ) {
12370             static int lastCounts[EmptySquare+1];
12371             int i;
12372             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12373             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12374         } else stretch = 0;
12375         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12376         move++;
12377     } while(1);
12378 }
12379
12380 void
12381 InitSearch ()
12382 {
12383     int r, f;
12384     flipSearch = FALSE;
12385     CopyBoard(soughtBoard, boards[currentMove]);
12386     soughtTotal = MakePieceList(soughtBoard, maxSought);
12387     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12388     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12389     CopyBoard(reverseBoard, boards[currentMove]);
12390     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12391         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12392         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12393         reverseBoard[r][f] = piece;
12394     }
12395     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12396     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12397     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12398                  || (boards[currentMove][CASTLING][2] == NoRights ||
12399                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12400                  && (boards[currentMove][CASTLING][5] == NoRights ||
12401                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12402       ) {
12403         flipSearch = TRUE;
12404         CopyBoard(flipBoard, soughtBoard);
12405         CopyBoard(rotateBoard, reverseBoard);
12406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12407             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12408             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12409         }
12410     }
12411     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12412     if(appData.searchMode >= 5) {
12413         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12414         MakePieceList(soughtBoard, minSought);
12415         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12416     }
12417     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12418         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12419 }
12420
12421 GameInfo dummyInfo;
12422 static int creatingBook;
12423
12424 int
12425 GameContainsPosition (FILE *f, ListGame *lg)
12426 {
12427     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12428     int fromX, fromY, toX, toY;
12429     char promoChar;
12430     static int initDone=FALSE;
12431
12432     // weed out games based on numerical tag comparison
12433     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12434     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12435     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12436     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12437     if(!initDone) {
12438         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12439         initDone = TRUE;
12440     }
12441     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12442     else CopyBoard(boards[scratch], initialPosition); // default start position
12443     if(lg->moves) {
12444         turn = btm + 1;
12445         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12446         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12447     }
12448     if(btm) plyNr++;
12449     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12450     fseek(f, lg->offset, 0);
12451     yynewfile(f);
12452     while(1) {
12453         yyboardindex = scratch;
12454         quickFlag = plyNr+1;
12455         next = Myylex();
12456         quickFlag = 0;
12457         switch(next) {
12458             case PGNTag:
12459                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12460             default:
12461                 continue;
12462
12463             case XBoardGame:
12464             case GNUChessGame:
12465                 if(plyNr) return -1; // after we have seen moves, this is for new game
12466               continue;
12467
12468             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12469             case ImpossibleMove:
12470             case WhiteWins: // game ends here with these four
12471             case BlackWins:
12472             case GameIsDrawn:
12473             case GameUnfinished:
12474                 return -1;
12475
12476             case IllegalMove:
12477                 if(appData.testLegality) return -1;
12478             case WhiteCapturesEnPassant:
12479             case BlackCapturesEnPassant:
12480             case WhitePromotion:
12481             case BlackPromotion:
12482             case WhiteNonPromotion:
12483             case BlackNonPromotion:
12484             case NormalMove:
12485             case FirstLeg:
12486             case WhiteKingSideCastle:
12487             case WhiteQueenSideCastle:
12488             case BlackKingSideCastle:
12489             case BlackQueenSideCastle:
12490             case WhiteKingSideCastleWild:
12491             case WhiteQueenSideCastleWild:
12492             case BlackKingSideCastleWild:
12493             case BlackQueenSideCastleWild:
12494             case WhiteHSideCastleFR:
12495             case WhiteASideCastleFR:
12496             case BlackHSideCastleFR:
12497             case BlackASideCastleFR:
12498                 fromX = currentMoveString[0] - AAA;
12499                 fromY = currentMoveString[1] - ONE;
12500                 toX = currentMoveString[2] - AAA;
12501                 toY = currentMoveString[3] - ONE;
12502                 promoChar = currentMoveString[4];
12503                 break;
12504             case WhiteDrop:
12505             case BlackDrop:
12506                 fromX = next == WhiteDrop ?
12507                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12508                   (int) CharToPiece(ToLower(currentMoveString[0]));
12509                 fromY = DROP_RANK;
12510                 toX = currentMoveString[2] - AAA;
12511                 toY = currentMoveString[3] - ONE;
12512                 promoChar = 0;
12513                 break;
12514         }
12515         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12516         plyNr++;
12517         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12518         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12519         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12520         if(appData.findMirror) {
12521             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12522             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12523         }
12524     }
12525 }
12526
12527 /* Load the nth game from open file f */
12528 int
12529 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12530 {
12531     ChessMove cm;
12532     char buf[MSG_SIZ];
12533     int gn = gameNumber;
12534     ListGame *lg = NULL;
12535     int numPGNTags = 0;
12536     int err, pos = -1;
12537     GameMode oldGameMode;
12538     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12539
12540     if (appData.debugMode)
12541         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12542
12543     if (gameMode == Training )
12544         SetTrainingModeOff();
12545
12546     oldGameMode = gameMode;
12547     if (gameMode != BeginningOfGame) {
12548       Reset(FALSE, TRUE);
12549     }
12550     killX = killY = -1; // [HGM] lion: in case we did not Reset
12551
12552     gameFileFP = f;
12553     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12554         fclose(lastLoadGameFP);
12555     }
12556
12557     if (useList) {
12558         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12559
12560         if (lg) {
12561             fseek(f, lg->offset, 0);
12562             GameListHighlight(gameNumber);
12563             pos = lg->position;
12564             gn = 1;
12565         }
12566         else {
12567             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12568               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12569             else
12570             DisplayError(_("Game number out of range"), 0);
12571             return FALSE;
12572         }
12573     } else {
12574         GameListDestroy();
12575         if (fseek(f, 0, 0) == -1) {
12576             if (f == lastLoadGameFP ?
12577                 gameNumber == lastLoadGameNumber + 1 :
12578                 gameNumber == 1) {
12579                 gn = 1;
12580             } else {
12581                 DisplayError(_("Can't seek on game file"), 0);
12582                 return FALSE;
12583             }
12584         }
12585     }
12586     lastLoadGameFP = f;
12587     lastLoadGameNumber = gameNumber;
12588     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12589     lastLoadGameUseList = useList;
12590
12591     yynewfile(f);
12592
12593     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12594       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12595                 lg->gameInfo.black);
12596             DisplayTitle(buf);
12597     } else if (*title != NULLCHAR) {
12598         if (gameNumber > 1) {
12599           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12600             DisplayTitle(buf);
12601         } else {
12602             DisplayTitle(title);
12603         }
12604     }
12605
12606     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12607         gameMode = PlayFromGameFile;
12608         ModeHighlight();
12609     }
12610
12611     currentMove = forwardMostMove = backwardMostMove = 0;
12612     CopyBoard(boards[0], initialPosition);
12613     StopClocks();
12614
12615     /*
12616      * Skip the first gn-1 games in the file.
12617      * Also skip over anything that precedes an identifiable
12618      * start of game marker, to avoid being confused by
12619      * garbage at the start of the file.  Currently
12620      * recognized start of game markers are the move number "1",
12621      * the pattern "gnuchess .* game", the pattern
12622      * "^[#;%] [^ ]* game file", and a PGN tag block.
12623      * A game that starts with one of the latter two patterns
12624      * will also have a move number 1, possibly
12625      * following a position diagram.
12626      * 5-4-02: Let's try being more lenient and allowing a game to
12627      * start with an unnumbered move.  Does that break anything?
12628      */
12629     cm = lastLoadGameStart = EndOfFile;
12630     while (gn > 0) {
12631         yyboardindex = forwardMostMove;
12632         cm = (ChessMove) Myylex();
12633         switch (cm) {
12634           case EndOfFile:
12635             if (cmailMsgLoaded) {
12636                 nCmailGames = CMAIL_MAX_GAMES - gn;
12637             } else {
12638                 Reset(TRUE, TRUE);
12639                 DisplayError(_("Game not found in file"), 0);
12640             }
12641             return FALSE;
12642
12643           case GNUChessGame:
12644           case XBoardGame:
12645             gn--;
12646             lastLoadGameStart = cm;
12647             break;
12648
12649           case MoveNumberOne:
12650             switch (lastLoadGameStart) {
12651               case GNUChessGame:
12652               case XBoardGame:
12653               case PGNTag:
12654                 break;
12655               case MoveNumberOne:
12656               case EndOfFile:
12657                 gn--;           /* count this game */
12658                 lastLoadGameStart = cm;
12659                 break;
12660               default:
12661                 /* impossible */
12662                 break;
12663             }
12664             break;
12665
12666           case PGNTag:
12667             switch (lastLoadGameStart) {
12668               case GNUChessGame:
12669               case PGNTag:
12670               case MoveNumberOne:
12671               case EndOfFile:
12672                 gn--;           /* count this game */
12673                 lastLoadGameStart = cm;
12674                 break;
12675               case XBoardGame:
12676                 lastLoadGameStart = cm; /* game counted already */
12677                 break;
12678               default:
12679                 /* impossible */
12680                 break;
12681             }
12682             if (gn > 0) {
12683                 do {
12684                     yyboardindex = forwardMostMove;
12685                     cm = (ChessMove) Myylex();
12686                 } while (cm == PGNTag || cm == Comment);
12687             }
12688             break;
12689
12690           case WhiteWins:
12691           case BlackWins:
12692           case GameIsDrawn:
12693             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12694                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12695                     != CMAIL_OLD_RESULT) {
12696                     nCmailResults ++ ;
12697                     cmailResult[  CMAIL_MAX_GAMES
12698                                 - gn - 1] = CMAIL_OLD_RESULT;
12699                 }
12700             }
12701             break;
12702
12703           case NormalMove:
12704           case FirstLeg:
12705             /* Only a NormalMove can be at the start of a game
12706              * without a position diagram. */
12707             if (lastLoadGameStart == EndOfFile ) {
12708               gn--;
12709               lastLoadGameStart = MoveNumberOne;
12710             }
12711             break;
12712
12713           default:
12714             break;
12715         }
12716     }
12717
12718     if (appData.debugMode)
12719       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12720
12721     if (cm == XBoardGame) {
12722         /* Skip any header junk before position diagram and/or move 1 */
12723         for (;;) {
12724             yyboardindex = forwardMostMove;
12725             cm = (ChessMove) Myylex();
12726
12727             if (cm == EndOfFile ||
12728                 cm == GNUChessGame || cm == XBoardGame) {
12729                 /* Empty game; pretend end-of-file and handle later */
12730                 cm = EndOfFile;
12731                 break;
12732             }
12733
12734             if (cm == MoveNumberOne || cm == PositionDiagram ||
12735                 cm == PGNTag || cm == Comment)
12736               break;
12737         }
12738     } else if (cm == GNUChessGame) {
12739         if (gameInfo.event != NULL) {
12740             free(gameInfo.event);
12741         }
12742         gameInfo.event = StrSave(yy_text);
12743     }
12744
12745     startedFromSetupPosition = FALSE;
12746     while (cm == PGNTag) {
12747         if (appData.debugMode)
12748           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12749         err = ParsePGNTag(yy_text, &gameInfo);
12750         if (!err) numPGNTags++;
12751
12752         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12753         if(gameInfo.variant != oldVariant) {
12754             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12755             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12756             InitPosition(TRUE);
12757             oldVariant = gameInfo.variant;
12758             if (appData.debugMode)
12759               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12760         }
12761
12762
12763         if (gameInfo.fen != NULL) {
12764           Board initial_position;
12765           startedFromSetupPosition = TRUE;
12766           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12767             Reset(TRUE, TRUE);
12768             DisplayError(_("Bad FEN position in file"), 0);
12769             return FALSE;
12770           }
12771           CopyBoard(boards[0], initial_position);
12772           if (blackPlaysFirst) {
12773             currentMove = forwardMostMove = backwardMostMove = 1;
12774             CopyBoard(boards[1], initial_position);
12775             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12776             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12777             timeRemaining[0][1] = whiteTimeRemaining;
12778             timeRemaining[1][1] = blackTimeRemaining;
12779             if (commentList[0] != NULL) {
12780               commentList[1] = commentList[0];
12781               commentList[0] = NULL;
12782             }
12783           } else {
12784             currentMove = forwardMostMove = backwardMostMove = 0;
12785           }
12786           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12787           {   int i;
12788               initialRulePlies = FENrulePlies;
12789               for( i=0; i< nrCastlingRights; i++ )
12790                   initialRights[i] = initial_position[CASTLING][i];
12791           }
12792           yyboardindex = forwardMostMove;
12793           free(gameInfo.fen);
12794           gameInfo.fen = NULL;
12795         }
12796
12797         yyboardindex = forwardMostMove;
12798         cm = (ChessMove) Myylex();
12799
12800         /* Handle comments interspersed among the tags */
12801         while (cm == Comment) {
12802             char *p;
12803             if (appData.debugMode)
12804               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12805             p = yy_text;
12806             AppendComment(currentMove, p, FALSE);
12807             yyboardindex = forwardMostMove;
12808             cm = (ChessMove) Myylex();
12809         }
12810     }
12811
12812     /* don't rely on existence of Event tag since if game was
12813      * pasted from clipboard the Event tag may not exist
12814      */
12815     if (numPGNTags > 0){
12816         char *tags;
12817         if (gameInfo.variant == VariantNormal) {
12818           VariantClass v = StringToVariant(gameInfo.event);
12819           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12820           if(v < VariantShogi) gameInfo.variant = v;
12821         }
12822         if (!matchMode) {
12823           if( appData.autoDisplayTags ) {
12824             tags = PGNTags(&gameInfo);
12825             TagsPopUp(tags, CmailMsg());
12826             free(tags);
12827           }
12828         }
12829     } else {
12830         /* Make something up, but don't display it now */
12831         SetGameInfo();
12832         TagsPopDown();
12833     }
12834
12835     if (cm == PositionDiagram) {
12836         int i, j;
12837         char *p;
12838         Board initial_position;
12839
12840         if (appData.debugMode)
12841           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12842
12843         if (!startedFromSetupPosition) {
12844             p = yy_text;
12845             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12846               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12847                 switch (*p) {
12848                   case '{':
12849                   case '[':
12850                   case '-':
12851                   case ' ':
12852                   case '\t':
12853                   case '\n':
12854                   case '\r':
12855                     break;
12856                   default:
12857                     initial_position[i][j++] = CharToPiece(*p);
12858                     break;
12859                 }
12860             while (*p == ' ' || *p == '\t' ||
12861                    *p == '\n' || *p == '\r') p++;
12862
12863             if (strncmp(p, "black", strlen("black"))==0)
12864               blackPlaysFirst = TRUE;
12865             else
12866               blackPlaysFirst = FALSE;
12867             startedFromSetupPosition = TRUE;
12868
12869             CopyBoard(boards[0], initial_position);
12870             if (blackPlaysFirst) {
12871                 currentMove = forwardMostMove = backwardMostMove = 1;
12872                 CopyBoard(boards[1], initial_position);
12873                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12874                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12875                 timeRemaining[0][1] = whiteTimeRemaining;
12876                 timeRemaining[1][1] = blackTimeRemaining;
12877                 if (commentList[0] != NULL) {
12878                     commentList[1] = commentList[0];
12879                     commentList[0] = NULL;
12880                 }
12881             } else {
12882                 currentMove = forwardMostMove = backwardMostMove = 0;
12883             }
12884         }
12885         yyboardindex = forwardMostMove;
12886         cm = (ChessMove) Myylex();
12887     }
12888
12889   if(!creatingBook) {
12890     if (first.pr == NoProc) {
12891         StartChessProgram(&first);
12892     }
12893     InitChessProgram(&first, FALSE);
12894     SendToProgram("force\n", &first);
12895     if (startedFromSetupPosition) {
12896         SendBoard(&first, forwardMostMove);
12897     if (appData.debugMode) {
12898         fprintf(debugFP, "Load Game\n");
12899     }
12900         DisplayBothClocks();
12901     }
12902   }
12903
12904     /* [HGM] server: flag to write setup moves in broadcast file as one */
12905     loadFlag = appData.suppressLoadMoves;
12906
12907     while (cm == Comment) {
12908         char *p;
12909         if (appData.debugMode)
12910           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12911         p = yy_text;
12912         AppendComment(currentMove, p, FALSE);
12913         yyboardindex = forwardMostMove;
12914         cm = (ChessMove) Myylex();
12915     }
12916
12917     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12918         cm == WhiteWins || cm == BlackWins ||
12919         cm == GameIsDrawn || cm == GameUnfinished) {
12920         DisplayMessage("", _("No moves in game"));
12921         if (cmailMsgLoaded) {
12922             if (appData.debugMode)
12923               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12924             ClearHighlights();
12925             flipView = FALSE;
12926         }
12927         DrawPosition(FALSE, boards[currentMove]);
12928         DisplayBothClocks();
12929         gameMode = EditGame;
12930         ModeHighlight();
12931         gameFileFP = NULL;
12932         cmailOldMove = 0;
12933         return TRUE;
12934     }
12935
12936     // [HGM] PV info: routine tests if comment empty
12937     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12938         DisplayComment(currentMove - 1, commentList[currentMove]);
12939     }
12940     if (!matchMode && appData.timeDelay != 0)
12941       DrawPosition(FALSE, boards[currentMove]);
12942
12943     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12944       programStats.ok_to_send = 1;
12945     }
12946
12947     /* if the first token after the PGN tags is a move
12948      * and not move number 1, retrieve it from the parser
12949      */
12950     if (cm != MoveNumberOne)
12951         LoadGameOneMove(cm);
12952
12953     /* load the remaining moves from the file */
12954     while (LoadGameOneMove(EndOfFile)) {
12955       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12956       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12957     }
12958
12959     /* rewind to the start of the game */
12960     currentMove = backwardMostMove;
12961
12962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12963
12964     if (oldGameMode == AnalyzeFile) {
12965       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12966       AnalyzeFileEvent();
12967     } else
12968     if (oldGameMode == AnalyzeMode) {
12969       AnalyzeFileEvent();
12970     }
12971
12972     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12973         long int w, b; // [HGM] adjourn: restore saved clock times
12974         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12975         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12976             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12977             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12978         }
12979     }
12980
12981     if(creatingBook) return TRUE;
12982     if (!matchMode && pos > 0) {
12983         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12984     } else
12985     if (matchMode || appData.timeDelay == 0) {
12986       ToEndEvent();
12987     } else if (appData.timeDelay > 0) {
12988       AutoPlayGameLoop();
12989     }
12990
12991     if (appData.debugMode)
12992         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12993
12994     loadFlag = 0; /* [HGM] true game starts */
12995     return TRUE;
12996 }
12997
12998 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12999 int
13000 ReloadPosition (int offset)
13001 {
13002     int positionNumber = lastLoadPositionNumber + offset;
13003     if (lastLoadPositionFP == NULL) {
13004         DisplayError(_("No position has been loaded yet"), 0);
13005         return FALSE;
13006     }
13007     if (positionNumber <= 0) {
13008         DisplayError(_("Can't back up any further"), 0);
13009         return FALSE;
13010     }
13011     return LoadPosition(lastLoadPositionFP, positionNumber,
13012                         lastLoadPositionTitle);
13013 }
13014
13015 /* Load the nth position from the given file */
13016 int
13017 LoadPositionFromFile (char *filename, int n, char *title)
13018 {
13019     FILE *f;
13020     char buf[MSG_SIZ];
13021
13022     if (strcmp(filename, "-") == 0) {
13023         return LoadPosition(stdin, n, "stdin");
13024     } else {
13025         f = fopen(filename, "rb");
13026         if (f == NULL) {
13027             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13028             DisplayError(buf, errno);
13029             return FALSE;
13030         } else {
13031             return LoadPosition(f, n, title);
13032         }
13033     }
13034 }
13035
13036 /* Load the nth position from the given open file, and close it */
13037 int
13038 LoadPosition (FILE *f, int positionNumber, char *title)
13039 {
13040     char *p, line[MSG_SIZ];
13041     Board initial_position;
13042     int i, j, fenMode, pn;
13043
13044     if (gameMode == Training )
13045         SetTrainingModeOff();
13046
13047     if (gameMode != BeginningOfGame) {
13048         Reset(FALSE, TRUE);
13049     }
13050     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13051         fclose(lastLoadPositionFP);
13052     }
13053     if (positionNumber == 0) positionNumber = 1;
13054     lastLoadPositionFP = f;
13055     lastLoadPositionNumber = positionNumber;
13056     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13057     if (first.pr == NoProc && !appData.noChessProgram) {
13058       StartChessProgram(&first);
13059       InitChessProgram(&first, FALSE);
13060     }
13061     pn = positionNumber;
13062     if (positionNumber < 0) {
13063         /* Negative position number means to seek to that byte offset */
13064         if (fseek(f, -positionNumber, 0) == -1) {
13065             DisplayError(_("Can't seek on position file"), 0);
13066             return FALSE;
13067         };
13068         pn = 1;
13069     } else {
13070         if (fseek(f, 0, 0) == -1) {
13071             if (f == lastLoadPositionFP ?
13072                 positionNumber == lastLoadPositionNumber + 1 :
13073                 positionNumber == 1) {
13074                 pn = 1;
13075             } else {
13076                 DisplayError(_("Can't seek on position file"), 0);
13077                 return FALSE;
13078             }
13079         }
13080     }
13081     /* See if this file is FEN or old-style xboard */
13082     if (fgets(line, MSG_SIZ, f) == NULL) {
13083         DisplayError(_("Position not found in file"), 0);
13084         return FALSE;
13085     }
13086     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13087     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13088
13089     if (pn >= 2) {
13090         if (fenMode || line[0] == '#') pn--;
13091         while (pn > 0) {
13092             /* skip positions before number pn */
13093             if (fgets(line, MSG_SIZ, f) == NULL) {
13094                 Reset(TRUE, TRUE);
13095                 DisplayError(_("Position not found in file"), 0);
13096                 return FALSE;
13097             }
13098             if (fenMode || line[0] == '#') pn--;
13099         }
13100     }
13101
13102     if (fenMode) {
13103         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13104             DisplayError(_("Bad FEN position in file"), 0);
13105             return FALSE;
13106         }
13107     } else {
13108         (void) fgets(line, MSG_SIZ, f);
13109         (void) fgets(line, MSG_SIZ, f);
13110
13111         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13112             (void) fgets(line, MSG_SIZ, f);
13113             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13114                 if (*p == ' ')
13115                   continue;
13116                 initial_position[i][j++] = CharToPiece(*p);
13117             }
13118         }
13119
13120         blackPlaysFirst = FALSE;
13121         if (!feof(f)) {
13122             (void) fgets(line, MSG_SIZ, f);
13123             if (strncmp(line, "black", strlen("black"))==0)
13124               blackPlaysFirst = TRUE;
13125         }
13126     }
13127     startedFromSetupPosition = TRUE;
13128
13129     CopyBoard(boards[0], initial_position);
13130     if (blackPlaysFirst) {
13131         currentMove = forwardMostMove = backwardMostMove = 1;
13132         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13133         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13134         CopyBoard(boards[1], initial_position);
13135         DisplayMessage("", _("Black to play"));
13136     } else {
13137         currentMove = forwardMostMove = backwardMostMove = 0;
13138         DisplayMessage("", _("White to play"));
13139     }
13140     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13141     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13142         SendToProgram("force\n", &first);
13143         SendBoard(&first, forwardMostMove);
13144     }
13145     if (appData.debugMode) {
13146 int i, j;
13147   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13148   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13149         fprintf(debugFP, "Load Position\n");
13150     }
13151
13152     if (positionNumber > 1) {
13153       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13154         DisplayTitle(line);
13155     } else {
13156         DisplayTitle(title);
13157     }
13158     gameMode = EditGame;
13159     ModeHighlight();
13160     ResetClocks();
13161     timeRemaining[0][1] = whiteTimeRemaining;
13162     timeRemaining[1][1] = blackTimeRemaining;
13163     DrawPosition(FALSE, boards[currentMove]);
13164
13165     return TRUE;
13166 }
13167
13168
13169 void
13170 CopyPlayerNameIntoFileName (char **dest, char *src)
13171 {
13172     while (*src != NULLCHAR && *src != ',') {
13173         if (*src == ' ') {
13174             *(*dest)++ = '_';
13175             src++;
13176         } else {
13177             *(*dest)++ = *src++;
13178         }
13179     }
13180 }
13181
13182 char *
13183 DefaultFileName (char *ext)
13184 {
13185     static char def[MSG_SIZ];
13186     char *p;
13187
13188     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13189         p = def;
13190         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13191         *p++ = '-';
13192         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13193         *p++ = '.';
13194         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13195     } else {
13196         def[0] = NULLCHAR;
13197     }
13198     return def;
13199 }
13200
13201 /* Save the current game to the given file */
13202 int
13203 SaveGameToFile (char *filename, int append)
13204 {
13205     FILE *f;
13206     char buf[MSG_SIZ];
13207     int result, i, t,tot=0;
13208
13209     if (strcmp(filename, "-") == 0) {
13210         return SaveGame(stdout, 0, NULL);
13211     } else {
13212         for(i=0; i<10; i++) { // upto 10 tries
13213              f = fopen(filename, append ? "a" : "w");
13214              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13215              if(f || errno != 13) break;
13216              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13217              tot += t;
13218         }
13219         if (f == NULL) {
13220             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13221             DisplayError(buf, errno);
13222             return FALSE;
13223         } else {
13224             safeStrCpy(buf, lastMsg, MSG_SIZ);
13225             DisplayMessage(_("Waiting for access to save file"), "");
13226             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13227             DisplayMessage(_("Saving game"), "");
13228             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13229             result = SaveGame(f, 0, NULL);
13230             DisplayMessage(buf, "");
13231             return result;
13232         }
13233     }
13234 }
13235
13236 char *
13237 SavePart (char *str)
13238 {
13239     static char buf[MSG_SIZ];
13240     char *p;
13241
13242     p = strchr(str, ' ');
13243     if (p == NULL) return str;
13244     strncpy(buf, str, p - str);
13245     buf[p - str] = NULLCHAR;
13246     return buf;
13247 }
13248
13249 #define PGN_MAX_LINE 75
13250
13251 #define PGN_SIDE_WHITE  0
13252 #define PGN_SIDE_BLACK  1
13253
13254 static int
13255 FindFirstMoveOutOfBook (int side)
13256 {
13257     int result = -1;
13258
13259     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13260         int index = backwardMostMove;
13261         int has_book_hit = 0;
13262
13263         if( (index % 2) != side ) {
13264             index++;
13265         }
13266
13267         while( index < forwardMostMove ) {
13268             /* Check to see if engine is in book */
13269             int depth = pvInfoList[index].depth;
13270             int score = pvInfoList[index].score;
13271             int in_book = 0;
13272
13273             if( depth <= 2 ) {
13274                 in_book = 1;
13275             }
13276             else if( score == 0 && depth == 63 ) {
13277                 in_book = 1; /* Zappa */
13278             }
13279             else if( score == 2 && depth == 99 ) {
13280                 in_book = 1; /* Abrok */
13281             }
13282
13283             has_book_hit += in_book;
13284
13285             if( ! in_book ) {
13286                 result = index;
13287
13288                 break;
13289             }
13290
13291             index += 2;
13292         }
13293     }
13294
13295     return result;
13296 }
13297
13298 void
13299 GetOutOfBookInfo (char * buf)
13300 {
13301     int oob[2];
13302     int i;
13303     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13304
13305     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13306     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13307
13308     *buf = '\0';
13309
13310     if( oob[0] >= 0 || oob[1] >= 0 ) {
13311         for( i=0; i<2; i++ ) {
13312             int idx = oob[i];
13313
13314             if( idx >= 0 ) {
13315                 if( i > 0 && oob[0] >= 0 ) {
13316                     strcat( buf, "   " );
13317                 }
13318
13319                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13320                 sprintf( buf+strlen(buf), "%s%.2f",
13321                     pvInfoList[idx].score >= 0 ? "+" : "",
13322                     pvInfoList[idx].score / 100.0 );
13323             }
13324         }
13325     }
13326 }
13327
13328 /* Save game in PGN style and close the file */
13329 int
13330 SaveGamePGN (FILE *f)
13331 {
13332     int i, offset, linelen, newblock;
13333 //    char *movetext;
13334     char numtext[32];
13335     int movelen, numlen, blank;
13336     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13337
13338     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13339
13340     PrintPGNTags(f, &gameInfo);
13341
13342     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13343
13344     if (backwardMostMove > 0 || startedFromSetupPosition) {
13345         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13346         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13347         fprintf(f, "\n{--------------\n");
13348         PrintPosition(f, backwardMostMove);
13349         fprintf(f, "--------------}\n");
13350         free(fen);
13351     }
13352     else {
13353         /* [AS] Out of book annotation */
13354         if( appData.saveOutOfBookInfo ) {
13355             char buf[64];
13356
13357             GetOutOfBookInfo( buf );
13358
13359             if( buf[0] != '\0' ) {
13360                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13361             }
13362         }
13363
13364         fprintf(f, "\n");
13365     }
13366
13367     i = backwardMostMove;
13368     linelen = 0;
13369     newblock = TRUE;
13370
13371     while (i < forwardMostMove) {
13372         /* Print comments preceding this move */
13373         if (commentList[i] != NULL) {
13374             if (linelen > 0) fprintf(f, "\n");
13375             fprintf(f, "%s", commentList[i]);
13376             linelen = 0;
13377             newblock = TRUE;
13378         }
13379
13380         /* Format move number */
13381         if ((i % 2) == 0)
13382           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13383         else
13384           if (newblock)
13385             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13386           else
13387             numtext[0] = NULLCHAR;
13388
13389         numlen = strlen(numtext);
13390         newblock = FALSE;
13391
13392         /* Print move number */
13393         blank = linelen > 0 && numlen > 0;
13394         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13395             fprintf(f, "\n");
13396             linelen = 0;
13397             blank = 0;
13398         }
13399         if (blank) {
13400             fprintf(f, " ");
13401             linelen++;
13402         }
13403         fprintf(f, "%s", numtext);
13404         linelen += numlen;
13405
13406         /* Get move */
13407         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13408         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13409
13410         /* Print move */
13411         blank = linelen > 0 && movelen > 0;
13412         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13413             fprintf(f, "\n");
13414             linelen = 0;
13415             blank = 0;
13416         }
13417         if (blank) {
13418             fprintf(f, " ");
13419             linelen++;
13420         }
13421         fprintf(f, "%s", move_buffer);
13422         linelen += movelen;
13423
13424         /* [AS] Add PV info if present */
13425         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13426             /* [HGM] add time */
13427             char buf[MSG_SIZ]; int seconds;
13428
13429             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13430
13431             if( seconds <= 0)
13432               buf[0] = 0;
13433             else
13434               if( seconds < 30 )
13435                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13436               else
13437                 {
13438                   seconds = (seconds + 4)/10; // round to full seconds
13439                   if( seconds < 60 )
13440                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13441                   else
13442                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13443                 }
13444
13445             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13446                       pvInfoList[i].score >= 0 ? "+" : "",
13447                       pvInfoList[i].score / 100.0,
13448                       pvInfoList[i].depth,
13449                       buf );
13450
13451             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13452
13453             /* Print score/depth */
13454             blank = linelen > 0 && movelen > 0;
13455             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13456                 fprintf(f, "\n");
13457                 linelen = 0;
13458                 blank = 0;
13459             }
13460             if (blank) {
13461                 fprintf(f, " ");
13462                 linelen++;
13463             }
13464             fprintf(f, "%s", move_buffer);
13465             linelen += movelen;
13466         }
13467
13468         i++;
13469     }
13470
13471     /* Start a new line */
13472     if (linelen > 0) fprintf(f, "\n");
13473
13474     /* Print comments after last move */
13475     if (commentList[i] != NULL) {
13476         fprintf(f, "%s\n", commentList[i]);
13477     }
13478
13479     /* Print result */
13480     if (gameInfo.resultDetails != NULL &&
13481         gameInfo.resultDetails[0] != NULLCHAR) {
13482         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13483         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13484            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13485             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13486         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13487     } else {
13488         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13489     }
13490
13491     fclose(f);
13492     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13493     return TRUE;
13494 }
13495
13496 /* Save game in old style and close the file */
13497 int
13498 SaveGameOldStyle (FILE *f)
13499 {
13500     int i, offset;
13501     time_t tm;
13502
13503     tm = time((time_t *) NULL);
13504
13505     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13506     PrintOpponents(f);
13507
13508     if (backwardMostMove > 0 || startedFromSetupPosition) {
13509         fprintf(f, "\n[--------------\n");
13510         PrintPosition(f, backwardMostMove);
13511         fprintf(f, "--------------]\n");
13512     } else {
13513         fprintf(f, "\n");
13514     }
13515
13516     i = backwardMostMove;
13517     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13518
13519     while (i < forwardMostMove) {
13520         if (commentList[i] != NULL) {
13521             fprintf(f, "[%s]\n", commentList[i]);
13522         }
13523
13524         if ((i % 2) == 1) {
13525             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13526             i++;
13527         } else {
13528             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13529             i++;
13530             if (commentList[i] != NULL) {
13531                 fprintf(f, "\n");
13532                 continue;
13533             }
13534             if (i >= forwardMostMove) {
13535                 fprintf(f, "\n");
13536                 break;
13537             }
13538             fprintf(f, "%s\n", parseList[i]);
13539             i++;
13540         }
13541     }
13542
13543     if (commentList[i] != NULL) {
13544         fprintf(f, "[%s]\n", commentList[i]);
13545     }
13546
13547     /* This isn't really the old style, but it's close enough */
13548     if (gameInfo.resultDetails != NULL &&
13549         gameInfo.resultDetails[0] != NULLCHAR) {
13550         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13551                 gameInfo.resultDetails);
13552     } else {
13553         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13554     }
13555
13556     fclose(f);
13557     return TRUE;
13558 }
13559
13560 /* Save the current game to open file f and close the file */
13561 int
13562 SaveGame (FILE *f, int dummy, char *dummy2)
13563 {
13564     if (gameMode == EditPosition) EditPositionDone(TRUE);
13565     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13566     if (appData.oldSaveStyle)
13567       return SaveGameOldStyle(f);
13568     else
13569       return SaveGamePGN(f);
13570 }
13571
13572 /* Save the current position to the given file */
13573 int
13574 SavePositionToFile (char *filename)
13575 {
13576     FILE *f;
13577     char buf[MSG_SIZ];
13578
13579     if (strcmp(filename, "-") == 0) {
13580         return SavePosition(stdout, 0, NULL);
13581     } else {
13582         f = fopen(filename, "a");
13583         if (f == NULL) {
13584             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13585             DisplayError(buf, errno);
13586             return FALSE;
13587         } else {
13588             safeStrCpy(buf, lastMsg, MSG_SIZ);
13589             DisplayMessage(_("Waiting for access to save file"), "");
13590             flock(fileno(f), LOCK_EX); // [HGM] lock
13591             DisplayMessage(_("Saving position"), "");
13592             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13593             SavePosition(f, 0, NULL);
13594             DisplayMessage(buf, "");
13595             return TRUE;
13596         }
13597     }
13598 }
13599
13600 /* Save the current position to the given open file and close the file */
13601 int
13602 SavePosition (FILE *f, int dummy, char *dummy2)
13603 {
13604     time_t tm;
13605     char *fen;
13606
13607     if (gameMode == EditPosition) EditPositionDone(TRUE);
13608     if (appData.oldSaveStyle) {
13609         tm = time((time_t *) NULL);
13610
13611         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13612         PrintOpponents(f);
13613         fprintf(f, "[--------------\n");
13614         PrintPosition(f, currentMove);
13615         fprintf(f, "--------------]\n");
13616     } else {
13617         fen = PositionToFEN(currentMove, NULL, 1);
13618         fprintf(f, "%s\n", fen);
13619         free(fen);
13620     }
13621     fclose(f);
13622     return TRUE;
13623 }
13624
13625 void
13626 ReloadCmailMsgEvent (int unregister)
13627 {
13628 #if !WIN32
13629     static char *inFilename = NULL;
13630     static char *outFilename;
13631     int i;
13632     struct stat inbuf, outbuf;
13633     int status;
13634
13635     /* Any registered moves are unregistered if unregister is set, */
13636     /* i.e. invoked by the signal handler */
13637     if (unregister) {
13638         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13639             cmailMoveRegistered[i] = FALSE;
13640             if (cmailCommentList[i] != NULL) {
13641                 free(cmailCommentList[i]);
13642                 cmailCommentList[i] = NULL;
13643             }
13644         }
13645         nCmailMovesRegistered = 0;
13646     }
13647
13648     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13649         cmailResult[i] = CMAIL_NOT_RESULT;
13650     }
13651     nCmailResults = 0;
13652
13653     if (inFilename == NULL) {
13654         /* Because the filenames are static they only get malloced once  */
13655         /* and they never get freed                                      */
13656         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13657         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13658
13659         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13660         sprintf(outFilename, "%s.out", appData.cmailGameName);
13661     }
13662
13663     status = stat(outFilename, &outbuf);
13664     if (status < 0) {
13665         cmailMailedMove = FALSE;
13666     } else {
13667         status = stat(inFilename, &inbuf);
13668         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13669     }
13670
13671     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13672        counts the games, notes how each one terminated, etc.
13673
13674        It would be nice to remove this kludge and instead gather all
13675        the information while building the game list.  (And to keep it
13676        in the game list nodes instead of having a bunch of fixed-size
13677        parallel arrays.)  Note this will require getting each game's
13678        termination from the PGN tags, as the game list builder does
13679        not process the game moves.  --mann
13680        */
13681     cmailMsgLoaded = TRUE;
13682     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13683
13684     /* Load first game in the file or popup game menu */
13685     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13686
13687 #endif /* !WIN32 */
13688     return;
13689 }
13690
13691 int
13692 RegisterMove ()
13693 {
13694     FILE *f;
13695     char string[MSG_SIZ];
13696
13697     if (   cmailMailedMove
13698         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13699         return TRUE;            /* Allow free viewing  */
13700     }
13701
13702     /* Unregister move to ensure that we don't leave RegisterMove        */
13703     /* with the move registered when the conditions for registering no   */
13704     /* longer hold                                                       */
13705     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13706         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13707         nCmailMovesRegistered --;
13708
13709         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13710           {
13711               free(cmailCommentList[lastLoadGameNumber - 1]);
13712               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13713           }
13714     }
13715
13716     if (cmailOldMove == -1) {
13717         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13718         return FALSE;
13719     }
13720
13721     if (currentMove > cmailOldMove + 1) {
13722         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13723         return FALSE;
13724     }
13725
13726     if (currentMove < cmailOldMove) {
13727         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13728         return FALSE;
13729     }
13730
13731     if (forwardMostMove > currentMove) {
13732         /* Silently truncate extra moves */
13733         TruncateGame();
13734     }
13735
13736     if (   (currentMove == cmailOldMove + 1)
13737         || (   (currentMove == cmailOldMove)
13738             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13739                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13740         if (gameInfo.result != GameUnfinished) {
13741             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13742         }
13743
13744         if (commentList[currentMove] != NULL) {
13745             cmailCommentList[lastLoadGameNumber - 1]
13746               = StrSave(commentList[currentMove]);
13747         }
13748         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13749
13750         if (appData.debugMode)
13751           fprintf(debugFP, "Saving %s for game %d\n",
13752                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13753
13754         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13755
13756         f = fopen(string, "w");
13757         if (appData.oldSaveStyle) {
13758             SaveGameOldStyle(f); /* also closes the file */
13759
13760             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13761             f = fopen(string, "w");
13762             SavePosition(f, 0, NULL); /* also closes the file */
13763         } else {
13764             fprintf(f, "{--------------\n");
13765             PrintPosition(f, currentMove);
13766             fprintf(f, "--------------}\n\n");
13767
13768             SaveGame(f, 0, NULL); /* also closes the file*/
13769         }
13770
13771         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13772         nCmailMovesRegistered ++;
13773     } else if (nCmailGames == 1) {
13774         DisplayError(_("You have not made a move yet"), 0);
13775         return FALSE;
13776     }
13777
13778     return TRUE;
13779 }
13780
13781 void
13782 MailMoveEvent ()
13783 {
13784 #if !WIN32
13785     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13786     FILE *commandOutput;
13787     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13788     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13789     int nBuffers;
13790     int i;
13791     int archived;
13792     char *arcDir;
13793
13794     if (! cmailMsgLoaded) {
13795         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13796         return;
13797     }
13798
13799     if (nCmailGames == nCmailResults) {
13800         DisplayError(_("No unfinished games"), 0);
13801         return;
13802     }
13803
13804 #if CMAIL_PROHIBIT_REMAIL
13805     if (cmailMailedMove) {
13806       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);
13807         DisplayError(msg, 0);
13808         return;
13809     }
13810 #endif
13811
13812     if (! (cmailMailedMove || RegisterMove())) return;
13813
13814     if (   cmailMailedMove
13815         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13816       snprintf(string, MSG_SIZ, partCommandString,
13817                appData.debugMode ? " -v" : "", appData.cmailGameName);
13818         commandOutput = popen(string, "r");
13819
13820         if (commandOutput == NULL) {
13821             DisplayError(_("Failed to invoke cmail"), 0);
13822         } else {
13823             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13824                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13825             }
13826             if (nBuffers > 1) {
13827                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13828                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13829                 nBytes = MSG_SIZ - 1;
13830             } else {
13831                 (void) memcpy(msg, buffer, nBytes);
13832             }
13833             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13834
13835             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13836                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13837
13838                 archived = TRUE;
13839                 for (i = 0; i < nCmailGames; i ++) {
13840                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13841                         archived = FALSE;
13842                     }
13843                 }
13844                 if (   archived
13845                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13846                         != NULL)) {
13847                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13848                            arcDir,
13849                            appData.cmailGameName,
13850                            gameInfo.date);
13851                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13852                     cmailMsgLoaded = FALSE;
13853                 }
13854             }
13855
13856             DisplayInformation(msg);
13857             pclose(commandOutput);
13858         }
13859     } else {
13860         if ((*cmailMsg) != '\0') {
13861             DisplayInformation(cmailMsg);
13862         }
13863     }
13864
13865     return;
13866 #endif /* !WIN32 */
13867 }
13868
13869 char *
13870 CmailMsg ()
13871 {
13872 #if WIN32
13873     return NULL;
13874 #else
13875     int  prependComma = 0;
13876     char number[5];
13877     char string[MSG_SIZ];       /* Space for game-list */
13878     int  i;
13879
13880     if (!cmailMsgLoaded) return "";
13881
13882     if (cmailMailedMove) {
13883       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13884     } else {
13885         /* Create a list of games left */
13886       snprintf(string, MSG_SIZ, "[");
13887         for (i = 0; i < nCmailGames; i ++) {
13888             if (! (   cmailMoveRegistered[i]
13889                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13890                 if (prependComma) {
13891                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13892                 } else {
13893                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13894                     prependComma = 1;
13895                 }
13896
13897                 strcat(string, number);
13898             }
13899         }
13900         strcat(string, "]");
13901
13902         if (nCmailMovesRegistered + nCmailResults == 0) {
13903             switch (nCmailGames) {
13904               case 1:
13905                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13906                 break;
13907
13908               case 2:
13909                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13910                 break;
13911
13912               default:
13913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13914                          nCmailGames);
13915                 break;
13916             }
13917         } else {
13918             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13919               case 1:
13920                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13921                          string);
13922                 break;
13923
13924               case 0:
13925                 if (nCmailResults == nCmailGames) {
13926                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13927                 } else {
13928                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13929                 }
13930                 break;
13931
13932               default:
13933                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13934                          string);
13935             }
13936         }
13937     }
13938     return cmailMsg;
13939 #endif /* WIN32 */
13940 }
13941
13942 void
13943 ResetGameEvent ()
13944 {
13945     if (gameMode == Training)
13946       SetTrainingModeOff();
13947
13948     Reset(TRUE, TRUE);
13949     cmailMsgLoaded = FALSE;
13950     if (appData.icsActive) {
13951       SendToICS(ics_prefix);
13952       SendToICS("refresh\n");
13953     }
13954 }
13955
13956 void
13957 ExitEvent (int status)
13958 {
13959     exiting++;
13960     if (exiting > 2) {
13961       /* Give up on clean exit */
13962       exit(status);
13963     }
13964     if (exiting > 1) {
13965       /* Keep trying for clean exit */
13966       return;
13967     }
13968
13969     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13970
13971     if (telnetISR != NULL) {
13972       RemoveInputSource(telnetISR);
13973     }
13974     if (icsPR != NoProc) {
13975       DestroyChildProcess(icsPR, TRUE);
13976     }
13977
13978     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13979     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13980
13981     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13982     /* make sure this other one finishes before killing it!                  */
13983     if(endingGame) { int count = 0;
13984         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13985         while(endingGame && count++ < 10) DoSleep(1);
13986         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13987     }
13988
13989     /* Kill off chess programs */
13990     if (first.pr != NoProc) {
13991         ExitAnalyzeMode();
13992
13993         DoSleep( appData.delayBeforeQuit );
13994         SendToProgram("quit\n", &first);
13995         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13996     }
13997     if (second.pr != NoProc) {
13998         DoSleep( appData.delayBeforeQuit );
13999         SendToProgram("quit\n", &second);
14000         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14001     }
14002     if (first.isr != NULL) {
14003         RemoveInputSource(first.isr);
14004     }
14005     if (second.isr != NULL) {
14006         RemoveInputSource(second.isr);
14007     }
14008
14009     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14010     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14011
14012     ShutDownFrontEnd();
14013     exit(status);
14014 }
14015
14016 void
14017 PauseEngine (ChessProgramState *cps)
14018 {
14019     SendToProgram("pause\n", cps);
14020     cps->pause = 2;
14021 }
14022
14023 void
14024 UnPauseEngine (ChessProgramState *cps)
14025 {
14026     SendToProgram("resume\n", cps);
14027     cps->pause = 1;
14028 }
14029
14030 void
14031 PauseEvent ()
14032 {
14033     if (appData.debugMode)
14034         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14035     if (pausing) {
14036         pausing = FALSE;
14037         ModeHighlight();
14038         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14039             StartClocks();
14040             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14041                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14042                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14043             }
14044             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14045             HandleMachineMove(stashedInputMove, stalledEngine);
14046             stalledEngine = NULL;
14047             return;
14048         }
14049         if (gameMode == MachinePlaysWhite ||
14050             gameMode == TwoMachinesPlay   ||
14051             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14052             if(first.pause)  UnPauseEngine(&first);
14053             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14054             if(second.pause) UnPauseEngine(&second);
14055             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14056             StartClocks();
14057         } else {
14058             DisplayBothClocks();
14059         }
14060         if (gameMode == PlayFromGameFile) {
14061             if (appData.timeDelay >= 0)
14062                 AutoPlayGameLoop();
14063         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14064             Reset(FALSE, TRUE);
14065             SendToICS(ics_prefix);
14066             SendToICS("refresh\n");
14067         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14068             ForwardInner(forwardMostMove);
14069         }
14070         pauseExamInvalid = FALSE;
14071     } else {
14072         switch (gameMode) {
14073           default:
14074             return;
14075           case IcsExamining:
14076             pauseExamForwardMostMove = forwardMostMove;
14077             pauseExamInvalid = FALSE;
14078             /* fall through */
14079           case IcsObserving:
14080           case IcsPlayingWhite:
14081           case IcsPlayingBlack:
14082             pausing = TRUE;
14083             ModeHighlight();
14084             return;
14085           case PlayFromGameFile:
14086             (void) StopLoadGameTimer();
14087             pausing = TRUE;
14088             ModeHighlight();
14089             break;
14090           case BeginningOfGame:
14091             if (appData.icsActive) return;
14092             /* else fall through */
14093           case MachinePlaysWhite:
14094           case MachinePlaysBlack:
14095           case TwoMachinesPlay:
14096             if (forwardMostMove == 0)
14097               return;           /* don't pause if no one has moved */
14098             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14099                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14100                 if(onMove->pause) {           // thinking engine can be paused
14101                     PauseEngine(onMove);      // do it
14102                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14103                         PauseEngine(onMove->other);
14104                     else
14105                         SendToProgram("easy\n", onMove->other);
14106                     StopClocks();
14107                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14108             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14109                 if(first.pause) {
14110                     PauseEngine(&first);
14111                     StopClocks();
14112                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14113             } else { // human on move, pause pondering by either method
14114                 if(first.pause)
14115                     PauseEngine(&first);
14116                 else if(appData.ponderNextMove)
14117                     SendToProgram("easy\n", &first);
14118                 StopClocks();
14119             }
14120             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14121           case AnalyzeMode:
14122             pausing = TRUE;
14123             ModeHighlight();
14124             break;
14125         }
14126     }
14127 }
14128
14129 void
14130 EditCommentEvent ()
14131 {
14132     char title[MSG_SIZ];
14133
14134     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14135       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14136     } else {
14137       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14138                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14139                parseList[currentMove - 1]);
14140     }
14141
14142     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14143 }
14144
14145
14146 void
14147 EditTagsEvent ()
14148 {
14149     char *tags = PGNTags(&gameInfo);
14150     bookUp = FALSE;
14151     EditTagsPopUp(tags, NULL);
14152     free(tags);
14153 }
14154
14155 void
14156 ToggleSecond ()
14157 {
14158   if(second.analyzing) {
14159     SendToProgram("exit\n", &second);
14160     second.analyzing = FALSE;
14161   } else {
14162     if (second.pr == NoProc) StartChessProgram(&second);
14163     InitChessProgram(&second, FALSE);
14164     FeedMovesToProgram(&second, currentMove);
14165
14166     SendToProgram("analyze\n", &second);
14167     second.analyzing = TRUE;
14168   }
14169 }
14170
14171 /* Toggle ShowThinking */
14172 void
14173 ToggleShowThinking()
14174 {
14175   appData.showThinking = !appData.showThinking;
14176   ShowThinkingEvent();
14177 }
14178
14179 int
14180 AnalyzeModeEvent ()
14181 {
14182     char buf[MSG_SIZ];
14183
14184     if (!first.analysisSupport) {
14185       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14186       DisplayError(buf, 0);
14187       return 0;
14188     }
14189     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14190     if (appData.icsActive) {
14191         if (gameMode != IcsObserving) {
14192           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14193             DisplayError(buf, 0);
14194             /* secure check */
14195             if (appData.icsEngineAnalyze) {
14196                 if (appData.debugMode)
14197                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14198                 ExitAnalyzeMode();
14199                 ModeHighlight();
14200             }
14201             return 0;
14202         }
14203         /* if enable, user wants to disable icsEngineAnalyze */
14204         if (appData.icsEngineAnalyze) {
14205                 ExitAnalyzeMode();
14206                 ModeHighlight();
14207                 return 0;
14208         }
14209         appData.icsEngineAnalyze = TRUE;
14210         if (appData.debugMode)
14211             fprintf(debugFP, "ICS engine analyze starting... \n");
14212     }
14213
14214     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14215     if (appData.noChessProgram || gameMode == AnalyzeMode)
14216       return 0;
14217
14218     if (gameMode != AnalyzeFile) {
14219         if (!appData.icsEngineAnalyze) {
14220                EditGameEvent();
14221                if (gameMode != EditGame) return 0;
14222         }
14223         if (!appData.showThinking) ToggleShowThinking();
14224         ResurrectChessProgram();
14225         SendToProgram("analyze\n", &first);
14226         first.analyzing = TRUE;
14227         /*first.maybeThinking = TRUE;*/
14228         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14229         EngineOutputPopUp();
14230     }
14231     if (!appData.icsEngineAnalyze) {
14232         gameMode = AnalyzeMode;
14233         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14234     }
14235     pausing = FALSE;
14236     ModeHighlight();
14237     SetGameInfo();
14238
14239     StartAnalysisClock();
14240     GetTimeMark(&lastNodeCountTime);
14241     lastNodeCount = 0;
14242     return 1;
14243 }
14244
14245 void
14246 AnalyzeFileEvent ()
14247 {
14248     if (appData.noChessProgram || gameMode == AnalyzeFile)
14249       return;
14250
14251     if (!first.analysisSupport) {
14252       char buf[MSG_SIZ];
14253       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14254       DisplayError(buf, 0);
14255       return;
14256     }
14257
14258     if (gameMode != AnalyzeMode) {
14259         keepInfo = 1; // mere annotating should not alter PGN tags
14260         EditGameEvent();
14261         keepInfo = 0;
14262         if (gameMode != EditGame) return;
14263         if (!appData.showThinking) ToggleShowThinking();
14264         ResurrectChessProgram();
14265         SendToProgram("analyze\n", &first);
14266         first.analyzing = TRUE;
14267         /*first.maybeThinking = TRUE;*/
14268         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14269         EngineOutputPopUp();
14270     }
14271     gameMode = AnalyzeFile;
14272     pausing = FALSE;
14273     ModeHighlight();
14274
14275     StartAnalysisClock();
14276     GetTimeMark(&lastNodeCountTime);
14277     lastNodeCount = 0;
14278     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14279     AnalysisPeriodicEvent(1);
14280 }
14281
14282 void
14283 MachineWhiteEvent ()
14284 {
14285     char buf[MSG_SIZ];
14286     char *bookHit = NULL;
14287
14288     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14289       return;
14290
14291
14292     if (gameMode == PlayFromGameFile ||
14293         gameMode == TwoMachinesPlay  ||
14294         gameMode == Training         ||
14295         gameMode == AnalyzeMode      ||
14296         gameMode == EndOfGame)
14297         EditGameEvent();
14298
14299     if (gameMode == EditPosition)
14300         EditPositionDone(TRUE);
14301
14302     if (!WhiteOnMove(currentMove)) {
14303         DisplayError(_("It is not White's turn"), 0);
14304         return;
14305     }
14306
14307     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14308       ExitAnalyzeMode();
14309
14310     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14311         gameMode == AnalyzeFile)
14312         TruncateGame();
14313
14314     ResurrectChessProgram();    /* in case it isn't running */
14315     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14316         gameMode = MachinePlaysWhite;
14317         ResetClocks();
14318     } else
14319     gameMode = MachinePlaysWhite;
14320     pausing = FALSE;
14321     ModeHighlight();
14322     SetGameInfo();
14323     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14324     DisplayTitle(buf);
14325     if (first.sendName) {
14326       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14327       SendToProgram(buf, &first);
14328     }
14329     if (first.sendTime) {
14330       if (first.useColors) {
14331         SendToProgram("black\n", &first); /*gnu kludge*/
14332       }
14333       SendTimeRemaining(&first, TRUE);
14334     }
14335     if (first.useColors) {
14336       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14337     }
14338     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14339     SetMachineThinkingEnables();
14340     first.maybeThinking = TRUE;
14341     StartClocks();
14342     firstMove = FALSE;
14343
14344     if (appData.autoFlipView && !flipView) {
14345       flipView = !flipView;
14346       DrawPosition(FALSE, NULL);
14347       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14348     }
14349
14350     if(bookHit) { // [HGM] book: simulate book reply
14351         static char bookMove[MSG_SIZ]; // a bit generous?
14352
14353         programStats.nodes = programStats.depth = programStats.time =
14354         programStats.score = programStats.got_only_move = 0;
14355         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14356
14357         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14358         strcat(bookMove, bookHit);
14359         HandleMachineMove(bookMove, &first);
14360     }
14361 }
14362
14363 void
14364 MachineBlackEvent ()
14365 {
14366   char buf[MSG_SIZ];
14367   char *bookHit = NULL;
14368
14369     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14370         return;
14371
14372
14373     if (gameMode == PlayFromGameFile ||
14374         gameMode == TwoMachinesPlay  ||
14375         gameMode == Training         ||
14376         gameMode == AnalyzeMode      ||
14377         gameMode == EndOfGame)
14378         EditGameEvent();
14379
14380     if (gameMode == EditPosition)
14381         EditPositionDone(TRUE);
14382
14383     if (WhiteOnMove(currentMove)) {
14384         DisplayError(_("It is not Black's turn"), 0);
14385         return;
14386     }
14387
14388     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14389       ExitAnalyzeMode();
14390
14391     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14392         gameMode == AnalyzeFile)
14393         TruncateGame();
14394
14395     ResurrectChessProgram();    /* in case it isn't running */
14396     gameMode = MachinePlaysBlack;
14397     pausing = FALSE;
14398     ModeHighlight();
14399     SetGameInfo();
14400     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14401     DisplayTitle(buf);
14402     if (first.sendName) {
14403       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14404       SendToProgram(buf, &first);
14405     }
14406     if (first.sendTime) {
14407       if (first.useColors) {
14408         SendToProgram("white\n", &first); /*gnu kludge*/
14409       }
14410       SendTimeRemaining(&first, FALSE);
14411     }
14412     if (first.useColors) {
14413       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14414     }
14415     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14416     SetMachineThinkingEnables();
14417     first.maybeThinking = TRUE;
14418     StartClocks();
14419
14420     if (appData.autoFlipView && flipView) {
14421       flipView = !flipView;
14422       DrawPosition(FALSE, NULL);
14423       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14424     }
14425     if(bookHit) { // [HGM] book: simulate book reply
14426         static char bookMove[MSG_SIZ]; // a bit generous?
14427
14428         programStats.nodes = programStats.depth = programStats.time =
14429         programStats.score = programStats.got_only_move = 0;
14430         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14431
14432         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14433         strcat(bookMove, bookHit);
14434         HandleMachineMove(bookMove, &first);
14435     }
14436 }
14437
14438
14439 void
14440 DisplayTwoMachinesTitle ()
14441 {
14442     char buf[MSG_SIZ];
14443     if (appData.matchGames > 0) {
14444         if(appData.tourneyFile[0]) {
14445           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14446                    gameInfo.white, _("vs."), gameInfo.black,
14447                    nextGame+1, appData.matchGames+1,
14448                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14449         } else
14450         if (first.twoMachinesColor[0] == 'w') {
14451           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14452                    gameInfo.white, _("vs."),  gameInfo.black,
14453                    first.matchWins, second.matchWins,
14454                    matchGame - 1 - (first.matchWins + second.matchWins));
14455         } else {
14456           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14457                    gameInfo.white, _("vs."), gameInfo.black,
14458                    second.matchWins, first.matchWins,
14459                    matchGame - 1 - (first.matchWins + second.matchWins));
14460         }
14461     } else {
14462       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14463     }
14464     DisplayTitle(buf);
14465 }
14466
14467 void
14468 SettingsMenuIfReady ()
14469 {
14470   if (second.lastPing != second.lastPong) {
14471     DisplayMessage("", _("Waiting for second chess program"));
14472     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14473     return;
14474   }
14475   ThawUI();
14476   DisplayMessage("", "");
14477   SettingsPopUp(&second);
14478 }
14479
14480 int
14481 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14482 {
14483     char buf[MSG_SIZ];
14484     if (cps->pr == NoProc) {
14485         StartChessProgram(cps);
14486         if (cps->protocolVersion == 1) {
14487           retry();
14488           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14489         } else {
14490           /* kludge: allow timeout for initial "feature" command */
14491           if(retry != TwoMachinesEventIfReady) FreezeUI();
14492           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14493           DisplayMessage("", buf);
14494           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14495         }
14496         return 1;
14497     }
14498     return 0;
14499 }
14500
14501 void
14502 TwoMachinesEvent P((void))
14503 {
14504     int i;
14505     char buf[MSG_SIZ];
14506     ChessProgramState *onmove;
14507     char *bookHit = NULL;
14508     static int stalling = 0;
14509     TimeMark now;
14510     long wait;
14511
14512     if (appData.noChessProgram) return;
14513
14514     switch (gameMode) {
14515       case TwoMachinesPlay:
14516         return;
14517       case MachinePlaysWhite:
14518       case MachinePlaysBlack:
14519         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14520             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14521             return;
14522         }
14523         /* fall through */
14524       case BeginningOfGame:
14525       case PlayFromGameFile:
14526       case EndOfGame:
14527         EditGameEvent();
14528         if (gameMode != EditGame) return;
14529         break;
14530       case EditPosition:
14531         EditPositionDone(TRUE);
14532         break;
14533       case AnalyzeMode:
14534       case AnalyzeFile:
14535         ExitAnalyzeMode();
14536         break;
14537       case EditGame:
14538       default:
14539         break;
14540     }
14541
14542 //    forwardMostMove = currentMove;
14543     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14544     startingEngine = TRUE;
14545
14546     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14547
14548     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14549     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14550       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14551       return;
14552     }
14553     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14554
14555     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14556                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14557         startingEngine = FALSE;
14558         DisplayError("second engine does not play this", 0);
14559         return;
14560     }
14561
14562     if(!stalling) {
14563       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14564       SendToProgram("force\n", &second);
14565       stalling = 1;
14566       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14567       return;
14568     }
14569     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14570     if(appData.matchPause>10000 || appData.matchPause<10)
14571                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14572     wait = SubtractTimeMarks(&now, &pauseStart);
14573     if(wait < appData.matchPause) {
14574         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14575         return;
14576     }
14577     // we are now committed to starting the game
14578     stalling = 0;
14579     DisplayMessage("", "");
14580     if (startedFromSetupPosition) {
14581         SendBoard(&second, backwardMostMove);
14582     if (appData.debugMode) {
14583         fprintf(debugFP, "Two Machines\n");
14584     }
14585     }
14586     for (i = backwardMostMove; i < forwardMostMove; i++) {
14587         SendMoveToProgram(i, &second);
14588     }
14589
14590     gameMode = TwoMachinesPlay;
14591     pausing = startingEngine = FALSE;
14592     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14593     SetGameInfo();
14594     DisplayTwoMachinesTitle();
14595     firstMove = TRUE;
14596     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14597         onmove = &first;
14598     } else {
14599         onmove = &second;
14600     }
14601     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14602     SendToProgram(first.computerString, &first);
14603     if (first.sendName) {
14604       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14605       SendToProgram(buf, &first);
14606     }
14607     SendToProgram(second.computerString, &second);
14608     if (second.sendName) {
14609       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14610       SendToProgram(buf, &second);
14611     }
14612
14613     ResetClocks();
14614     if (!first.sendTime || !second.sendTime) {
14615         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14616         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14617     }
14618     if (onmove->sendTime) {
14619       if (onmove->useColors) {
14620         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14621       }
14622       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14623     }
14624     if (onmove->useColors) {
14625       SendToProgram(onmove->twoMachinesColor, onmove);
14626     }
14627     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14628 //    SendToProgram("go\n", onmove);
14629     onmove->maybeThinking = TRUE;
14630     SetMachineThinkingEnables();
14631
14632     StartClocks();
14633
14634     if(bookHit) { // [HGM] book: simulate book reply
14635         static char bookMove[MSG_SIZ]; // a bit generous?
14636
14637         programStats.nodes = programStats.depth = programStats.time =
14638         programStats.score = programStats.got_only_move = 0;
14639         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14640
14641         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14642         strcat(bookMove, bookHit);
14643         savedMessage = bookMove; // args for deferred call
14644         savedState = onmove;
14645         ScheduleDelayedEvent(DeferredBookMove, 1);
14646     }
14647 }
14648
14649 void
14650 TrainingEvent ()
14651 {
14652     if (gameMode == Training) {
14653       SetTrainingModeOff();
14654       gameMode = PlayFromGameFile;
14655       DisplayMessage("", _("Training mode off"));
14656     } else {
14657       gameMode = Training;
14658       animateTraining = appData.animate;
14659
14660       /* make sure we are not already at the end of the game */
14661       if (currentMove < forwardMostMove) {
14662         SetTrainingModeOn();
14663         DisplayMessage("", _("Training mode on"));
14664       } else {
14665         gameMode = PlayFromGameFile;
14666         DisplayError(_("Already at end of game"), 0);
14667       }
14668     }
14669     ModeHighlight();
14670 }
14671
14672 void
14673 IcsClientEvent ()
14674 {
14675     if (!appData.icsActive) return;
14676     switch (gameMode) {
14677       case IcsPlayingWhite:
14678       case IcsPlayingBlack:
14679       case IcsObserving:
14680       case IcsIdle:
14681       case BeginningOfGame:
14682       case IcsExamining:
14683         return;
14684
14685       case EditGame:
14686         break;
14687
14688       case EditPosition:
14689         EditPositionDone(TRUE);
14690         break;
14691
14692       case AnalyzeMode:
14693       case AnalyzeFile:
14694         ExitAnalyzeMode();
14695         break;
14696
14697       default:
14698         EditGameEvent();
14699         break;
14700     }
14701
14702     gameMode = IcsIdle;
14703     ModeHighlight();
14704     return;
14705 }
14706
14707 void
14708 EditGameEvent ()
14709 {
14710     int i;
14711
14712     switch (gameMode) {
14713       case Training:
14714         SetTrainingModeOff();
14715         break;
14716       case MachinePlaysWhite:
14717       case MachinePlaysBlack:
14718       case BeginningOfGame:
14719         SendToProgram("force\n", &first);
14720         SetUserThinkingEnables();
14721         break;
14722       case PlayFromGameFile:
14723         (void) StopLoadGameTimer();
14724         if (gameFileFP != NULL) {
14725             gameFileFP = NULL;
14726         }
14727         break;
14728       case EditPosition:
14729         EditPositionDone(TRUE);
14730         break;
14731       case AnalyzeMode:
14732       case AnalyzeFile:
14733         ExitAnalyzeMode();
14734         SendToProgram("force\n", &first);
14735         break;
14736       case TwoMachinesPlay:
14737         GameEnds(EndOfFile, NULL, GE_PLAYER);
14738         ResurrectChessProgram();
14739         SetUserThinkingEnables();
14740         break;
14741       case EndOfGame:
14742         ResurrectChessProgram();
14743         break;
14744       case IcsPlayingBlack:
14745       case IcsPlayingWhite:
14746         DisplayError(_("Warning: You are still playing a game"), 0);
14747         break;
14748       case IcsObserving:
14749         DisplayError(_("Warning: You are still observing a game"), 0);
14750         break;
14751       case IcsExamining:
14752         DisplayError(_("Warning: You are still examining a game"), 0);
14753         break;
14754       case IcsIdle:
14755         break;
14756       case EditGame:
14757       default:
14758         return;
14759     }
14760
14761     pausing = FALSE;
14762     StopClocks();
14763     first.offeredDraw = second.offeredDraw = 0;
14764
14765     if (gameMode == PlayFromGameFile) {
14766         whiteTimeRemaining = timeRemaining[0][currentMove];
14767         blackTimeRemaining = timeRemaining[1][currentMove];
14768         DisplayTitle("");
14769     }
14770
14771     if (gameMode == MachinePlaysWhite ||
14772         gameMode == MachinePlaysBlack ||
14773         gameMode == TwoMachinesPlay ||
14774         gameMode == EndOfGame) {
14775         i = forwardMostMove;
14776         while (i > currentMove) {
14777             SendToProgram("undo\n", &first);
14778             i--;
14779         }
14780         if(!adjustedClock) {
14781         whiteTimeRemaining = timeRemaining[0][currentMove];
14782         blackTimeRemaining = timeRemaining[1][currentMove];
14783         DisplayBothClocks();
14784         }
14785         if (whiteFlag || blackFlag) {
14786             whiteFlag = blackFlag = 0;
14787         }
14788         DisplayTitle("");
14789     }
14790
14791     gameMode = EditGame;
14792     ModeHighlight();
14793     SetGameInfo();
14794 }
14795
14796
14797 void
14798 EditPositionEvent ()
14799 {
14800     if (gameMode == EditPosition) {
14801         EditGameEvent();
14802         return;
14803     }
14804
14805     EditGameEvent();
14806     if (gameMode != EditGame) return;
14807
14808     gameMode = EditPosition;
14809     ModeHighlight();
14810     SetGameInfo();
14811     if (currentMove > 0)
14812       CopyBoard(boards[0], boards[currentMove]);
14813
14814     blackPlaysFirst = !WhiteOnMove(currentMove);
14815     ResetClocks();
14816     currentMove = forwardMostMove = backwardMostMove = 0;
14817     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14818     DisplayMove(-1);
14819     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14820 }
14821
14822 void
14823 ExitAnalyzeMode ()
14824 {
14825     /* [DM] icsEngineAnalyze - possible call from other functions */
14826     if (appData.icsEngineAnalyze) {
14827         appData.icsEngineAnalyze = FALSE;
14828
14829         DisplayMessage("",_("Close ICS engine analyze..."));
14830     }
14831     if (first.analysisSupport && first.analyzing) {
14832       SendToBoth("exit\n");
14833       first.analyzing = second.analyzing = FALSE;
14834     }
14835     thinkOutput[0] = NULLCHAR;
14836 }
14837
14838 void
14839 EditPositionDone (Boolean fakeRights)
14840 {
14841     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14842
14843     startedFromSetupPosition = TRUE;
14844     InitChessProgram(&first, FALSE);
14845     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14846       boards[0][EP_STATUS] = EP_NONE;
14847       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14848       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14849         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14850         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14851       } else boards[0][CASTLING][2] = NoRights;
14852       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14853         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14854         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14855       } else boards[0][CASTLING][5] = NoRights;
14856       if(gameInfo.variant == VariantSChess) {
14857         int i;
14858         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14859           boards[0][VIRGIN][i] = 0;
14860           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14861           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14862         }
14863       }
14864     }
14865     SendToProgram("force\n", &first);
14866     if (blackPlaysFirst) {
14867         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14868         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14869         currentMove = forwardMostMove = backwardMostMove = 1;
14870         CopyBoard(boards[1], boards[0]);
14871     } else {
14872         currentMove = forwardMostMove = backwardMostMove = 0;
14873     }
14874     SendBoard(&first, forwardMostMove);
14875     if (appData.debugMode) {
14876         fprintf(debugFP, "EditPosDone\n");
14877     }
14878     DisplayTitle("");
14879     DisplayMessage("", "");
14880     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14881     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14882     gameMode = EditGame;
14883     ModeHighlight();
14884     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14885     ClearHighlights(); /* [AS] */
14886 }
14887
14888 /* Pause for `ms' milliseconds */
14889 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14890 void
14891 TimeDelay (long ms)
14892 {
14893     TimeMark m1, m2;
14894
14895     GetTimeMark(&m1);
14896     do {
14897         GetTimeMark(&m2);
14898     } while (SubtractTimeMarks(&m2, &m1) < ms);
14899 }
14900
14901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14902 void
14903 SendMultiLineToICS (char *buf)
14904 {
14905     char temp[MSG_SIZ+1], *p;
14906     int len;
14907
14908     len = strlen(buf);
14909     if (len > MSG_SIZ)
14910       len = MSG_SIZ;
14911
14912     strncpy(temp, buf, len);
14913     temp[len] = 0;
14914
14915     p = temp;
14916     while (*p) {
14917         if (*p == '\n' || *p == '\r')
14918           *p = ' ';
14919         ++p;
14920     }
14921
14922     strcat(temp, "\n");
14923     SendToICS(temp);
14924     SendToPlayer(temp, strlen(temp));
14925 }
14926
14927 void
14928 SetWhiteToPlayEvent ()
14929 {
14930     if (gameMode == EditPosition) {
14931         blackPlaysFirst = FALSE;
14932         DisplayBothClocks();    /* works because currentMove is 0 */
14933     } else if (gameMode == IcsExamining) {
14934         SendToICS(ics_prefix);
14935         SendToICS("tomove white\n");
14936     }
14937 }
14938
14939 void
14940 SetBlackToPlayEvent ()
14941 {
14942     if (gameMode == EditPosition) {
14943         blackPlaysFirst = TRUE;
14944         currentMove = 1;        /* kludge */
14945         DisplayBothClocks();
14946         currentMove = 0;
14947     } else if (gameMode == IcsExamining) {
14948         SendToICS(ics_prefix);
14949         SendToICS("tomove black\n");
14950     }
14951 }
14952
14953 void
14954 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14955 {
14956     char buf[MSG_SIZ];
14957     ChessSquare piece = boards[0][y][x];
14958     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14959     static int lastVariant;
14960
14961     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14962
14963     switch (selection) {
14964       case ClearBoard:
14965         CopyBoard(currentBoard, boards[0]);
14966         CopyBoard(menuBoard, initialPosition);
14967         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14968             SendToICS(ics_prefix);
14969             SendToICS("bsetup clear\n");
14970         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14971             SendToICS(ics_prefix);
14972             SendToICS("clearboard\n");
14973         } else {
14974             int nonEmpty = 0;
14975             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14976                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14977                 for (y = 0; y < BOARD_HEIGHT; y++) {
14978                     if (gameMode == IcsExamining) {
14979                         if (boards[currentMove][y][x] != EmptySquare) {
14980                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14981                                     AAA + x, ONE + y);
14982                             SendToICS(buf);
14983                         }
14984                     } else {
14985                         if(boards[0][y][x] != p) nonEmpty++;
14986                         boards[0][y][x] = p;
14987                     }
14988                 }
14989                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14990             }
14991             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14992                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14993                     ChessSquare p = menuBoard[0][x];
14994                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14995                     p = menuBoard[BOARD_HEIGHT-1][x];
14996                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14997                 }
14998                 DisplayMessage("Clicking clock again restores position", "");
14999                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15000                 if(!nonEmpty) { // asked to clear an empty board
15001                     CopyBoard(boards[0], menuBoard);
15002                 } else
15003                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15004                     CopyBoard(boards[0], initialPosition);
15005                 } else
15006                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15007                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15008                     CopyBoard(boards[0], erasedBoard);
15009                 } else
15010                     CopyBoard(erasedBoard, currentBoard);
15011
15012             }
15013         }
15014         if (gameMode == EditPosition) {
15015             DrawPosition(FALSE, boards[0]);
15016         }
15017         break;
15018
15019       case WhitePlay:
15020         SetWhiteToPlayEvent();
15021         break;
15022
15023       case BlackPlay:
15024         SetBlackToPlayEvent();
15025         break;
15026
15027       case EmptySquare:
15028         if (gameMode == IcsExamining) {
15029             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15030             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15031             SendToICS(buf);
15032         } else {
15033             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15034                 if(x == BOARD_LEFT-2) {
15035                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15036                     boards[0][y][1] = 0;
15037                 } else
15038                 if(x == BOARD_RGHT+1) {
15039                     if(y >= gameInfo.holdingsSize) break;
15040                     boards[0][y][BOARD_WIDTH-2] = 0;
15041                 } else break;
15042             }
15043             boards[0][y][x] = EmptySquare;
15044             DrawPosition(FALSE, boards[0]);
15045         }
15046         break;
15047
15048       case PromotePiece:
15049         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15050            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15051             selection = (ChessSquare) (PROMOTED piece);
15052         } else if(piece == EmptySquare) selection = WhiteSilver;
15053         else selection = (ChessSquare)((int)piece - 1);
15054         goto defaultlabel;
15055
15056       case DemotePiece:
15057         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15058            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15059             selection = (ChessSquare) (DEMOTED piece);
15060         } else if(piece == EmptySquare) selection = BlackSilver;
15061         else selection = (ChessSquare)((int)piece + 1);
15062         goto defaultlabel;
15063
15064       case WhiteQueen:
15065       case BlackQueen:
15066         if(gameInfo.variant == VariantShatranj ||
15067            gameInfo.variant == VariantXiangqi  ||
15068            gameInfo.variant == VariantCourier  ||
15069            gameInfo.variant == VariantASEAN    ||
15070            gameInfo.variant == VariantMakruk     )
15071             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15072         goto defaultlabel;
15073
15074       case WhiteKing:
15075       case BlackKing:
15076         if(gameInfo.variant == VariantXiangqi)
15077             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15078         if(gameInfo.variant == VariantKnightmate)
15079             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15080       default:
15081         defaultlabel:
15082         if (gameMode == IcsExamining) {
15083             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15084             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15085                      PieceToChar(selection), AAA + x, ONE + y);
15086             SendToICS(buf);
15087         } else {
15088             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15089                 int n;
15090                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15091                     n = PieceToNumber(selection - BlackPawn);
15092                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15093                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15094                     boards[0][BOARD_HEIGHT-1-n][1]++;
15095                 } else
15096                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15097                     n = PieceToNumber(selection);
15098                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15099                     boards[0][n][BOARD_WIDTH-1] = selection;
15100                     boards[0][n][BOARD_WIDTH-2]++;
15101                 }
15102             } else
15103             boards[0][y][x] = selection;
15104             DrawPosition(TRUE, boards[0]);
15105             ClearHighlights();
15106             fromX = fromY = -1;
15107         }
15108         break;
15109     }
15110 }
15111
15112
15113 void
15114 DropMenuEvent (ChessSquare selection, int x, int y)
15115 {
15116     ChessMove moveType;
15117
15118     switch (gameMode) {
15119       case IcsPlayingWhite:
15120       case MachinePlaysBlack:
15121         if (!WhiteOnMove(currentMove)) {
15122             DisplayMoveError(_("It is Black's turn"));
15123             return;
15124         }
15125         moveType = WhiteDrop;
15126         break;
15127       case IcsPlayingBlack:
15128       case MachinePlaysWhite:
15129         if (WhiteOnMove(currentMove)) {
15130             DisplayMoveError(_("It is White's turn"));
15131             return;
15132         }
15133         moveType = BlackDrop;
15134         break;
15135       case EditGame:
15136         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15137         break;
15138       default:
15139         return;
15140     }
15141
15142     if (moveType == BlackDrop && selection < BlackPawn) {
15143       selection = (ChessSquare) ((int) selection
15144                                  + (int) BlackPawn - (int) WhitePawn);
15145     }
15146     if (boards[currentMove][y][x] != EmptySquare) {
15147         DisplayMoveError(_("That square is occupied"));
15148         return;
15149     }
15150
15151     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15152 }
15153
15154 void
15155 AcceptEvent ()
15156 {
15157     /* Accept a pending offer of any kind from opponent */
15158
15159     if (appData.icsActive) {
15160         SendToICS(ics_prefix);
15161         SendToICS("accept\n");
15162     } else if (cmailMsgLoaded) {
15163         if (currentMove == cmailOldMove &&
15164             commentList[cmailOldMove] != NULL &&
15165             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15166                    "Black offers a draw" : "White offers a draw")) {
15167             TruncateGame();
15168             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15169             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15170         } else {
15171             DisplayError(_("There is no pending offer on this move"), 0);
15172             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15173         }
15174     } else {
15175         /* Not used for offers from chess program */
15176     }
15177 }
15178
15179 void
15180 DeclineEvent ()
15181 {
15182     /* Decline a pending offer of any kind from opponent */
15183
15184     if (appData.icsActive) {
15185         SendToICS(ics_prefix);
15186         SendToICS("decline\n");
15187     } else if (cmailMsgLoaded) {
15188         if (currentMove == cmailOldMove &&
15189             commentList[cmailOldMove] != NULL &&
15190             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15191                    "Black offers a draw" : "White offers a draw")) {
15192 #ifdef NOTDEF
15193             AppendComment(cmailOldMove, "Draw declined", TRUE);
15194             DisplayComment(cmailOldMove - 1, "Draw declined");
15195 #endif /*NOTDEF*/
15196         } else {
15197             DisplayError(_("There is no pending offer on this move"), 0);
15198         }
15199     } else {
15200         /* Not used for offers from chess program */
15201     }
15202 }
15203
15204 void
15205 RematchEvent ()
15206 {
15207     /* Issue ICS rematch command */
15208     if (appData.icsActive) {
15209         SendToICS(ics_prefix);
15210         SendToICS("rematch\n");
15211     }
15212 }
15213
15214 void
15215 CallFlagEvent ()
15216 {
15217     /* Call your opponent's flag (claim a win on time) */
15218     if (appData.icsActive) {
15219         SendToICS(ics_prefix);
15220         SendToICS("flag\n");
15221     } else {
15222         switch (gameMode) {
15223           default:
15224             return;
15225           case MachinePlaysWhite:
15226             if (whiteFlag) {
15227                 if (blackFlag)
15228                   GameEnds(GameIsDrawn, "Both players ran out of time",
15229                            GE_PLAYER);
15230                 else
15231                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15232             } else {
15233                 DisplayError(_("Your opponent is not out of time"), 0);
15234             }
15235             break;
15236           case MachinePlaysBlack:
15237             if (blackFlag) {
15238                 if (whiteFlag)
15239                   GameEnds(GameIsDrawn, "Both players ran out of time",
15240                            GE_PLAYER);
15241                 else
15242                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15243             } else {
15244                 DisplayError(_("Your opponent is not out of time"), 0);
15245             }
15246             break;
15247         }
15248     }
15249 }
15250
15251 void
15252 ClockClick (int which)
15253 {       // [HGM] code moved to back-end from winboard.c
15254         if(which) { // black clock
15255           if (gameMode == EditPosition || gameMode == IcsExamining) {
15256             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15257             SetBlackToPlayEvent();
15258           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15259           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15260           } else if (shiftKey) {
15261             AdjustClock(which, -1);
15262           } else if (gameMode == IcsPlayingWhite ||
15263                      gameMode == MachinePlaysBlack) {
15264             CallFlagEvent();
15265           }
15266         } else { // white clock
15267           if (gameMode == EditPosition || gameMode == IcsExamining) {
15268             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15269             SetWhiteToPlayEvent();
15270           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15271           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15272           } else if (shiftKey) {
15273             AdjustClock(which, -1);
15274           } else if (gameMode == IcsPlayingBlack ||
15275                    gameMode == MachinePlaysWhite) {
15276             CallFlagEvent();
15277           }
15278         }
15279 }
15280
15281 void
15282 DrawEvent ()
15283 {
15284     /* Offer draw or accept pending draw offer from opponent */
15285
15286     if (appData.icsActive) {
15287         /* Note: tournament rules require draw offers to be
15288            made after you make your move but before you punch
15289            your clock.  Currently ICS doesn't let you do that;
15290            instead, you immediately punch your clock after making
15291            a move, but you can offer a draw at any time. */
15292
15293         SendToICS(ics_prefix);
15294         SendToICS("draw\n");
15295         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15296     } else if (cmailMsgLoaded) {
15297         if (currentMove == cmailOldMove &&
15298             commentList[cmailOldMove] != NULL &&
15299             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15300                    "Black offers a draw" : "White offers a draw")) {
15301             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15302             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15303         } else if (currentMove == cmailOldMove + 1) {
15304             char *offer = WhiteOnMove(cmailOldMove) ?
15305               "White offers a draw" : "Black offers a draw";
15306             AppendComment(currentMove, offer, TRUE);
15307             DisplayComment(currentMove - 1, offer);
15308             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15309         } else {
15310             DisplayError(_("You must make your move before offering a draw"), 0);
15311             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15312         }
15313     } else if (first.offeredDraw) {
15314         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15315     } else {
15316         if (first.sendDrawOffers) {
15317             SendToProgram("draw\n", &first);
15318             userOfferedDraw = TRUE;
15319         }
15320     }
15321 }
15322
15323 void
15324 AdjournEvent ()
15325 {
15326     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15327
15328     if (appData.icsActive) {
15329         SendToICS(ics_prefix);
15330         SendToICS("adjourn\n");
15331     } else {
15332         /* Currently GNU Chess doesn't offer or accept Adjourns */
15333     }
15334 }
15335
15336
15337 void
15338 AbortEvent ()
15339 {
15340     /* Offer Abort or accept pending Abort offer from opponent */
15341
15342     if (appData.icsActive) {
15343         SendToICS(ics_prefix);
15344         SendToICS("abort\n");
15345     } else {
15346         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15347     }
15348 }
15349
15350 void
15351 ResignEvent ()
15352 {
15353     /* Resign.  You can do this even if it's not your turn. */
15354
15355     if (appData.icsActive) {
15356         SendToICS(ics_prefix);
15357         SendToICS("resign\n");
15358     } else {
15359         switch (gameMode) {
15360           case MachinePlaysWhite:
15361             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15362             break;
15363           case MachinePlaysBlack:
15364             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15365             break;
15366           case EditGame:
15367             if (cmailMsgLoaded) {
15368                 TruncateGame();
15369                 if (WhiteOnMove(cmailOldMove)) {
15370                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15371                 } else {
15372                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15373                 }
15374                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15375             }
15376             break;
15377           default:
15378             break;
15379         }
15380     }
15381 }
15382
15383
15384 void
15385 StopObservingEvent ()
15386 {
15387     /* Stop observing current games */
15388     SendToICS(ics_prefix);
15389     SendToICS("unobserve\n");
15390 }
15391
15392 void
15393 StopExaminingEvent ()
15394 {
15395     /* Stop observing current game */
15396     SendToICS(ics_prefix);
15397     SendToICS("unexamine\n");
15398 }
15399
15400 void
15401 ForwardInner (int target)
15402 {
15403     int limit; int oldSeekGraphUp = seekGraphUp;
15404
15405     if (appData.debugMode)
15406         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15407                 target, currentMove, forwardMostMove);
15408
15409     if (gameMode == EditPosition)
15410       return;
15411
15412     seekGraphUp = FALSE;
15413     MarkTargetSquares(1);
15414
15415     if (gameMode == PlayFromGameFile && !pausing)
15416       PauseEvent();
15417
15418     if (gameMode == IcsExamining && pausing)
15419       limit = pauseExamForwardMostMove;
15420     else
15421       limit = forwardMostMove;
15422
15423     if (target > limit) target = limit;
15424
15425     if (target > 0 && moveList[target - 1][0]) {
15426         int fromX, fromY, toX, toY;
15427         toX = moveList[target - 1][2] - AAA;
15428         toY = moveList[target - 1][3] - ONE;
15429         if (moveList[target - 1][1] == '@') {
15430             if (appData.highlightLastMove) {
15431                 SetHighlights(-1, -1, toX, toY);
15432             }
15433         } else {
15434             fromX = moveList[target - 1][0] - AAA;
15435             fromY = moveList[target - 1][1] - ONE;
15436             if (target == currentMove + 1) {
15437                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15438             }
15439             if (appData.highlightLastMove) {
15440                 SetHighlights(fromX, fromY, toX, toY);
15441             }
15442         }
15443     }
15444     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15445         gameMode == Training || gameMode == PlayFromGameFile ||
15446         gameMode == AnalyzeFile) {
15447         while (currentMove < target) {
15448             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15449             SendMoveToProgram(currentMove++, &first);
15450         }
15451     } else {
15452         currentMove = target;
15453     }
15454
15455     if (gameMode == EditGame || gameMode == EndOfGame) {
15456         whiteTimeRemaining = timeRemaining[0][currentMove];
15457         blackTimeRemaining = timeRemaining[1][currentMove];
15458     }
15459     DisplayBothClocks();
15460     DisplayMove(currentMove - 1);
15461     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15462     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15463     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15464         DisplayComment(currentMove - 1, commentList[currentMove]);
15465     }
15466     ClearMap(); // [HGM] exclude: invalidate map
15467 }
15468
15469
15470 void
15471 ForwardEvent ()
15472 {
15473     if (gameMode == IcsExamining && !pausing) {
15474         SendToICS(ics_prefix);
15475         SendToICS("forward\n");
15476     } else {
15477         ForwardInner(currentMove + 1);
15478     }
15479 }
15480
15481 void
15482 ToEndEvent ()
15483 {
15484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15485         /* to optimze, we temporarily turn off analysis mode while we feed
15486          * the remaining moves to the engine. Otherwise we get analysis output
15487          * after each move.
15488          */
15489         if (first.analysisSupport) {
15490           SendToProgram("exit\nforce\n", &first);
15491           first.analyzing = FALSE;
15492         }
15493     }
15494
15495     if (gameMode == IcsExamining && !pausing) {
15496         SendToICS(ics_prefix);
15497         SendToICS("forward 999999\n");
15498     } else {
15499         ForwardInner(forwardMostMove);
15500     }
15501
15502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15503         /* we have fed all the moves, so reactivate analysis mode */
15504         SendToProgram("analyze\n", &first);
15505         first.analyzing = TRUE;
15506         /*first.maybeThinking = TRUE;*/
15507         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15508     }
15509 }
15510
15511 void
15512 BackwardInner (int target)
15513 {
15514     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15515
15516     if (appData.debugMode)
15517         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15518                 target, currentMove, forwardMostMove);
15519
15520     if (gameMode == EditPosition) return;
15521     seekGraphUp = FALSE;
15522     MarkTargetSquares(1);
15523     if (currentMove <= backwardMostMove) {
15524         ClearHighlights();
15525         DrawPosition(full_redraw, boards[currentMove]);
15526         return;
15527     }
15528     if (gameMode == PlayFromGameFile && !pausing)
15529       PauseEvent();
15530
15531     if (moveList[target][0]) {
15532         int fromX, fromY, toX, toY;
15533         toX = moveList[target][2] - AAA;
15534         toY = moveList[target][3] - ONE;
15535         if (moveList[target][1] == '@') {
15536             if (appData.highlightLastMove) {
15537                 SetHighlights(-1, -1, toX, toY);
15538             }
15539         } else {
15540             fromX = moveList[target][0] - AAA;
15541             fromY = moveList[target][1] - ONE;
15542             if (target == currentMove - 1) {
15543                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15544             }
15545             if (appData.highlightLastMove) {
15546                 SetHighlights(fromX, fromY, toX, toY);
15547             }
15548         }
15549     }
15550     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15551         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15552         while (currentMove > target) {
15553             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15554                 // null move cannot be undone. Reload program with move history before it.
15555                 int i;
15556                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15557                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15558                 }
15559                 SendBoard(&first, i);
15560               if(second.analyzing) SendBoard(&second, i);
15561                 for(currentMove=i; currentMove<target; currentMove++) {
15562                     SendMoveToProgram(currentMove, &first);
15563                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15564                 }
15565                 break;
15566             }
15567             SendToBoth("undo\n");
15568             currentMove--;
15569         }
15570     } else {
15571         currentMove = target;
15572     }
15573
15574     if (gameMode == EditGame || gameMode == EndOfGame) {
15575         whiteTimeRemaining = timeRemaining[0][currentMove];
15576         blackTimeRemaining = timeRemaining[1][currentMove];
15577     }
15578     DisplayBothClocks();
15579     DisplayMove(currentMove - 1);
15580     DrawPosition(full_redraw, boards[currentMove]);
15581     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15582     // [HGM] PV info: routine tests if comment empty
15583     DisplayComment(currentMove - 1, commentList[currentMove]);
15584     ClearMap(); // [HGM] exclude: invalidate map
15585 }
15586
15587 void
15588 BackwardEvent ()
15589 {
15590     if (gameMode == IcsExamining && !pausing) {
15591         SendToICS(ics_prefix);
15592         SendToICS("backward\n");
15593     } else {
15594         BackwardInner(currentMove - 1);
15595     }
15596 }
15597
15598 void
15599 ToStartEvent ()
15600 {
15601     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15602         /* to optimize, we temporarily turn off analysis mode while we undo
15603          * all the moves. Otherwise we get analysis output after each undo.
15604          */
15605         if (first.analysisSupport) {
15606           SendToProgram("exit\nforce\n", &first);
15607           first.analyzing = FALSE;
15608         }
15609     }
15610
15611     if (gameMode == IcsExamining && !pausing) {
15612         SendToICS(ics_prefix);
15613         SendToICS("backward 999999\n");
15614     } else {
15615         BackwardInner(backwardMostMove);
15616     }
15617
15618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15619         /* we have fed all the moves, so reactivate analysis mode */
15620         SendToProgram("analyze\n", &first);
15621         first.analyzing = TRUE;
15622         /*first.maybeThinking = TRUE;*/
15623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15624     }
15625 }
15626
15627 void
15628 ToNrEvent (int to)
15629 {
15630   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15631   if (to >= forwardMostMove) to = forwardMostMove;
15632   if (to <= backwardMostMove) to = backwardMostMove;
15633   if (to < currentMove) {
15634     BackwardInner(to);
15635   } else {
15636     ForwardInner(to);
15637   }
15638 }
15639
15640 void
15641 RevertEvent (Boolean annotate)
15642 {
15643     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15644         return;
15645     }
15646     if (gameMode != IcsExamining) {
15647         DisplayError(_("You are not examining a game"), 0);
15648         return;
15649     }
15650     if (pausing) {
15651         DisplayError(_("You can't revert while pausing"), 0);
15652         return;
15653     }
15654     SendToICS(ics_prefix);
15655     SendToICS("revert\n");
15656 }
15657
15658 void
15659 RetractMoveEvent ()
15660 {
15661     switch (gameMode) {
15662       case MachinePlaysWhite:
15663       case MachinePlaysBlack:
15664         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15665             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15666             return;
15667         }
15668         if (forwardMostMove < 2) return;
15669         currentMove = forwardMostMove = forwardMostMove - 2;
15670         whiteTimeRemaining = timeRemaining[0][currentMove];
15671         blackTimeRemaining = timeRemaining[1][currentMove];
15672         DisplayBothClocks();
15673         DisplayMove(currentMove - 1);
15674         ClearHighlights();/*!! could figure this out*/
15675         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15676         SendToProgram("remove\n", &first);
15677         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15678         break;
15679
15680       case BeginningOfGame:
15681       default:
15682         break;
15683
15684       case IcsPlayingWhite:
15685       case IcsPlayingBlack:
15686         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15687             SendToICS(ics_prefix);
15688             SendToICS("takeback 2\n");
15689         } else {
15690             SendToICS(ics_prefix);
15691             SendToICS("takeback 1\n");
15692         }
15693         break;
15694     }
15695 }
15696
15697 void
15698 MoveNowEvent ()
15699 {
15700     ChessProgramState *cps;
15701
15702     switch (gameMode) {
15703       case MachinePlaysWhite:
15704         if (!WhiteOnMove(forwardMostMove)) {
15705             DisplayError(_("It is your turn"), 0);
15706             return;
15707         }
15708         cps = &first;
15709         break;
15710       case MachinePlaysBlack:
15711         if (WhiteOnMove(forwardMostMove)) {
15712             DisplayError(_("It is your turn"), 0);
15713             return;
15714         }
15715         cps = &first;
15716         break;
15717       case TwoMachinesPlay:
15718         if (WhiteOnMove(forwardMostMove) ==
15719             (first.twoMachinesColor[0] == 'w')) {
15720             cps = &first;
15721         } else {
15722             cps = &second;
15723         }
15724         break;
15725       case BeginningOfGame:
15726       default:
15727         return;
15728     }
15729     SendToProgram("?\n", cps);
15730 }
15731
15732 void
15733 TruncateGameEvent ()
15734 {
15735     EditGameEvent();
15736     if (gameMode != EditGame) return;
15737     TruncateGame();
15738 }
15739
15740 void
15741 TruncateGame ()
15742 {
15743     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15744     if (forwardMostMove > currentMove) {
15745         if (gameInfo.resultDetails != NULL) {
15746             free(gameInfo.resultDetails);
15747             gameInfo.resultDetails = NULL;
15748             gameInfo.result = GameUnfinished;
15749         }
15750         forwardMostMove = currentMove;
15751         HistorySet(parseList, backwardMostMove, forwardMostMove,
15752                    currentMove-1);
15753     }
15754 }
15755
15756 void
15757 HintEvent ()
15758 {
15759     if (appData.noChessProgram) return;
15760     switch (gameMode) {
15761       case MachinePlaysWhite:
15762         if (WhiteOnMove(forwardMostMove)) {
15763             DisplayError(_("Wait until your turn."), 0);
15764             return;
15765         }
15766         break;
15767       case BeginningOfGame:
15768       case MachinePlaysBlack:
15769         if (!WhiteOnMove(forwardMostMove)) {
15770             DisplayError(_("Wait until your turn."), 0);
15771             return;
15772         }
15773         break;
15774       default:
15775         DisplayError(_("No hint available"), 0);
15776         return;
15777     }
15778     SendToProgram("hint\n", &first);
15779     hintRequested = TRUE;
15780 }
15781
15782 void
15783 CreateBookEvent ()
15784 {
15785     ListGame * lg = (ListGame *) gameList.head;
15786     FILE *f, *g;
15787     int nItem;
15788     static int secondTime = FALSE;
15789
15790     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15791         DisplayError(_("Game list not loaded or empty"), 0);
15792         return;
15793     }
15794
15795     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15796         fclose(g);
15797         secondTime++;
15798         DisplayNote(_("Book file exists! Try again for overwrite."));
15799         return;
15800     }
15801
15802     creatingBook = TRUE;
15803     secondTime = FALSE;
15804
15805     /* Get list size */
15806     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15807         LoadGame(f, nItem, "", TRUE);
15808         AddGameToBook(TRUE);
15809         lg = (ListGame *) lg->node.succ;
15810     }
15811
15812     creatingBook = FALSE;
15813     FlushBook();
15814 }
15815
15816 void
15817 BookEvent ()
15818 {
15819     if (appData.noChessProgram) return;
15820     switch (gameMode) {
15821       case MachinePlaysWhite:
15822         if (WhiteOnMove(forwardMostMove)) {
15823             DisplayError(_("Wait until your turn."), 0);
15824             return;
15825         }
15826         break;
15827       case BeginningOfGame:
15828       case MachinePlaysBlack:
15829         if (!WhiteOnMove(forwardMostMove)) {
15830             DisplayError(_("Wait until your turn."), 0);
15831             return;
15832         }
15833         break;
15834       case EditPosition:
15835         EditPositionDone(TRUE);
15836         break;
15837       case TwoMachinesPlay:
15838         return;
15839       default:
15840         break;
15841     }
15842     SendToProgram("bk\n", &first);
15843     bookOutput[0] = NULLCHAR;
15844     bookRequested = TRUE;
15845 }
15846
15847 void
15848 AboutGameEvent ()
15849 {
15850     char *tags = PGNTags(&gameInfo);
15851     TagsPopUp(tags, CmailMsg());
15852     free(tags);
15853 }
15854
15855 /* end button procedures */
15856
15857 void
15858 PrintPosition (FILE *fp, int move)
15859 {
15860     int i, j;
15861
15862     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15863         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15864             char c = PieceToChar(boards[move][i][j]);
15865             fputc(c == 'x' ? '.' : c, fp);
15866             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15867         }
15868     }
15869     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15870       fprintf(fp, "white to play\n");
15871     else
15872       fprintf(fp, "black to play\n");
15873 }
15874
15875 void
15876 PrintOpponents (FILE *fp)
15877 {
15878     if (gameInfo.white != NULL) {
15879         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15880     } else {
15881         fprintf(fp, "\n");
15882     }
15883 }
15884
15885 /* Find last component of program's own name, using some heuristics */
15886 void
15887 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15888 {
15889     char *p, *q, c;
15890     int local = (strcmp(host, "localhost") == 0);
15891     while (!local && (p = strchr(prog, ';')) != NULL) {
15892         p++;
15893         while (*p == ' ') p++;
15894         prog = p;
15895     }
15896     if (*prog == '"' || *prog == '\'') {
15897         q = strchr(prog + 1, *prog);
15898     } else {
15899         q = strchr(prog, ' ');
15900     }
15901     if (q == NULL) q = prog + strlen(prog);
15902     p = q;
15903     while (p >= prog && *p != '/' && *p != '\\') p--;
15904     p++;
15905     if(p == prog && *p == '"') p++;
15906     c = *q; *q = 0;
15907     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15908     memcpy(buf, p, q - p);
15909     buf[q - p] = NULLCHAR;
15910     if (!local) {
15911         strcat(buf, "@");
15912         strcat(buf, host);
15913     }
15914 }
15915
15916 char *
15917 TimeControlTagValue ()
15918 {
15919     char buf[MSG_SIZ];
15920     if (!appData.clockMode) {
15921       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15922     } else if (movesPerSession > 0) {
15923       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15924     } else if (timeIncrement == 0) {
15925       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15926     } else {
15927       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15928     }
15929     return StrSave(buf);
15930 }
15931
15932 void
15933 SetGameInfo ()
15934 {
15935     /* This routine is used only for certain modes */
15936     VariantClass v = gameInfo.variant;
15937     ChessMove r = GameUnfinished;
15938     char *p = NULL;
15939
15940     if(keepInfo) return;
15941
15942     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15943         r = gameInfo.result;
15944         p = gameInfo.resultDetails;
15945         gameInfo.resultDetails = NULL;
15946     }
15947     ClearGameInfo(&gameInfo);
15948     gameInfo.variant = v;
15949
15950     switch (gameMode) {
15951       case MachinePlaysWhite:
15952         gameInfo.event = StrSave( appData.pgnEventHeader );
15953         gameInfo.site = StrSave(HostName());
15954         gameInfo.date = PGNDate();
15955         gameInfo.round = StrSave("-");
15956         gameInfo.white = StrSave(first.tidy);
15957         gameInfo.black = StrSave(UserName());
15958         gameInfo.timeControl = TimeControlTagValue();
15959         break;
15960
15961       case MachinePlaysBlack:
15962         gameInfo.event = StrSave( appData.pgnEventHeader );
15963         gameInfo.site = StrSave(HostName());
15964         gameInfo.date = PGNDate();
15965         gameInfo.round = StrSave("-");
15966         gameInfo.white = StrSave(UserName());
15967         gameInfo.black = StrSave(first.tidy);
15968         gameInfo.timeControl = TimeControlTagValue();
15969         break;
15970
15971       case TwoMachinesPlay:
15972         gameInfo.event = StrSave( appData.pgnEventHeader );
15973         gameInfo.site = StrSave(HostName());
15974         gameInfo.date = PGNDate();
15975         if (roundNr > 0) {
15976             char buf[MSG_SIZ];
15977             snprintf(buf, MSG_SIZ, "%d", roundNr);
15978             gameInfo.round = StrSave(buf);
15979         } else {
15980             gameInfo.round = StrSave("-");
15981         }
15982         if (first.twoMachinesColor[0] == 'w') {
15983             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15984             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15985         } else {
15986             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15987             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15988         }
15989         gameInfo.timeControl = TimeControlTagValue();
15990         break;
15991
15992       case EditGame:
15993         gameInfo.event = StrSave("Edited game");
15994         gameInfo.site = StrSave(HostName());
15995         gameInfo.date = PGNDate();
15996         gameInfo.round = StrSave("-");
15997         gameInfo.white = StrSave("-");
15998         gameInfo.black = StrSave("-");
15999         gameInfo.result = r;
16000         gameInfo.resultDetails = p;
16001         break;
16002
16003       case EditPosition:
16004         gameInfo.event = StrSave("Edited position");
16005         gameInfo.site = StrSave(HostName());
16006         gameInfo.date = PGNDate();
16007         gameInfo.round = StrSave("-");
16008         gameInfo.white = StrSave("-");
16009         gameInfo.black = StrSave("-");
16010         break;
16011
16012       case IcsPlayingWhite:
16013       case IcsPlayingBlack:
16014       case IcsObserving:
16015       case IcsExamining:
16016         break;
16017
16018       case PlayFromGameFile:
16019         gameInfo.event = StrSave("Game from non-PGN file");
16020         gameInfo.site = StrSave(HostName());
16021         gameInfo.date = PGNDate();
16022         gameInfo.round = StrSave("-");
16023         gameInfo.white = StrSave("?");
16024         gameInfo.black = StrSave("?");
16025         break;
16026
16027       default:
16028         break;
16029     }
16030 }
16031
16032 void
16033 ReplaceComment (int index, char *text)
16034 {
16035     int len;
16036     char *p;
16037     float score;
16038
16039     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16040        pvInfoList[index-1].depth == len &&
16041        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16042        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16043     while (*text == '\n') text++;
16044     len = strlen(text);
16045     while (len > 0 && text[len - 1] == '\n') len--;
16046
16047     if (commentList[index] != NULL)
16048       free(commentList[index]);
16049
16050     if (len == 0) {
16051         commentList[index] = NULL;
16052         return;
16053     }
16054   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16055       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16056       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16057     commentList[index] = (char *) malloc(len + 2);
16058     strncpy(commentList[index], text, len);
16059     commentList[index][len] = '\n';
16060     commentList[index][len + 1] = NULLCHAR;
16061   } else {
16062     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16063     char *p;
16064     commentList[index] = (char *) malloc(len + 7);
16065     safeStrCpy(commentList[index], "{\n", 3);
16066     safeStrCpy(commentList[index]+2, text, len+1);
16067     commentList[index][len+2] = NULLCHAR;
16068     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16069     strcat(commentList[index], "\n}\n");
16070   }
16071 }
16072
16073 void
16074 CrushCRs (char *text)
16075 {
16076   char *p = text;
16077   char *q = text;
16078   char ch;
16079
16080   do {
16081     ch = *p++;
16082     if (ch == '\r') continue;
16083     *q++ = ch;
16084   } while (ch != '\0');
16085 }
16086
16087 void
16088 AppendComment (int index, char *text, Boolean addBraces)
16089 /* addBraces  tells if we should add {} */
16090 {
16091     int oldlen, len;
16092     char *old;
16093
16094 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16095     if(addBraces == 3) addBraces = 0; else // force appending literally
16096     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16097
16098     CrushCRs(text);
16099     while (*text == '\n') text++;
16100     len = strlen(text);
16101     while (len > 0 && text[len - 1] == '\n') len--;
16102     text[len] = NULLCHAR;
16103
16104     if (len == 0) return;
16105
16106     if (commentList[index] != NULL) {
16107       Boolean addClosingBrace = addBraces;
16108         old = commentList[index];
16109         oldlen = strlen(old);
16110         while(commentList[index][oldlen-1] ==  '\n')
16111           commentList[index][--oldlen] = NULLCHAR;
16112         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16113         safeStrCpy(commentList[index], old, oldlen + len + 6);
16114         free(old);
16115         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16116         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16117           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16118           while (*text == '\n') { text++; len--; }
16119           commentList[index][--oldlen] = NULLCHAR;
16120       }
16121         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16122         else          strcat(commentList[index], "\n");
16123         strcat(commentList[index], text);
16124         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16125         else          strcat(commentList[index], "\n");
16126     } else {
16127         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16128         if(addBraces)
16129           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16130         else commentList[index][0] = NULLCHAR;
16131         strcat(commentList[index], text);
16132         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16133         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16134     }
16135 }
16136
16137 static char *
16138 FindStr (char * text, char * sub_text)
16139 {
16140     char * result = strstr( text, sub_text );
16141
16142     if( result != NULL ) {
16143         result += strlen( sub_text );
16144     }
16145
16146     return result;
16147 }
16148
16149 /* [AS] Try to extract PV info from PGN comment */
16150 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16151 char *
16152 GetInfoFromComment (int index, char * text)
16153 {
16154     char * sep = text, *p;
16155
16156     if( text != NULL && index > 0 ) {
16157         int score = 0;
16158         int depth = 0;
16159         int time = -1, sec = 0, deci;
16160         char * s_eval = FindStr( text, "[%eval " );
16161         char * s_emt = FindStr( text, "[%emt " );
16162 #if 0
16163         if( s_eval != NULL || s_emt != NULL ) {
16164 #else
16165         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16166 #endif
16167             /* New style */
16168             char delim;
16169
16170             if( s_eval != NULL ) {
16171                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16172                     return text;
16173                 }
16174
16175                 if( delim != ']' ) {
16176                     return text;
16177                 }
16178             }
16179
16180             if( s_emt != NULL ) {
16181             }
16182                 return text;
16183         }
16184         else {
16185             /* We expect something like: [+|-]nnn.nn/dd */
16186             int score_lo = 0;
16187
16188             if(*text != '{') return text; // [HGM] braces: must be normal comment
16189
16190             sep = strchr( text, '/' );
16191             if( sep == NULL || sep < (text+4) ) {
16192                 return text;
16193             }
16194
16195             p = text;
16196             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16197             if(p[1] == '(') { // comment starts with PV
16198                p = strchr(p, ')'); // locate end of PV
16199                if(p == NULL || sep < p+5) return text;
16200                // at this point we have something like "{(.*) +0.23/6 ..."
16201                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16202                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16203                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16204             }
16205             time = -1; sec = -1; deci = -1;
16206             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16207                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16208                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16209                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16210                 return text;
16211             }
16212
16213             if( score_lo < 0 || score_lo >= 100 ) {
16214                 return text;
16215             }
16216
16217             if(sec >= 0) time = 600*time + 10*sec; else
16218             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16219
16220             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16221
16222             /* [HGM] PV time: now locate end of PV info */
16223             while( *++sep >= '0' && *sep <= '9'); // strip depth
16224             if(time >= 0)
16225             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16226             if(sec >= 0)
16227             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16228             if(deci >= 0)
16229             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16230             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16231         }
16232
16233         if( depth <= 0 ) {
16234             return text;
16235         }
16236
16237         if( time < 0 ) {
16238             time = -1;
16239         }
16240
16241         pvInfoList[index-1].depth = depth;
16242         pvInfoList[index-1].score = score;
16243         pvInfoList[index-1].time  = 10*time; // centi-sec
16244         if(*sep == '}') *sep = 0; else *--sep = '{';
16245         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16246     }
16247     return sep;
16248 }
16249
16250 void
16251 SendToProgram (char *message, ChessProgramState *cps)
16252 {
16253     int count, outCount, error;
16254     char buf[MSG_SIZ];
16255
16256     if (cps->pr == NoProc) return;
16257     Attention(cps);
16258
16259     if (appData.debugMode) {
16260         TimeMark now;
16261         GetTimeMark(&now);
16262         fprintf(debugFP, "%ld >%-6s: %s",
16263                 SubtractTimeMarks(&now, &programStartTime),
16264                 cps->which, message);
16265         if(serverFP)
16266             fprintf(serverFP, "%ld >%-6s: %s",
16267                 SubtractTimeMarks(&now, &programStartTime),
16268                 cps->which, message), fflush(serverFP);
16269     }
16270
16271     count = strlen(message);
16272     outCount = OutputToProcess(cps->pr, message, count, &error);
16273     if (outCount < count && !exiting
16274                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16275       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16276       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16277         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16278             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16279                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16280                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16281                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16282             } else {
16283                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16284                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16285                 gameInfo.result = res;
16286             }
16287             gameInfo.resultDetails = StrSave(buf);
16288         }
16289         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16290         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16291     }
16292 }
16293
16294 void
16295 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16296 {
16297     char *end_str;
16298     char buf[MSG_SIZ];
16299     ChessProgramState *cps = (ChessProgramState *)closure;
16300
16301     if (isr != cps->isr) return; /* Killed intentionally */
16302     if (count <= 0) {
16303         if (count == 0) {
16304             RemoveInputSource(cps->isr);
16305             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16306                     _(cps->which), cps->program);
16307             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16308             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16309                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16310                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16311                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16312                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16313                 } else {
16314                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16315                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16316                     gameInfo.result = res;
16317                 }
16318                 gameInfo.resultDetails = StrSave(buf);
16319             }
16320             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16321             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16322         } else {
16323             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16324                     _(cps->which), cps->program);
16325             RemoveInputSource(cps->isr);
16326
16327             /* [AS] Program is misbehaving badly... kill it */
16328             if( count == -2 ) {
16329                 DestroyChildProcess( cps->pr, 9 );
16330                 cps->pr = NoProc;
16331             }
16332
16333             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16334         }
16335         return;
16336     }
16337
16338     if ((end_str = strchr(message, '\r')) != NULL)
16339       *end_str = NULLCHAR;
16340     if ((end_str = strchr(message, '\n')) != NULL)
16341       *end_str = NULLCHAR;
16342
16343     if (appData.debugMode) {
16344         TimeMark now; int print = 1;
16345         char *quote = ""; char c; int i;
16346
16347         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16348                 char start = message[0];
16349                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16350                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16351                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16352                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16353                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16354                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16355                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16356                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16357                    sscanf(message, "hint: %c", &c)!=1 &&
16358                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16359                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16360                     print = (appData.engineComments >= 2);
16361                 }
16362                 message[0] = start; // restore original message
16363         }
16364         if(print) {
16365                 GetTimeMark(&now);
16366                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16367                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16368                         quote,
16369                         message);
16370                 if(serverFP)
16371                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16372                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16373                         quote,
16374                         message), fflush(serverFP);
16375         }
16376     }
16377
16378     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16379     if (appData.icsEngineAnalyze) {
16380         if (strstr(message, "whisper") != NULL ||
16381              strstr(message, "kibitz") != NULL ||
16382             strstr(message, "tellics") != NULL) return;
16383     }
16384
16385     HandleMachineMove(message, cps);
16386 }
16387
16388
16389 void
16390 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16391 {
16392     char buf[MSG_SIZ];
16393     int seconds;
16394
16395     if( timeControl_2 > 0 ) {
16396         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16397             tc = timeControl_2;
16398         }
16399     }
16400     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16401     inc /= cps->timeOdds;
16402     st  /= cps->timeOdds;
16403
16404     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16405
16406     if (st > 0) {
16407       /* Set exact time per move, normally using st command */
16408       if (cps->stKludge) {
16409         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16410         seconds = st % 60;
16411         if (seconds == 0) {
16412           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16413         } else {
16414           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16415         }
16416       } else {
16417         snprintf(buf, MSG_SIZ, "st %d\n", st);
16418       }
16419     } else {
16420       /* Set conventional or incremental time control, using level command */
16421       if (seconds == 0) {
16422         /* Note old gnuchess bug -- minutes:seconds used to not work.
16423            Fixed in later versions, but still avoid :seconds
16424            when seconds is 0. */
16425         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16426       } else {
16427         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16428                  seconds, inc/1000.);
16429       }
16430     }
16431     SendToProgram(buf, cps);
16432
16433     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16434     /* Orthogonally, limit search to given depth */
16435     if (sd > 0) {
16436       if (cps->sdKludge) {
16437         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16438       } else {
16439         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16440       }
16441       SendToProgram(buf, cps);
16442     }
16443
16444     if(cps->nps >= 0) { /* [HGM] nps */
16445         if(cps->supportsNPS == FALSE)
16446           cps->nps = -1; // don't use if engine explicitly says not supported!
16447         else {
16448           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16449           SendToProgram(buf, cps);
16450         }
16451     }
16452 }
16453
16454 ChessProgramState *
16455 WhitePlayer ()
16456 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16457 {
16458     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16459        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16460         return &second;
16461     return &first;
16462 }
16463
16464 void
16465 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16466 {
16467     char message[MSG_SIZ];
16468     long time, otime;
16469
16470     /* Note: this routine must be called when the clocks are stopped
16471        or when they have *just* been set or switched; otherwise
16472        it will be off by the time since the current tick started.
16473     */
16474     if (machineWhite) {
16475         time = whiteTimeRemaining / 10;
16476         otime = blackTimeRemaining / 10;
16477     } else {
16478         time = blackTimeRemaining / 10;
16479         otime = whiteTimeRemaining / 10;
16480     }
16481     /* [HGM] translate opponent's time by time-odds factor */
16482     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16483
16484     if (time <= 0) time = 1;
16485     if (otime <= 0) otime = 1;
16486
16487     snprintf(message, MSG_SIZ, "time %ld\n", time);
16488     SendToProgram(message, cps);
16489
16490     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16491     SendToProgram(message, cps);
16492 }
16493
16494 char *
16495 EngineDefinedVariant (ChessProgramState *cps, int n)
16496 {   // return name of n-th unknown variant that engine supports
16497     static char buf[MSG_SIZ];
16498     char *p, *s = cps->variants;
16499     if(!s) return NULL;
16500     do { // parse string from variants feature
16501       VariantClass v;
16502         p = strchr(s, ',');
16503         if(p) *p = NULLCHAR;
16504       v = StringToVariant(s);
16505       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16506         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16507             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16508         }
16509         if(p) *p++ = ',';
16510         if(n < 0) return buf;
16511     } while(s = p);
16512     return NULL;
16513 }
16514
16515 int
16516 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16517 {
16518   char buf[MSG_SIZ];
16519   int len = strlen(name);
16520   int val;
16521
16522   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16523     (*p) += len + 1;
16524     sscanf(*p, "%d", &val);
16525     *loc = (val != 0);
16526     while (**p && **p != ' ')
16527       (*p)++;
16528     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16529     SendToProgram(buf, cps);
16530     return TRUE;
16531   }
16532   return FALSE;
16533 }
16534
16535 int
16536 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16537 {
16538   char buf[MSG_SIZ];
16539   int len = strlen(name);
16540   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16541     (*p) += len + 1;
16542     sscanf(*p, "%d", loc);
16543     while (**p && **p != ' ') (*p)++;
16544     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16545     SendToProgram(buf, cps);
16546     return TRUE;
16547   }
16548   return FALSE;
16549 }
16550
16551 int
16552 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16553 {
16554   char buf[MSG_SIZ];
16555   int len = strlen(name);
16556   if (strncmp((*p), name, len) == 0
16557       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16558     (*p) += len + 2;
16559     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16560     sscanf(*p, "%[^\"]", *loc);
16561     while (**p && **p != '\"') (*p)++;
16562     if (**p == '\"') (*p)++;
16563     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16564     SendToProgram(buf, cps);
16565     return TRUE;
16566   }
16567   return FALSE;
16568 }
16569
16570 int
16571 ParseOption (Option *opt, ChessProgramState *cps)
16572 // [HGM] options: process the string that defines an engine option, and determine
16573 // name, type, default value, and allowed value range
16574 {
16575         char *p, *q, buf[MSG_SIZ];
16576         int n, min = (-1)<<31, max = 1<<31, def;
16577
16578         if(p = strstr(opt->name, " -spin ")) {
16579             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16580             if(max < min) max = min; // enforce consistency
16581             if(def < min) def = min;
16582             if(def > max) def = max;
16583             opt->value = def;
16584             opt->min = min;
16585             opt->max = max;
16586             opt->type = Spin;
16587         } else if((p = strstr(opt->name, " -slider "))) {
16588             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16589             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16590             if(max < min) max = min; // enforce consistency
16591             if(def < min) def = min;
16592             if(def > max) def = max;
16593             opt->value = def;
16594             opt->min = min;
16595             opt->max = max;
16596             opt->type = Spin; // Slider;
16597         } else if((p = strstr(opt->name, " -string "))) {
16598             opt->textValue = p+9;
16599             opt->type = TextBox;
16600         } else if((p = strstr(opt->name, " -file "))) {
16601             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16602             opt->textValue = p+7;
16603             opt->type = FileName; // FileName;
16604         } else if((p = strstr(opt->name, " -path "))) {
16605             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16606             opt->textValue = p+7;
16607             opt->type = PathName; // PathName;
16608         } else if(p = strstr(opt->name, " -check ")) {
16609             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16610             opt->value = (def != 0);
16611             opt->type = CheckBox;
16612         } else if(p = strstr(opt->name, " -combo ")) {
16613             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16614             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16615             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16616             opt->value = n = 0;
16617             while(q = StrStr(q, " /// ")) {
16618                 n++; *q = 0;    // count choices, and null-terminate each of them
16619                 q += 5;
16620                 if(*q == '*') { // remember default, which is marked with * prefix
16621                     q++;
16622                     opt->value = n;
16623                 }
16624                 cps->comboList[cps->comboCnt++] = q;
16625             }
16626             cps->comboList[cps->comboCnt++] = NULL;
16627             opt->max = n + 1;
16628             opt->type = ComboBox;
16629         } else if(p = strstr(opt->name, " -button")) {
16630             opt->type = Button;
16631         } else if(p = strstr(opt->name, " -save")) {
16632             opt->type = SaveButton;
16633         } else return FALSE;
16634         *p = 0; // terminate option name
16635         // now look if the command-line options define a setting for this engine option.
16636         if(cps->optionSettings && cps->optionSettings[0])
16637             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16638         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16639           snprintf(buf, MSG_SIZ, "option %s", p);
16640                 if(p = strstr(buf, ",")) *p = 0;
16641                 if(q = strchr(buf, '=')) switch(opt->type) {
16642                     case ComboBox:
16643                         for(n=0; n<opt->max; n++)
16644                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16645                         break;
16646                     case TextBox:
16647                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16648                         break;
16649                     case Spin:
16650                     case CheckBox:
16651                         opt->value = atoi(q+1);
16652                     default:
16653                         break;
16654                 }
16655                 strcat(buf, "\n");
16656                 SendToProgram(buf, cps);
16657         }
16658         return TRUE;
16659 }
16660
16661 void
16662 FeatureDone (ChessProgramState *cps, int val)
16663 {
16664   DelayedEventCallback cb = GetDelayedEvent();
16665   if ((cb == InitBackEnd3 && cps == &first) ||
16666       (cb == SettingsMenuIfReady && cps == &second) ||
16667       (cb == LoadEngine) ||
16668       (cb == TwoMachinesEventIfReady)) {
16669     CancelDelayedEvent();
16670     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16671   }
16672   cps->initDone = val;
16673   if(val) cps->reload = FALSE;
16674 }
16675
16676 /* Parse feature command from engine */
16677 void
16678 ParseFeatures (char *args, ChessProgramState *cps)
16679 {
16680   char *p = args;
16681   char *q = NULL;
16682   int val;
16683   char buf[MSG_SIZ];
16684
16685   for (;;) {
16686     while (*p == ' ') p++;
16687     if (*p == NULLCHAR) return;
16688
16689     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16690     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16691     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16692     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16693     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16694     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16695     if (BoolFeature(&p, "reuse", &val, cps)) {
16696       /* Engine can disable reuse, but can't enable it if user said no */
16697       if (!val) cps->reuse = FALSE;
16698       continue;
16699     }
16700     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16701     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16702       if (gameMode == TwoMachinesPlay) {
16703         DisplayTwoMachinesTitle();
16704       } else {
16705         DisplayTitle("");
16706       }
16707       continue;
16708     }
16709     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16710     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16711     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16712     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16713     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16714     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16715     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16716     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16717     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16718     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16719     if (IntFeature(&p, "done", &val, cps)) {
16720       FeatureDone(cps, val);
16721       continue;
16722     }
16723     /* Added by Tord: */
16724     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16725     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16726     /* End of additions by Tord */
16727
16728     /* [HGM] added features: */
16729     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16730     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16731     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16732     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16733     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16734     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16735     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16736     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16737         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16738         FREE(cps->option[cps->nrOptions].name);
16739         cps->option[cps->nrOptions].name = q; q = NULL;
16740         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16741           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16742             SendToProgram(buf, cps);
16743             continue;
16744         }
16745         if(cps->nrOptions >= MAX_OPTIONS) {
16746             cps->nrOptions--;
16747             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16748             DisplayError(buf, 0);
16749         }
16750         continue;
16751     }
16752     /* End of additions by HGM */
16753
16754     /* unknown feature: complain and skip */
16755     q = p;
16756     while (*q && *q != '=') q++;
16757     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16758     SendToProgram(buf, cps);
16759     p = q;
16760     if (*p == '=') {
16761       p++;
16762       if (*p == '\"') {
16763         p++;
16764         while (*p && *p != '\"') p++;
16765         if (*p == '\"') p++;
16766       } else {
16767         while (*p && *p != ' ') p++;
16768       }
16769     }
16770   }
16771
16772 }
16773
16774 void
16775 PeriodicUpdatesEvent (int newState)
16776 {
16777     if (newState == appData.periodicUpdates)
16778       return;
16779
16780     appData.periodicUpdates=newState;
16781
16782     /* Display type changes, so update it now */
16783 //    DisplayAnalysis();
16784
16785     /* Get the ball rolling again... */
16786     if (newState) {
16787         AnalysisPeriodicEvent(1);
16788         StartAnalysisClock();
16789     }
16790 }
16791
16792 void
16793 PonderNextMoveEvent (int newState)
16794 {
16795     if (newState == appData.ponderNextMove) return;
16796     if (gameMode == EditPosition) EditPositionDone(TRUE);
16797     if (newState) {
16798         SendToProgram("hard\n", &first);
16799         if (gameMode == TwoMachinesPlay) {
16800             SendToProgram("hard\n", &second);
16801         }
16802     } else {
16803         SendToProgram("easy\n", &first);
16804         thinkOutput[0] = NULLCHAR;
16805         if (gameMode == TwoMachinesPlay) {
16806             SendToProgram("easy\n", &second);
16807         }
16808     }
16809     appData.ponderNextMove = newState;
16810 }
16811
16812 void
16813 NewSettingEvent (int option, int *feature, char *command, int value)
16814 {
16815     char buf[MSG_SIZ];
16816
16817     if (gameMode == EditPosition) EditPositionDone(TRUE);
16818     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16819     if(feature == NULL || *feature) SendToProgram(buf, &first);
16820     if (gameMode == TwoMachinesPlay) {
16821         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16822     }
16823 }
16824
16825 void
16826 ShowThinkingEvent ()
16827 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16828 {
16829     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16830     int newState = appData.showThinking
16831         // [HGM] thinking: other features now need thinking output as well
16832         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16833
16834     if (oldState == newState) return;
16835     oldState = newState;
16836     if (gameMode == EditPosition) EditPositionDone(TRUE);
16837     if (oldState) {
16838         SendToProgram("post\n", &first);
16839         if (gameMode == TwoMachinesPlay) {
16840             SendToProgram("post\n", &second);
16841         }
16842     } else {
16843         SendToProgram("nopost\n", &first);
16844         thinkOutput[0] = NULLCHAR;
16845         if (gameMode == TwoMachinesPlay) {
16846             SendToProgram("nopost\n", &second);
16847         }
16848     }
16849 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16850 }
16851
16852 void
16853 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16854 {
16855   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16856   if (pr == NoProc) return;
16857   AskQuestion(title, question, replyPrefix, pr);
16858 }
16859
16860 void
16861 TypeInEvent (char firstChar)
16862 {
16863     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16864         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16865         gameMode == AnalyzeMode || gameMode == EditGame ||
16866         gameMode == EditPosition || gameMode == IcsExamining ||
16867         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16868         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16869                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16870                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16871         gameMode == Training) PopUpMoveDialog(firstChar);
16872 }
16873
16874 void
16875 TypeInDoneEvent (char *move)
16876 {
16877         Board board;
16878         int n, fromX, fromY, toX, toY;
16879         char promoChar;
16880         ChessMove moveType;
16881
16882         // [HGM] FENedit
16883         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16884                 EditPositionPasteFEN(move);
16885                 return;
16886         }
16887         // [HGM] movenum: allow move number to be typed in any mode
16888         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16889           ToNrEvent(2*n-1);
16890           return;
16891         }
16892         // undocumented kludge: allow command-line option to be typed in!
16893         // (potentially fatal, and does not implement the effect of the option.)
16894         // should only be used for options that are values on which future decisions will be made,
16895         // and definitely not on options that would be used during initialization.
16896         if(strstr(move, "!!! -") == move) {
16897             ParseArgsFromString(move+4);
16898             return;
16899         }
16900
16901       if (gameMode != EditGame && currentMove != forwardMostMove &&
16902         gameMode != Training) {
16903         DisplayMoveError(_("Displayed move is not current"));
16904       } else {
16905         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16906           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16907         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16908         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16909           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16910           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16911         } else {
16912           DisplayMoveError(_("Could not parse move"));
16913         }
16914       }
16915 }
16916
16917 void
16918 DisplayMove (int moveNumber)
16919 {
16920     char message[MSG_SIZ];
16921     char res[MSG_SIZ];
16922     char cpThinkOutput[MSG_SIZ];
16923
16924     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16925
16926     if (moveNumber == forwardMostMove - 1 ||
16927         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16928
16929         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16930
16931         if (strchr(cpThinkOutput, '\n')) {
16932             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16933         }
16934     } else {
16935         *cpThinkOutput = NULLCHAR;
16936     }
16937
16938     /* [AS] Hide thinking from human user */
16939     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16940         *cpThinkOutput = NULLCHAR;
16941         if( thinkOutput[0] != NULLCHAR ) {
16942             int i;
16943
16944             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16945                 cpThinkOutput[i] = '.';
16946             }
16947             cpThinkOutput[i] = NULLCHAR;
16948             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16949         }
16950     }
16951
16952     if (moveNumber == forwardMostMove - 1 &&
16953         gameInfo.resultDetails != NULL) {
16954         if (gameInfo.resultDetails[0] == NULLCHAR) {
16955           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16956         } else {
16957           snprintf(res, MSG_SIZ, " {%s} %s",
16958                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16959         }
16960     } else {
16961         res[0] = NULLCHAR;
16962     }
16963
16964     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16965         DisplayMessage(res, cpThinkOutput);
16966     } else {
16967       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16968                 WhiteOnMove(moveNumber) ? " " : ".. ",
16969                 parseList[moveNumber], res);
16970         DisplayMessage(message, cpThinkOutput);
16971     }
16972 }
16973
16974 void
16975 DisplayComment (int moveNumber, char *text)
16976 {
16977     char title[MSG_SIZ];
16978
16979     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16980       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16981     } else {
16982       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16983               WhiteOnMove(moveNumber) ? " " : ".. ",
16984               parseList[moveNumber]);
16985     }
16986     if (text != NULL && (appData.autoDisplayComment || commentUp))
16987         CommentPopUp(title, text);
16988 }
16989
16990 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16991  * might be busy thinking or pondering.  It can be omitted if your
16992  * gnuchess is configured to stop thinking immediately on any user
16993  * input.  However, that gnuchess feature depends on the FIONREAD
16994  * ioctl, which does not work properly on some flavors of Unix.
16995  */
16996 void
16997 Attention (ChessProgramState *cps)
16998 {
16999 #if ATTENTION
17000     if (!cps->useSigint) return;
17001     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17002     switch (gameMode) {
17003       case MachinePlaysWhite:
17004       case MachinePlaysBlack:
17005       case TwoMachinesPlay:
17006       case IcsPlayingWhite:
17007       case IcsPlayingBlack:
17008       case AnalyzeMode:
17009       case AnalyzeFile:
17010         /* Skip if we know it isn't thinking */
17011         if (!cps->maybeThinking) return;
17012         if (appData.debugMode)
17013           fprintf(debugFP, "Interrupting %s\n", cps->which);
17014         InterruptChildProcess(cps->pr);
17015         cps->maybeThinking = FALSE;
17016         break;
17017       default:
17018         break;
17019     }
17020 #endif /*ATTENTION*/
17021 }
17022
17023 int
17024 CheckFlags ()
17025 {
17026     if (whiteTimeRemaining <= 0) {
17027         if (!whiteFlag) {
17028             whiteFlag = TRUE;
17029             if (appData.icsActive) {
17030                 if (appData.autoCallFlag &&
17031                     gameMode == IcsPlayingBlack && !blackFlag) {
17032                   SendToICS(ics_prefix);
17033                   SendToICS("flag\n");
17034                 }
17035             } else {
17036                 if (blackFlag) {
17037                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17038                 } else {
17039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17040                     if (appData.autoCallFlag) {
17041                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17042                         return TRUE;
17043                     }
17044                 }
17045             }
17046         }
17047     }
17048     if (blackTimeRemaining <= 0) {
17049         if (!blackFlag) {
17050             blackFlag = TRUE;
17051             if (appData.icsActive) {
17052                 if (appData.autoCallFlag &&
17053                     gameMode == IcsPlayingWhite && !whiteFlag) {
17054                   SendToICS(ics_prefix);
17055                   SendToICS("flag\n");
17056                 }
17057             } else {
17058                 if (whiteFlag) {
17059                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17060                 } else {
17061                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17062                     if (appData.autoCallFlag) {
17063                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17064                         return TRUE;
17065                     }
17066                 }
17067             }
17068         }
17069     }
17070     return FALSE;
17071 }
17072
17073 void
17074 CheckTimeControl ()
17075 {
17076     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17077         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17078
17079     /*
17080      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17081      */
17082     if ( !WhiteOnMove(forwardMostMove) ) {
17083         /* White made time control */
17084         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17085         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17086         /* [HGM] time odds: correct new time quota for time odds! */
17087                                             / WhitePlayer()->timeOdds;
17088         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17089     } else {
17090         lastBlack -= blackTimeRemaining;
17091         /* Black made time control */
17092         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17093                                             / WhitePlayer()->other->timeOdds;
17094         lastWhite = whiteTimeRemaining;
17095     }
17096 }
17097
17098 void
17099 DisplayBothClocks ()
17100 {
17101     int wom = gameMode == EditPosition ?
17102       !blackPlaysFirst : WhiteOnMove(currentMove);
17103     DisplayWhiteClock(whiteTimeRemaining, wom);
17104     DisplayBlackClock(blackTimeRemaining, !wom);
17105 }
17106
17107
17108 /* Timekeeping seems to be a portability nightmare.  I think everyone
17109    has ftime(), but I'm really not sure, so I'm including some ifdefs
17110    to use other calls if you don't.  Clocks will be less accurate if
17111    you have neither ftime nor gettimeofday.
17112 */
17113
17114 /* VS 2008 requires the #include outside of the function */
17115 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17116 #include <sys/timeb.h>
17117 #endif
17118
17119 /* Get the current time as a TimeMark */
17120 void
17121 GetTimeMark (TimeMark *tm)
17122 {
17123 #if HAVE_GETTIMEOFDAY
17124
17125     struct timeval timeVal;
17126     struct timezone timeZone;
17127
17128     gettimeofday(&timeVal, &timeZone);
17129     tm->sec = (long) timeVal.tv_sec;
17130     tm->ms = (int) (timeVal.tv_usec / 1000L);
17131
17132 #else /*!HAVE_GETTIMEOFDAY*/
17133 #if HAVE_FTIME
17134
17135 // include <sys/timeb.h> / moved to just above start of function
17136     struct timeb timeB;
17137
17138     ftime(&timeB);
17139     tm->sec = (long) timeB.time;
17140     tm->ms = (int) timeB.millitm;
17141
17142 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17143     tm->sec = (long) time(NULL);
17144     tm->ms = 0;
17145 #endif
17146 #endif
17147 }
17148
17149 /* Return the difference in milliseconds between two
17150    time marks.  We assume the difference will fit in a long!
17151 */
17152 long
17153 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17154 {
17155     return 1000L*(tm2->sec - tm1->sec) +
17156            (long) (tm2->ms - tm1->ms);
17157 }
17158
17159
17160 /*
17161  * Code to manage the game clocks.
17162  *
17163  * In tournament play, black starts the clock and then white makes a move.
17164  * We give the human user a slight advantage if he is playing white---the
17165  * clocks don't run until he makes his first move, so it takes zero time.
17166  * Also, we don't account for network lag, so we could get out of sync
17167  * with GNU Chess's clock -- but then, referees are always right.
17168  */
17169
17170 static TimeMark tickStartTM;
17171 static long intendedTickLength;
17172
17173 long
17174 NextTickLength (long timeRemaining)
17175 {
17176     long nominalTickLength, nextTickLength;
17177
17178     if (timeRemaining > 0L && timeRemaining <= 10000L)
17179       nominalTickLength = 100L;
17180     else
17181       nominalTickLength = 1000L;
17182     nextTickLength = timeRemaining % nominalTickLength;
17183     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17184
17185     return nextTickLength;
17186 }
17187
17188 /* Adjust clock one minute up or down */
17189 void
17190 AdjustClock (Boolean which, int dir)
17191 {
17192     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17193     if(which) blackTimeRemaining += 60000*dir;
17194     else      whiteTimeRemaining += 60000*dir;
17195     DisplayBothClocks();
17196     adjustedClock = TRUE;
17197 }
17198
17199 /* Stop clocks and reset to a fresh time control */
17200 void
17201 ResetClocks ()
17202 {
17203     (void) StopClockTimer();
17204     if (appData.icsActive) {
17205         whiteTimeRemaining = blackTimeRemaining = 0;
17206     } else if (searchTime) {
17207         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17208         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17209     } else { /* [HGM] correct new time quote for time odds */
17210         whiteTC = blackTC = fullTimeControlString;
17211         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17212         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17213     }
17214     if (whiteFlag || blackFlag) {
17215         DisplayTitle("");
17216         whiteFlag = blackFlag = FALSE;
17217     }
17218     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17219     DisplayBothClocks();
17220     adjustedClock = FALSE;
17221 }
17222
17223 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17224
17225 /* Decrement running clock by amount of time that has passed */
17226 void
17227 DecrementClocks ()
17228 {
17229     long timeRemaining;
17230     long lastTickLength, fudge;
17231     TimeMark now;
17232
17233     if (!appData.clockMode) return;
17234     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17235
17236     GetTimeMark(&now);
17237
17238     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17239
17240     /* Fudge if we woke up a little too soon */
17241     fudge = intendedTickLength - lastTickLength;
17242     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17243
17244     if (WhiteOnMove(forwardMostMove)) {
17245         if(whiteNPS >= 0) lastTickLength = 0;
17246         timeRemaining = whiteTimeRemaining -= lastTickLength;
17247         if(timeRemaining < 0 && !appData.icsActive) {
17248             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17249             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17250                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17251                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17252             }
17253         }
17254         DisplayWhiteClock(whiteTimeRemaining - fudge,
17255                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17256     } else {
17257         if(blackNPS >= 0) lastTickLength = 0;
17258         timeRemaining = blackTimeRemaining -= lastTickLength;
17259         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17260             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17261             if(suddenDeath) {
17262                 blackStartMove = forwardMostMove;
17263                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17264             }
17265         }
17266         DisplayBlackClock(blackTimeRemaining - fudge,
17267                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17268     }
17269     if (CheckFlags()) return;
17270
17271     if(twoBoards) { // count down secondary board's clocks as well
17272         activePartnerTime -= lastTickLength;
17273         partnerUp = 1;
17274         if(activePartner == 'W')
17275             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17276         else
17277             DisplayBlackClock(activePartnerTime, TRUE);
17278         partnerUp = 0;
17279     }
17280
17281     tickStartTM = now;
17282     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17283     StartClockTimer(intendedTickLength);
17284
17285     /* if the time remaining has fallen below the alarm threshold, sound the
17286      * alarm. if the alarm has sounded and (due to a takeback or time control
17287      * with increment) the time remaining has increased to a level above the
17288      * threshold, reset the alarm so it can sound again.
17289      */
17290
17291     if (appData.icsActive && appData.icsAlarm) {
17292
17293         /* make sure we are dealing with the user's clock */
17294         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17295                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17296            )) return;
17297
17298         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17299             alarmSounded = FALSE;
17300         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17301             PlayAlarmSound();
17302             alarmSounded = TRUE;
17303         }
17304     }
17305 }
17306
17307
17308 /* A player has just moved, so stop the previously running
17309    clock and (if in clock mode) start the other one.
17310    We redisplay both clocks in case we're in ICS mode, because
17311    ICS gives us an update to both clocks after every move.
17312    Note that this routine is called *after* forwardMostMove
17313    is updated, so the last fractional tick must be subtracted
17314    from the color that is *not* on move now.
17315 */
17316 void
17317 SwitchClocks (int newMoveNr)
17318 {
17319     long lastTickLength;
17320     TimeMark now;
17321     int flagged = FALSE;
17322
17323     GetTimeMark(&now);
17324
17325     if (StopClockTimer() && appData.clockMode) {
17326         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17327         if (!WhiteOnMove(forwardMostMove)) {
17328             if(blackNPS >= 0) lastTickLength = 0;
17329             blackTimeRemaining -= lastTickLength;
17330            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17331 //         if(pvInfoList[forwardMostMove].time == -1)
17332                  pvInfoList[forwardMostMove].time =               // use GUI time
17333                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17334         } else {
17335            if(whiteNPS >= 0) lastTickLength = 0;
17336            whiteTimeRemaining -= lastTickLength;
17337            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17338 //         if(pvInfoList[forwardMostMove].time == -1)
17339                  pvInfoList[forwardMostMove].time =
17340                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17341         }
17342         flagged = CheckFlags();
17343     }
17344     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17345     CheckTimeControl();
17346
17347     if (flagged || !appData.clockMode) return;
17348
17349     switch (gameMode) {
17350       case MachinePlaysBlack:
17351       case MachinePlaysWhite:
17352       case BeginningOfGame:
17353         if (pausing) return;
17354         break;
17355
17356       case EditGame:
17357       case PlayFromGameFile:
17358       case IcsExamining:
17359         return;
17360
17361       default:
17362         break;
17363     }
17364
17365     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17366         if(WhiteOnMove(forwardMostMove))
17367              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17368         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17369     }
17370
17371     tickStartTM = now;
17372     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17373       whiteTimeRemaining : blackTimeRemaining);
17374     StartClockTimer(intendedTickLength);
17375 }
17376
17377
17378 /* Stop both clocks */
17379 void
17380 StopClocks ()
17381 {
17382     long lastTickLength;
17383     TimeMark now;
17384
17385     if (!StopClockTimer()) return;
17386     if (!appData.clockMode) return;
17387
17388     GetTimeMark(&now);
17389
17390     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17391     if (WhiteOnMove(forwardMostMove)) {
17392         if(whiteNPS >= 0) lastTickLength = 0;
17393         whiteTimeRemaining -= lastTickLength;
17394         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17395     } else {
17396         if(blackNPS >= 0) lastTickLength = 0;
17397         blackTimeRemaining -= lastTickLength;
17398         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17399     }
17400     CheckFlags();
17401 }
17402
17403 /* Start clock of player on move.  Time may have been reset, so
17404    if clock is already running, stop and restart it. */
17405 void
17406 StartClocks ()
17407 {
17408     (void) StopClockTimer(); /* in case it was running already */
17409     DisplayBothClocks();
17410     if (CheckFlags()) return;
17411
17412     if (!appData.clockMode) return;
17413     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17414
17415     GetTimeMark(&tickStartTM);
17416     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17417       whiteTimeRemaining : blackTimeRemaining);
17418
17419    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17420     whiteNPS = blackNPS = -1;
17421     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17422        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17423         whiteNPS = first.nps;
17424     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17425        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17426         blackNPS = first.nps;
17427     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17428         whiteNPS = second.nps;
17429     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17430         blackNPS = second.nps;
17431     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17432
17433     StartClockTimer(intendedTickLength);
17434 }
17435
17436 char *
17437 TimeString (long ms)
17438 {
17439     long second, minute, hour, day;
17440     char *sign = "";
17441     static char buf[32];
17442
17443     if (ms > 0 && ms <= 9900) {
17444       /* convert milliseconds to tenths, rounding up */
17445       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17446
17447       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17448       return buf;
17449     }
17450
17451     /* convert milliseconds to seconds, rounding up */
17452     /* use floating point to avoid strangeness of integer division
17453        with negative dividends on many machines */
17454     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17455
17456     if (second < 0) {
17457         sign = "-";
17458         second = -second;
17459     }
17460
17461     day = second / (60 * 60 * 24);
17462     second = second % (60 * 60 * 24);
17463     hour = second / (60 * 60);
17464     second = second % (60 * 60);
17465     minute = second / 60;
17466     second = second % 60;
17467
17468     if (day > 0)
17469       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17470               sign, day, hour, minute, second);
17471     else if (hour > 0)
17472       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17473     else
17474       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17475
17476     return buf;
17477 }
17478
17479
17480 /*
17481  * This is necessary because some C libraries aren't ANSI C compliant yet.
17482  */
17483 char *
17484 StrStr (char *string, char *match)
17485 {
17486     int i, length;
17487
17488     length = strlen(match);
17489
17490     for (i = strlen(string) - length; i >= 0; i--, string++)
17491       if (!strncmp(match, string, length))
17492         return string;
17493
17494     return NULL;
17495 }
17496
17497 char *
17498 StrCaseStr (char *string, char *match)
17499 {
17500     int i, j, length;
17501
17502     length = strlen(match);
17503
17504     for (i = strlen(string) - length; i >= 0; i--, string++) {
17505         for (j = 0; j < length; j++) {
17506             if (ToLower(match[j]) != ToLower(string[j]))
17507               break;
17508         }
17509         if (j == length) return string;
17510     }
17511
17512     return NULL;
17513 }
17514
17515 #ifndef _amigados
17516 int
17517 StrCaseCmp (char *s1, char *s2)
17518 {
17519     char c1, c2;
17520
17521     for (;;) {
17522         c1 = ToLower(*s1++);
17523         c2 = ToLower(*s2++);
17524         if (c1 > c2) return 1;
17525         if (c1 < c2) return -1;
17526         if (c1 == NULLCHAR) return 0;
17527     }
17528 }
17529
17530
17531 int
17532 ToLower (int c)
17533 {
17534     return isupper(c) ? tolower(c) : c;
17535 }
17536
17537
17538 int
17539 ToUpper (int c)
17540 {
17541     return islower(c) ? toupper(c) : c;
17542 }
17543 #endif /* !_amigados    */
17544
17545 char *
17546 StrSave (char *s)
17547 {
17548   char *ret;
17549
17550   if ((ret = (char *) malloc(strlen(s) + 1)))
17551     {
17552       safeStrCpy(ret, s, strlen(s)+1);
17553     }
17554   return ret;
17555 }
17556
17557 char *
17558 StrSavePtr (char *s, char **savePtr)
17559 {
17560     if (*savePtr) {
17561         free(*savePtr);
17562     }
17563     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17564       safeStrCpy(*savePtr, s, strlen(s)+1);
17565     }
17566     return(*savePtr);
17567 }
17568
17569 char *
17570 PGNDate ()
17571 {
17572     time_t clock;
17573     struct tm *tm;
17574     char buf[MSG_SIZ];
17575
17576     clock = time((time_t *)NULL);
17577     tm = localtime(&clock);
17578     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17579             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17580     return StrSave(buf);
17581 }
17582
17583
17584 char *
17585 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17586 {
17587     int i, j, fromX, fromY, toX, toY;
17588     int whiteToPlay;
17589     char buf[MSG_SIZ];
17590     char *p, *q;
17591     int emptycount;
17592     ChessSquare piece;
17593
17594     whiteToPlay = (gameMode == EditPosition) ?
17595       !blackPlaysFirst : (move % 2 == 0);
17596     p = buf;
17597
17598     /* Piece placement data */
17599     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17600         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17601         emptycount = 0;
17602         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17603             if (boards[move][i][j] == EmptySquare) {
17604                 emptycount++;
17605             } else { ChessSquare piece = boards[move][i][j];
17606                 if (emptycount > 0) {
17607                     if(emptycount<10) /* [HGM] can be >= 10 */
17608                         *p++ = '0' + emptycount;
17609                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17610                     emptycount = 0;
17611                 }
17612                 if(PieceToChar(piece) == '+') {
17613                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17614                     *p++ = '+';
17615                     piece = (ChessSquare)(DEMOTED piece);
17616                 }
17617                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17618                 if(p[-1] == '~') {
17619                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17620                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17621                     *p++ = '~';
17622                 }
17623             }
17624         }
17625         if (emptycount > 0) {
17626             if(emptycount<10) /* [HGM] can be >= 10 */
17627                 *p++ = '0' + emptycount;
17628             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17629             emptycount = 0;
17630         }
17631         *p++ = '/';
17632     }
17633     *(p - 1) = ' ';
17634
17635     /* [HGM] print Crazyhouse or Shogi holdings */
17636     if( gameInfo.holdingsWidth ) {
17637         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17638         q = p;
17639         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17640             piece = boards[move][i][BOARD_WIDTH-1];
17641             if( piece != EmptySquare )
17642               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17643                   *p++ = PieceToChar(piece);
17644         }
17645         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17646             piece = boards[move][BOARD_HEIGHT-i-1][0];
17647             if( piece != EmptySquare )
17648               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17649                   *p++ = PieceToChar(piece);
17650         }
17651
17652         if( q == p ) *p++ = '-';
17653         *p++ = ']';
17654         *p++ = ' ';
17655     }
17656
17657     /* Active color */
17658     *p++ = whiteToPlay ? 'w' : 'b';
17659     *p++ = ' ';
17660
17661   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17662     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17663   } else {
17664   if(nrCastlingRights) {
17665      q = p;
17666      if(appData.fischerCastling) {
17667        /* [HGM] write directly from rights */
17668            if(boards[move][CASTLING][2] != NoRights &&
17669               boards[move][CASTLING][0] != NoRights   )
17670                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17671            if(boards[move][CASTLING][2] != NoRights &&
17672               boards[move][CASTLING][1] != NoRights   )
17673                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17674            if(boards[move][CASTLING][5] != NoRights &&
17675               boards[move][CASTLING][3] != NoRights   )
17676                 *p++ = boards[move][CASTLING][3] + AAA;
17677            if(boards[move][CASTLING][5] != NoRights &&
17678               boards[move][CASTLING][4] != NoRights   )
17679                 *p++ = boards[move][CASTLING][4] + AAA;
17680      } else {
17681
17682         /* [HGM] write true castling rights */
17683         if( nrCastlingRights == 6 ) {
17684             int q, k=0;
17685             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17686                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17687             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17688                  boards[move][CASTLING][2] != NoRights  );
17689             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17690                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17691                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17692                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17693                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17694             }
17695             if(q) *p++ = 'Q';
17696             k = 0;
17697             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17698                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17699             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17700                  boards[move][CASTLING][5] != NoRights  );
17701             if(gameInfo.variant == VariantSChess) {
17702                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17703                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17704                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17705                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17706             }
17707             if(q) *p++ = 'q';
17708         }
17709      }
17710      if (q == p) *p++ = '-'; /* No castling rights */
17711      *p++ = ' ';
17712   }
17713
17714   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17715      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17716      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17717     /* En passant target square */
17718     if (move > backwardMostMove) {
17719         fromX = moveList[move - 1][0] - AAA;
17720         fromY = moveList[move - 1][1] - ONE;
17721         toX = moveList[move - 1][2] - AAA;
17722         toY = moveList[move - 1][3] - ONE;
17723         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17724             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17725             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17726             fromX == toX) {
17727             /* 2-square pawn move just happened */
17728             *p++ = toX + AAA;
17729             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17730         } else {
17731             *p++ = '-';
17732         }
17733     } else if(move == backwardMostMove) {
17734         // [HGM] perhaps we should always do it like this, and forget the above?
17735         if((signed char)boards[move][EP_STATUS] >= 0) {
17736             *p++ = boards[move][EP_STATUS] + AAA;
17737             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17738         } else {
17739             *p++ = '-';
17740         }
17741     } else {
17742         *p++ = '-';
17743     }
17744     *p++ = ' ';
17745   }
17746   }
17747
17748     if(moveCounts)
17749     {   int i = 0, j=move;
17750
17751         /* [HGM] find reversible plies */
17752         if (appData.debugMode) { int k;
17753             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17754             for(k=backwardMostMove; k<=forwardMostMove; k++)
17755                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17756
17757         }
17758
17759         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17760         if( j == backwardMostMove ) i += initialRulePlies;
17761         sprintf(p, "%d ", i);
17762         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17763
17764         /* Fullmove number */
17765         sprintf(p, "%d", (move / 2) + 1);
17766     } else *--p = NULLCHAR;
17767
17768     return StrSave(buf);
17769 }
17770
17771 Boolean
17772 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17773 {
17774     int i, j, k, w=0, subst=0, shuffle=0;
17775     char *p, c;
17776     int emptycount, virgin[BOARD_FILES];
17777     ChessSquare piece;
17778
17779     p = fen;
17780
17781     /* Piece placement data */
17782     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17783         j = 0;
17784         for (;;) {
17785             if (*p == '/' || *p == ' ' || *p == '[' ) {
17786                 if(j > w) w = j;
17787                 emptycount = gameInfo.boardWidth - j;
17788                 while (emptycount--)
17789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17790                 if (*p == '/') p++;
17791                 else if(autoSize) { // we stumbled unexpectedly into end of board
17792                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17793                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17794                     }
17795                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17796                 }
17797                 break;
17798 #if(BOARD_FILES >= 10)
17799             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17800                 p++; emptycount=10;
17801                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17802                 while (emptycount--)
17803                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17804 #endif
17805             } else if (*p == '*') {
17806                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17807             } else if (isdigit(*p)) {
17808                 emptycount = *p++ - '0';
17809                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17810                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17811                 while (emptycount--)
17812                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17813             } else if (*p == '<') {
17814                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17815                 else if (i != 0 || !shuffle) return FALSE;
17816                 p++;
17817             } else if (shuffle && *p == '>') {
17818                 p++; // for now ignore closing shuffle range, and assume rank-end
17819             } else if (*p == '?') {
17820                 if (j >= gameInfo.boardWidth) return FALSE;
17821                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17822                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17823             } else if (*p == '+' || isalpha(*p)) {
17824                 if (j >= gameInfo.boardWidth) return FALSE;
17825                 if(*p=='+') {
17826                     piece = CharToPiece(*++p);
17827                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17828                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17829                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17830                 } else piece = CharToPiece(*p++);
17831
17832                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17833                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17834                     piece = (ChessSquare) (PROMOTED piece);
17835                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17836                     p++;
17837                 }
17838                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17839             } else {
17840                 return FALSE;
17841             }
17842         }
17843     }
17844     while (*p == '/' || *p == ' ') p++;
17845
17846     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17847
17848     /* [HGM] by default clear Crazyhouse holdings, if present */
17849     if(gameInfo.holdingsWidth) {
17850        for(i=0; i<BOARD_HEIGHT; i++) {
17851            board[i][0]             = EmptySquare; /* black holdings */
17852            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17853            board[i][1]             = (ChessSquare) 0; /* black counts */
17854            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17855        }
17856     }
17857
17858     /* [HGM] look for Crazyhouse holdings here */
17859     while(*p==' ') p++;
17860     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17861         int swap=0, wcnt=0, bcnt=0;
17862         if(*p == '[') p++;
17863         if(*p == '<') swap++, p++;
17864         if(*p == '-' ) p++; /* empty holdings */ else {
17865             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17866             /* if we would allow FEN reading to set board size, we would   */
17867             /* have to add holdings and shift the board read so far here   */
17868             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17869                 p++;
17870                 if((int) piece >= (int) BlackPawn ) {
17871                     i = (int)piece - (int)BlackPawn;
17872                     i = PieceToNumber((ChessSquare)i);
17873                     if( i >= gameInfo.holdingsSize ) return FALSE;
17874                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17875                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17876                     bcnt++;
17877                 } else {
17878                     i = (int)piece - (int)WhitePawn;
17879                     i = PieceToNumber((ChessSquare)i);
17880                     if( i >= gameInfo.holdingsSize ) return FALSE;
17881                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17882                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17883                     wcnt++;
17884                 }
17885             }
17886             if(subst) { // substitute back-rank question marks by holdings pieces
17887                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17888                     int k, m, n = bcnt + 1;
17889                     if(board[0][j] == ClearBoard) {
17890                         if(!wcnt) return FALSE;
17891                         n = rand() % wcnt;
17892                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17893                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17894                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17895                             break;
17896                         }
17897                     }
17898                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17899                         if(!bcnt) return FALSE;
17900                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17901                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17902                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17903                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17904                             break;
17905                         }
17906                     }
17907                 }
17908                 subst = 0;
17909             }
17910         }
17911         if(*p == ']') p++;
17912     }
17913
17914     if(subst) return FALSE; // substitution requested, but no holdings
17915
17916     while(*p == ' ') p++;
17917
17918     /* Active color */
17919     c = *p++;
17920     if(appData.colorNickNames) {
17921       if( c == appData.colorNickNames[0] ) c = 'w'; else
17922       if( c == appData.colorNickNames[1] ) c = 'b';
17923     }
17924     switch (c) {
17925       case 'w':
17926         *blackPlaysFirst = FALSE;
17927         break;
17928       case 'b':
17929         *blackPlaysFirst = TRUE;
17930         break;
17931       default:
17932         return FALSE;
17933     }
17934
17935     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17936     /* return the extra info in global variiables             */
17937
17938     /* set defaults in case FEN is incomplete */
17939     board[EP_STATUS] = EP_UNKNOWN;
17940     for(i=0; i<nrCastlingRights; i++ ) {
17941         board[CASTLING][i] =
17942             appData.fischerCastling ? NoRights : initialRights[i];
17943     }   /* assume possible unless obviously impossible */
17944     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17945     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17946     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17947                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17948     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17949     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17950     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17951                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17952     FENrulePlies = 0;
17953
17954     while(*p==' ') p++;
17955     if(nrCastlingRights) {
17956       int fischer = 0;
17957       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17958       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17959           /* castling indicator present, so default becomes no castlings */
17960           for(i=0; i<nrCastlingRights; i++ ) {
17961                  board[CASTLING][i] = NoRights;
17962           }
17963       }
17964       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17965              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17966              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17967              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17968         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17969
17970         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17971             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17972             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17973         }
17974         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17975             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17976         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17977                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17978         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17979                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17980         switch(c) {
17981           case'K':
17982               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17983               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17984               board[CASTLING][2] = whiteKingFile;
17985               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17986               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17987               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17988               break;
17989           case'Q':
17990               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17991               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17992               board[CASTLING][2] = whiteKingFile;
17993               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17994               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17995               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17996               break;
17997           case'k':
17998               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17999               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18000               board[CASTLING][5] = blackKingFile;
18001               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18002               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18003               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18004               break;
18005           case'q':
18006               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18007               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18008               board[CASTLING][5] = blackKingFile;
18009               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18010               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18011               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18012           case '-':
18013               break;
18014           default: /* FRC castlings */
18015               if(c >= 'a') { /* black rights */
18016                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18017                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18018                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18019                   if(i == BOARD_RGHT) break;
18020                   board[CASTLING][5] = i;
18021                   c -= AAA;
18022                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18023                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18024                   if(c > i)
18025                       board[CASTLING][3] = c;
18026                   else
18027                       board[CASTLING][4] = c;
18028               } else { /* white rights */
18029                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18030                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18031                     if(board[0][i] == WhiteKing) break;
18032                   if(i == BOARD_RGHT) break;
18033                   board[CASTLING][2] = i;
18034                   c -= AAA - 'a' + 'A';
18035                   if(board[0][c] >= WhiteKing) break;
18036                   if(c > i)
18037                       board[CASTLING][0] = c;
18038                   else
18039                       board[CASTLING][1] = c;
18040               }
18041         }
18042       }
18043       for(i=0; i<nrCastlingRights; i++)
18044         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18045       if(gameInfo.variant == VariantSChess)
18046         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18047       if(fischer && shuffle) appData.fischerCastling = TRUE;
18048     if (appData.debugMode) {
18049         fprintf(debugFP, "FEN castling rights:");
18050         for(i=0; i<nrCastlingRights; i++)
18051         fprintf(debugFP, " %d", board[CASTLING][i]);
18052         fprintf(debugFP, "\n");
18053     }
18054
18055       while(*p==' ') p++;
18056     }
18057
18058     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18059
18060     /* read e.p. field in games that know e.p. capture */
18061     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18062        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18063        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18064       if(*p=='-') {
18065         p++; board[EP_STATUS] = EP_NONE;
18066       } else {
18067          char c = *p++ - AAA;
18068
18069          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18070          if(*p >= '0' && *p <='9') p++;
18071          board[EP_STATUS] = c;
18072       }
18073     }
18074
18075
18076     if(sscanf(p, "%d", &i) == 1) {
18077         FENrulePlies = i; /* 50-move ply counter */
18078         /* (The move number is still ignored)    */
18079     }
18080
18081     return TRUE;
18082 }
18083
18084 void
18085 EditPositionPasteFEN (char *fen)
18086 {
18087   if (fen != NULL) {
18088     Board initial_position;
18089
18090     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18091       DisplayError(_("Bad FEN position in clipboard"), 0);
18092       return ;
18093     } else {
18094       int savedBlackPlaysFirst = blackPlaysFirst;
18095       EditPositionEvent();
18096       blackPlaysFirst = savedBlackPlaysFirst;
18097       CopyBoard(boards[0], initial_position);
18098       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18099       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18100       DisplayBothClocks();
18101       DrawPosition(FALSE, boards[currentMove]);
18102     }
18103   }
18104 }
18105
18106 static char cseq[12] = "\\   ";
18107
18108 Boolean
18109 set_cont_sequence (char *new_seq)
18110 {
18111     int len;
18112     Boolean ret;
18113
18114     // handle bad attempts to set the sequence
18115         if (!new_seq)
18116                 return 0; // acceptable error - no debug
18117
18118     len = strlen(new_seq);
18119     ret = (len > 0) && (len < sizeof(cseq));
18120     if (ret)
18121       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18122     else if (appData.debugMode)
18123       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18124     return ret;
18125 }
18126
18127 /*
18128     reformat a source message so words don't cross the width boundary.  internal
18129     newlines are not removed.  returns the wrapped size (no null character unless
18130     included in source message).  If dest is NULL, only calculate the size required
18131     for the dest buffer.  lp argument indicats line position upon entry, and it's
18132     passed back upon exit.
18133 */
18134 int
18135 wrap (char *dest, char *src, int count, int width, int *lp)
18136 {
18137     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18138
18139     cseq_len = strlen(cseq);
18140     old_line = line = *lp;
18141     ansi = len = clen = 0;
18142
18143     for (i=0; i < count; i++)
18144     {
18145         if (src[i] == '\033')
18146             ansi = 1;
18147
18148         // if we hit the width, back up
18149         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18150         {
18151             // store i & len in case the word is too long
18152             old_i = i, old_len = len;
18153
18154             // find the end of the last word
18155             while (i && src[i] != ' ' && src[i] != '\n')
18156             {
18157                 i--;
18158                 len--;
18159             }
18160
18161             // word too long?  restore i & len before splitting it
18162             if ((old_i-i+clen) >= width)
18163             {
18164                 i = old_i;
18165                 len = old_len;
18166             }
18167
18168             // extra space?
18169             if (i && src[i-1] == ' ')
18170                 len--;
18171
18172             if (src[i] != ' ' && src[i] != '\n')
18173             {
18174                 i--;
18175                 if (len)
18176                     len--;
18177             }
18178
18179             // now append the newline and continuation sequence
18180             if (dest)
18181                 dest[len] = '\n';
18182             len++;
18183             if (dest)
18184                 strncpy(dest+len, cseq, cseq_len);
18185             len += cseq_len;
18186             line = cseq_len;
18187             clen = cseq_len;
18188             continue;
18189         }
18190
18191         if (dest)
18192             dest[len] = src[i];
18193         len++;
18194         if (!ansi)
18195             line++;
18196         if (src[i] == '\n')
18197             line = 0;
18198         if (src[i] == 'm')
18199             ansi = 0;
18200     }
18201     if (dest && appData.debugMode)
18202     {
18203         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18204             count, width, line, len, *lp);
18205         show_bytes(debugFP, src, count);
18206         fprintf(debugFP, "\ndest: ");
18207         show_bytes(debugFP, dest, len);
18208         fprintf(debugFP, "\n");
18209     }
18210     *lp = dest ? line : old_line;
18211
18212     return len;
18213 }
18214
18215 // [HGM] vari: routines for shelving variations
18216 Boolean modeRestore = FALSE;
18217
18218 void
18219 PushInner (int firstMove, int lastMove)
18220 {
18221         int i, j, nrMoves = lastMove - firstMove;
18222
18223         // push current tail of game on stack
18224         savedResult[storedGames] = gameInfo.result;
18225         savedDetails[storedGames] = gameInfo.resultDetails;
18226         gameInfo.resultDetails = NULL;
18227         savedFirst[storedGames] = firstMove;
18228         savedLast [storedGames] = lastMove;
18229         savedFramePtr[storedGames] = framePtr;
18230         framePtr -= nrMoves; // reserve space for the boards
18231         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18232             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18233             for(j=0; j<MOVE_LEN; j++)
18234                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18235             for(j=0; j<2*MOVE_LEN; j++)
18236                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18237             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18238             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18239             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18240             pvInfoList[firstMove+i-1].depth = 0;
18241             commentList[framePtr+i] = commentList[firstMove+i];
18242             commentList[firstMove+i] = NULL;
18243         }
18244
18245         storedGames++;
18246         forwardMostMove = firstMove; // truncate game so we can start variation
18247 }
18248
18249 void
18250 PushTail (int firstMove, int lastMove)
18251 {
18252         if(appData.icsActive) { // only in local mode
18253                 forwardMostMove = currentMove; // mimic old ICS behavior
18254                 return;
18255         }
18256         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18257
18258         PushInner(firstMove, lastMove);
18259         if(storedGames == 1) GreyRevert(FALSE);
18260         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18261 }
18262
18263 void
18264 PopInner (Boolean annotate)
18265 {
18266         int i, j, nrMoves;
18267         char buf[8000], moveBuf[20];
18268
18269         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18270         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18271         nrMoves = savedLast[storedGames] - currentMove;
18272         if(annotate) {
18273                 int cnt = 10;
18274                 if(!WhiteOnMove(currentMove))
18275                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18276                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18277                 for(i=currentMove; i<forwardMostMove; i++) {
18278                         if(WhiteOnMove(i))
18279                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18280                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18281                         strcat(buf, moveBuf);
18282                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18283                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18284                 }
18285                 strcat(buf, ")");
18286         }
18287         for(i=1; i<=nrMoves; i++) { // copy last variation back
18288             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18289             for(j=0; j<MOVE_LEN; j++)
18290                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18291             for(j=0; j<2*MOVE_LEN; j++)
18292                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18293             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18294             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18295             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18296             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18297             commentList[currentMove+i] = commentList[framePtr+i];
18298             commentList[framePtr+i] = NULL;
18299         }
18300         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18301         framePtr = savedFramePtr[storedGames];
18302         gameInfo.result = savedResult[storedGames];
18303         if(gameInfo.resultDetails != NULL) {
18304             free(gameInfo.resultDetails);
18305       }
18306         gameInfo.resultDetails = savedDetails[storedGames];
18307         forwardMostMove = currentMove + nrMoves;
18308 }
18309
18310 Boolean
18311 PopTail (Boolean annotate)
18312 {
18313         if(appData.icsActive) return FALSE; // only in local mode
18314         if(!storedGames) return FALSE; // sanity
18315         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18316
18317         PopInner(annotate);
18318         if(currentMove < forwardMostMove) ForwardEvent(); else
18319         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18320
18321         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18322         return TRUE;
18323 }
18324
18325 void
18326 CleanupTail ()
18327 {       // remove all shelved variations
18328         int i;
18329         for(i=0; i<storedGames; i++) {
18330             if(savedDetails[i])
18331                 free(savedDetails[i]);
18332             savedDetails[i] = NULL;
18333         }
18334         for(i=framePtr; i<MAX_MOVES; i++) {
18335                 if(commentList[i]) free(commentList[i]);
18336                 commentList[i] = NULL;
18337         }
18338         framePtr = MAX_MOVES-1;
18339         storedGames = 0;
18340 }
18341
18342 void
18343 LoadVariation (int index, char *text)
18344 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18345         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18346         int level = 0, move;
18347
18348         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18349         // first find outermost bracketing variation
18350         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18351             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18352                 if(*p == '{') wait = '}'; else
18353                 if(*p == '[') wait = ']'; else
18354                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18355                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18356             }
18357             if(*p == wait) wait = NULLCHAR; // closing ]} found
18358             p++;
18359         }
18360         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18361         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18362         end[1] = NULLCHAR; // clip off comment beyond variation
18363         ToNrEvent(currentMove-1);
18364         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18365         // kludge: use ParsePV() to append variation to game
18366         move = currentMove;
18367         ParsePV(start, TRUE, TRUE);
18368         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18369         ClearPremoveHighlights();
18370         CommentPopDown();
18371         ToNrEvent(currentMove+1);
18372 }
18373
18374 void
18375 LoadTheme ()
18376 {
18377     char *p, *q, buf[MSG_SIZ];
18378     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18379         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18380         ParseArgsFromString(buf);
18381         ActivateTheme(TRUE); // also redo colors
18382         return;
18383     }
18384     p = nickName;
18385     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18386     {
18387         int len;
18388         q = appData.themeNames;
18389         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18390       if(appData.useBitmaps) {
18391         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18392                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18393                 appData.liteBackTextureMode,
18394                 appData.darkBackTextureMode );
18395       } else {
18396         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18397                 Col2Text(2),   // lightSquareColor
18398                 Col2Text(3) ); // darkSquareColor
18399       }
18400       if(appData.useBorder) {
18401         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18402                 appData.border);
18403       } else {
18404         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18405       }
18406       if(appData.useFont) {
18407         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18408                 appData.renderPiecesWithFont,
18409                 appData.fontToPieceTable,
18410                 Col2Text(9),    // appData.fontBackColorWhite
18411                 Col2Text(10) ); // appData.fontForeColorBlack
18412       } else {
18413         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18414                 appData.pieceDirectory);
18415         if(!appData.pieceDirectory[0])
18416           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18417                 Col2Text(0),   // whitePieceColor
18418                 Col2Text(1) ); // blackPieceColor
18419       }
18420       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18421                 Col2Text(4),   // highlightSquareColor
18422                 Col2Text(5) ); // premoveHighlightColor
18423         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18424         if(insert != q) insert[-1] = NULLCHAR;
18425         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18426         if(q)   free(q);
18427     }
18428     ActivateTheme(FALSE);
18429 }