ac29d58ce1e23457decc8b57d6d788327a5b471b
[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, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
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 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1000         return;
1001     }
1002     p = engineName;
1003     while(q = strchr(p, SLASH)) p = q+1;
1004     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005     if(engineDir[0] != NULLCHAR) {
1006         ASSIGN(appData.directory[i], engineDir); p = engineName;
1007     } else if(p != engineName) { // derive directory from engine path, when not given
1008         p[-1] = 0;
1009         ASSIGN(appData.directory[i], engineName);
1010         p[-1] = SLASH;
1011         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012     } else { ASSIGN(appData.directory[i], "."); }
1013     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014     if(params[0]) {
1015         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016         snprintf(command, MSG_SIZ, "%s %s", p, params);
1017         p = command;
1018     }
1019     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020     ASSIGN(appData.chessProgram[i], p);
1021     appData.isUCI[i] = isUCI;
1022     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023     appData.hasOwnBookUCI[i] = hasBook;
1024     if(!nickName[0]) useNick = FALSE;
1025     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1026     if(addToList) {
1027         int len;
1028         char quote;
1029         q = firstChessProgramNames;
1030         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033                         quote, p, quote, appData.directory[i],
1034                         useNick ? " -fn \"" : "",
1035                         useNick ? nickName : "",
1036                         useNick ? "\"" : "",
1037                         v1 ? " -firstProtocolVersion 1" : "",
1038                         hasBook ? "" : " -fNoOwnBookUCI",
1039                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040                         storeVariant ? " -variant " : "",
1041                         storeVariant ? VariantName(gameInfo.variant) : "");
1042         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044         if(insert != q) insert[-1] = NULLCHAR;
1045         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046         if(q)   free(q);
1047         FloatToFront(&appData.recentEngineList, buf);
1048     }
1049     ReplaceEngine(cps, i);
1050 }
1051
1052 void
1053 InitTimeControls ()
1054 {
1055     int matched, min, sec;
1056     /*
1057      * Parse timeControl resource
1058      */
1059     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060                           appData.movesPerSession)) {
1061         char buf[MSG_SIZ];
1062         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063         DisplayFatalError(buf, 0, 2);
1064     }
1065
1066     /*
1067      * Parse searchTime resource
1068      */
1069     if (*appData.searchTime != NULLCHAR) {
1070         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071         if (matched == 1) {
1072             searchTime = min * 60;
1073         } else if (matched == 2) {
1074             searchTime = min * 60 + sec;
1075         } else {
1076             char buf[MSG_SIZ];
1077             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078             DisplayFatalError(buf, 0, 2);
1079         }
1080     }
1081 }
1082
1083 void
1084 InitBackEnd1 ()
1085 {
1086
1087     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089
1090     GetTimeMark(&programStartTime);
1091     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092     appData.seedBase = random() + (random()<<15);
1093     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094
1095     ClearProgramStats();
1096     programStats.ok_to_send = 1;
1097     programStats.seen_stat = 0;
1098
1099     /*
1100      * Initialize game list
1101      */
1102     ListNew(&gameList);
1103
1104
1105     /*
1106      * Internet chess server status
1107      */
1108     if (appData.icsActive) {
1109         appData.matchMode = FALSE;
1110         appData.matchGames = 0;
1111 #if ZIPPY
1112         appData.noChessProgram = !appData.zippyPlay;
1113 #else
1114         appData.zippyPlay = FALSE;
1115         appData.zippyTalk = FALSE;
1116         appData.noChessProgram = TRUE;
1117 #endif
1118         if (*appData.icsHelper != NULLCHAR) {
1119             appData.useTelnet = TRUE;
1120             appData.telnetProgram = appData.icsHelper;
1121         }
1122     } else {
1123         appData.zippyTalk = appData.zippyPlay = FALSE;
1124     }
1125
1126     /* [AS] Initialize pv info list [HGM] and game state */
1127     {
1128         int i, j;
1129
1130         for( i=0; i<=framePtr; i++ ) {
1131             pvInfoList[i].depth = -1;
1132             boards[i][EP_STATUS] = EP_NONE;
1133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1134         }
1135     }
1136
1137     InitTimeControls();
1138
1139     /* [AS] Adjudication threshold */
1140     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141
1142     InitEngine(&first, 0);
1143     InitEngine(&second, 1);
1144     CommonEngineInit();
1145
1146     pairing.which = "pairing"; // pairing engine
1147     pairing.pr = NoProc;
1148     pairing.isr = NULL;
1149     pairing.program = appData.pairingEngine;
1150     pairing.host = "localhost";
1151     pairing.dir = ".";
1152
1153     if (appData.icsActive) {
1154         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1155     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156         appData.clockMode = FALSE;
1157         first.sendTime = second.sendTime = 0;
1158     }
1159
1160 #if ZIPPY
1161     /* Override some settings from environment variables, for backward
1162        compatibility.  Unfortunately it's not feasible to have the env
1163        vars just set defaults, at least in xboard.  Ugh.
1164     */
1165     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166       ZippyInit();
1167     }
1168 #endif
1169
1170     if (!appData.icsActive) {
1171       char buf[MSG_SIZ];
1172       int len;
1173
1174       /* Check for variants that are supported only in ICS mode,
1175          or not at all.  Some that are accepted here nevertheless
1176          have bugs; see comments below.
1177       */
1178       VariantClass variant = StringToVariant(appData.variant);
1179       switch (variant) {
1180       case VariantBughouse:     /* need four players and two boards */
1181       case VariantKriegspiel:   /* need to hide pieces and move details */
1182         /* case VariantFischeRandom: (Fabien: moved below) */
1183         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184         if( (len >= MSG_SIZ) && appData.debugMode )
1185           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186
1187         DisplayFatalError(buf, 0, 2);
1188         return;
1189
1190       case VariantUnknown:
1191       case VariantLoadable:
1192       case Variant29:
1193       case Variant30:
1194       case Variant31:
1195       case Variant32:
1196       case Variant33:
1197       case Variant34:
1198       case Variant35:
1199       case Variant36:
1200       default:
1201         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202         if( (len >= MSG_SIZ) && appData.debugMode )
1203           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204
1205         DisplayFatalError(buf, 0, 2);
1206         return;
1207
1208       case VariantNormal:     /* definitely works! */
1209         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211           return;
1212         }
1213       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1214       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1215       case VariantGothic:     /* [HGM] should work */
1216       case VariantCapablanca: /* [HGM] should work */
1217       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1218       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1219       case VariantChu:        /* [HGM] experimental */
1220       case VariantKnightmate: /* [HGM] should work */
1221       case VariantCylinder:   /* [HGM] untested */
1222       case VariantFalcon:     /* [HGM] untested */
1223       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224                                  offboard interposition not understood */
1225       case VariantWildCastle: /* pieces not automatically shuffled */
1226       case VariantNoCastle:   /* pieces not automatically shuffled */
1227       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228       case VariantLosers:     /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantSuicide:    /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantGiveaway:   /* should work except for win condition,
1233                                  and doesn't know captures are mandatory */
1234       case VariantTwoKings:   /* should work */
1235       case VariantAtomic:     /* should work except for win condition */
1236       case Variant3Check:     /* should work except for win condition */
1237       case VariantShatranj:   /* should work except for all win conditions */
1238       case VariantMakruk:     /* should work except for draw countdown */
1239       case VariantASEAN :     /* should work except for draw countdown */
1240       case VariantBerolina:   /* might work if TestLegality is off */
1241       case VariantCapaRandom: /* should work */
1242       case VariantJanus:      /* should work */
1243       case VariantSuper:      /* experimental */
1244       case VariantGreat:      /* experimental, requires legality testing to be off */
1245       case VariantSChess:     /* S-Chess, should work */
1246       case VariantGrand:      /* should work */
1247       case VariantSpartan:    /* should work */
1248       case VariantLion:       /* should work */
1249       case VariantChuChess:   /* should work */
1250         break;
1251       }
1252     }
1253
1254 }
1255
1256 int
1257 NextIntegerFromString (char ** str, long * value)
1258 {
1259     int result = -1;
1260     char * s = *str;
1261
1262     while( *s == ' ' || *s == '\t' ) {
1263         s++;
1264     }
1265
1266     *value = 0;
1267
1268     if( *s >= '0' && *s <= '9' ) {
1269         while( *s >= '0' && *s <= '9' ) {
1270             *value = *value * 10 + (*s - '0');
1271             s++;
1272         }
1273
1274         result = 0;
1275     }
1276
1277     *str = s;
1278
1279     return result;
1280 }
1281
1282 int
1283 NextTimeControlFromString (char ** str, long * value)
1284 {
1285     long temp;
1286     int result = NextIntegerFromString( str, &temp );
1287
1288     if( result == 0 ) {
1289         *value = temp * 60; /* Minutes */
1290         if( **str == ':' ) {
1291             (*str)++;
1292             result = NextIntegerFromString( str, &temp );
1293             *value += temp; /* Seconds */
1294         }
1295     }
1296
1297     return result;
1298 }
1299
1300 int
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303     int result = -1, type = 0; long temp, temp2;
1304
1305     if(**str != ':') return -1; // old params remain in force!
1306     (*str)++;
1307     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308     if( NextIntegerFromString( str, &temp ) ) return -1;
1309     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310
1311     if(**str != '/') {
1312         /* time only: incremental or sudden-death time control */
1313         if(**str == '+') { /* increment follows; read it */
1314             (*str)++;
1315             if(**str == '!') type = *(*str)++; // Bronstein TC
1316             if(result = NextIntegerFromString( str, &temp2)) return -1;
1317             *inc = temp2 * 1000;
1318             if(**str == '.') { // read fraction of increment
1319                 char *start = ++(*str);
1320                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321                 temp2 *= 1000;
1322                 while(start++ < *str) temp2 /= 10;
1323                 *inc += temp2;
1324             }
1325         } else *inc = 0;
1326         *moves = 0; *tc = temp * 1000; *incType = type;
1327         return 0;
1328     }
1329
1330     (*str)++; /* classical time control */
1331     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1332
1333     if(result == 0) {
1334         *moves = temp;
1335         *tc    = temp2 * 1000;
1336         *inc   = 0;
1337         *incType = type;
1338     }
1339     return result;
1340 }
1341
1342 int
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 {   /* [HGM] get time to add from the multi-session time-control string */
1345     int incType, moves=1; /* kludge to force reading of first session */
1346     long time, increment;
1347     char *s = tcString;
1348
1349     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350     do {
1351         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353         if(movenr == -1) return time;    /* last move before new session     */
1354         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356         if(!moves) return increment;     /* current session is incremental   */
1357         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358     } while(movenr >= -1);               /* try again for next session       */
1359
1360     return 0; // no new time quota on this move
1361 }
1362
1363 int
1364 ParseTimeControl (char *tc, float ti, int mps)
1365 {
1366   long tc1;
1367   long tc2;
1368   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369   int min, sec=0;
1370
1371   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1374   if(ti > 0) {
1375
1376     if(mps)
1377       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378     else
1379       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380   } else {
1381     if(mps)
1382       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383     else
1384       snprintf(buf, MSG_SIZ, ":%s", mytc);
1385   }
1386   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387
1388   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1389     return FALSE;
1390   }
1391
1392   if( *tc == '/' ) {
1393     /* Parse second time control */
1394     tc++;
1395
1396     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1397       return FALSE;
1398     }
1399
1400     if( tc2 == 0 ) {
1401       return FALSE;
1402     }
1403
1404     timeControl_2 = tc2 * 1000;
1405   }
1406   else {
1407     timeControl_2 = 0;
1408   }
1409
1410   if( tc1 == 0 ) {
1411     return FALSE;
1412   }
1413
1414   timeControl = tc1 * 1000;
1415
1416   if (ti >= 0) {
1417     timeIncrement = ti * 1000;  /* convert to ms */
1418     movesPerSession = 0;
1419   } else {
1420     timeIncrement = 0;
1421     movesPerSession = mps;
1422   }
1423   return TRUE;
1424 }
1425
1426 void
1427 InitBackEnd2 ()
1428 {
1429     if (appData.debugMode) {
1430 #    ifdef __GIT_VERSION
1431       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 #    else
1433       fprintf(debugFP, "Version: %s\n", programVersion);
1434 #    endif
1435     }
1436     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437
1438     set_cont_sequence(appData.wrapContSeq);
1439     if (appData.matchGames > 0) {
1440         appData.matchMode = TRUE;
1441     } else if (appData.matchMode) {
1442         appData.matchGames = 1;
1443     }
1444     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445         appData.matchGames = appData.sameColorGames;
1446     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449     }
1450     Reset(TRUE, FALSE);
1451     if (appData.noChessProgram || first.protocolVersion == 1) {
1452       InitBackEnd3();
1453     } else {
1454       /* kludge: allow timeout for initial "feature" commands */
1455       FreezeUI();
1456       DisplayMessage("", _("Starting chess program"));
1457       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1458     }
1459 }
1460
1461 int
1462 CalculateIndex (int index, int gameNr)
1463 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464     int res;
1465     if(index > 0) return index; // fixed nmber
1466     if(index == 0) return 1;
1467     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1469     return res;
1470 }
1471
1472 int
1473 LoadGameOrPosition (int gameNr)
1474 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475     if (*appData.loadGameFile != NULLCHAR) {
1476         if (!LoadGameFromFile(appData.loadGameFile,
1477                 CalculateIndex(appData.loadGameIndex, gameNr),
1478                               appData.loadGameFile, FALSE)) {
1479             DisplayFatalError(_("Bad game file"), 0, 1);
1480             return 0;
1481         }
1482     } else if (*appData.loadPositionFile != NULLCHAR) {
1483         if (!LoadPositionFromFile(appData.loadPositionFile,
1484                 CalculateIndex(appData.loadPositionIndex, gameNr),
1485                                   appData.loadPositionFile)) {
1486             DisplayFatalError(_("Bad position file"), 0, 1);
1487             return 0;
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 void
1494 ReserveGame (int gameNr, char resChar)
1495 {
1496     FILE *tf = fopen(appData.tourneyFile, "r+");
1497     char *p, *q, c, buf[MSG_SIZ];
1498     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499     safeStrCpy(buf, lastMsg, MSG_SIZ);
1500     DisplayMessage(_("Pick new game"), "");
1501     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502     ParseArgsFromFile(tf);
1503     p = q = appData.results;
1504     if(appData.debugMode) {
1505       char *r = appData.participants;
1506       fprintf(debugFP, "results = '%s'\n", p);
1507       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508       fprintf(debugFP, "\n");
1509     }
1510     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511     nextGame = q - p;
1512     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513     safeStrCpy(q, p, strlen(p) + 2);
1514     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518         q[nextGame] = '*';
1519     }
1520     fseek(tf, -(strlen(p)+4), SEEK_END);
1521     c = fgetc(tf);
1522     if(c != '"') // depending on DOS or Unix line endings we can be one off
1523          fseek(tf, -(strlen(p)+2), SEEK_END);
1524     else fseek(tf, -(strlen(p)+3), SEEK_END);
1525     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526     DisplayMessage(buf, "");
1527     free(p); appData.results = q;
1528     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530       int round = appData.defaultMatchGames * appData.tourneyType;
1531       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1532          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533         UnloadEngine(&first);  // next game belongs to other pairing;
1534         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535     }
1536     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1537 }
1538
1539 void
1540 MatchEvent (int mode)
1541 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542         int dummy;
1543         if(matchMode) { // already in match mode: switch it off
1544             abortMatch = TRUE;
1545             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546             return;
1547         }
1548 //      if(gameMode != BeginningOfGame) {
1549 //          DisplayError(_("You can only start a match from the initial position."), 0);
1550 //          return;
1551 //      }
1552         abortMatch = FALSE;
1553         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554         /* Set up machine vs. machine match */
1555         nextGame = 0;
1556         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557         if(appData.tourneyFile[0]) {
1558             ReserveGame(-1, 0);
1559             if(nextGame > appData.matchGames) {
1560                 char buf[MSG_SIZ];
1561                 if(strchr(appData.results, '*') == NULL) {
1562                     FILE *f;
1563                     appData.tourneyCycles++;
1564                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565                         fclose(f);
1566                         NextTourneyGame(-1, &dummy);
1567                         ReserveGame(-1, 0);
1568                         if(nextGame <= appData.matchGames) {
1569                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570                             matchMode = mode;
1571                             ScheduleDelayedEvent(NextMatchGame, 10000);
1572                             return;
1573                         }
1574                     }
1575                 }
1576                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577                 DisplayError(buf, 0);
1578                 appData.tourneyFile[0] = 0;
1579                 return;
1580             }
1581         } else
1582         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1583             DisplayFatalError(_("Can't have a match with no chess programs"),
1584                               0, 2);
1585             return;
1586         }
1587         matchMode = mode;
1588         matchGame = roundNr = 1;
1589         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1590         NextMatchGame();
1591 }
1592
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594
1595 void
1596 InitBackEnd3 P((void))
1597 {
1598     GameMode initialMode;
1599     char buf[MSG_SIZ];
1600     int err, len;
1601
1602     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1603        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1604         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1605        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1606        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607         char c, *q = first.variants, *p = strchr(q, ',');
1608         if(p) *p = NULLCHAR;
1609         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610             int w, h, s;
1611             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614             Reset(TRUE, FALSE);         // and re-initialize
1615         }
1616         if(p) *p = ',';
1617     }
1618
1619     InitChessProgram(&first, startedFromSetupPosition);
1620
1621     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1622         free(programVersion);
1623         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626     }
1627
1628     if (appData.icsActive) {
1629 #ifdef WIN32
1630         /* [DM] Make a console window if needed [HGM] merged ifs */
1631         ConsoleCreate();
1632 #endif
1633         err = establish();
1634         if (err != 0)
1635           {
1636             if (*appData.icsCommPort != NULLCHAR)
1637               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638                              appData.icsCommPort);
1639             else
1640               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641                         appData.icsHost, appData.icsPort);
1642
1643             if( (len >= MSG_SIZ) && appData.debugMode )
1644               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645
1646             DisplayFatalError(buf, err, 1);
1647             return;
1648         }
1649         SetICSMode();
1650         telnetISR =
1651           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652         fromUserISR =
1653           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656     } else if (appData.noChessProgram) {
1657         SetNCPMode();
1658     } else {
1659         SetGNUMode();
1660     }
1661
1662     if (*appData.cmailGameName != NULLCHAR) {
1663         SetCmailMode();
1664         OpenLoopback(&cmailPR);
1665         cmailISR =
1666           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1667     }
1668
1669     ThawUI();
1670     DisplayMessage("", "");
1671     if (StrCaseCmp(appData.initialMode, "") == 0) {
1672       initialMode = BeginningOfGame;
1673       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677         ModeHighlight();
1678       }
1679     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680       initialMode = TwoMachinesPlay;
1681     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682       initialMode = AnalyzeFile;
1683     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684       initialMode = AnalyzeMode;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686       initialMode = MachinePlaysWhite;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688       initialMode = MachinePlaysBlack;
1689     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690       initialMode = EditGame;
1691     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692       initialMode = EditPosition;
1693     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694       initialMode = Training;
1695     } else {
1696       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697       if( (len >= MSG_SIZ) && appData.debugMode )
1698         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699
1700       DisplayFatalError(buf, 0, 2);
1701       return;
1702     }
1703
1704     if (appData.matchMode) {
1705         if(appData.tourneyFile[0]) { // start tourney from command line
1706             FILE *f;
1707             if(f = fopen(appData.tourneyFile, "r")) {
1708                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709                 fclose(f);
1710                 appData.clockMode = TRUE;
1711                 SetGNUMode();
1712             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713         }
1714         MatchEvent(TRUE);
1715     } else if (*appData.cmailGameName != NULLCHAR) {
1716         /* Set up cmail mode */
1717         ReloadCmailMsgEvent(TRUE);
1718     } else {
1719         /* Set up other modes */
1720         if (initialMode == AnalyzeFile) {
1721           if (*appData.loadGameFile == NULLCHAR) {
1722             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1723             return;
1724           }
1725         }
1726         if (*appData.loadGameFile != NULLCHAR) {
1727             (void) LoadGameFromFile(appData.loadGameFile,
1728                                     appData.loadGameIndex,
1729                                     appData.loadGameFile, TRUE);
1730         } else if (*appData.loadPositionFile != NULLCHAR) {
1731             (void) LoadPositionFromFile(appData.loadPositionFile,
1732                                         appData.loadPositionIndex,
1733                                         appData.loadPositionFile);
1734             /* [HGM] try to make self-starting even after FEN load */
1735             /* to allow automatic setup of fairy variants with wtm */
1736             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737                 gameMode = BeginningOfGame;
1738                 setboardSpoiledMachineBlack = 1;
1739             }
1740             /* [HGM] loadPos: make that every new game uses the setup */
1741             /* from file as long as we do not switch variant          */
1742             if(!blackPlaysFirst) {
1743                 startedFromPositionFile = TRUE;
1744                 CopyBoard(filePosition, boards[0]);
1745                 CopyBoard(initialPosition, boards[0]);
1746             }
1747         } else if(*appData.fen != NULLCHAR) {
1748             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749                 startedFromPositionFile = TRUE;
1750                 Reset(TRUE, TRUE);
1751             }
1752         }
1753         if (initialMode == AnalyzeMode) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1756             return;
1757           }
1758           if (appData.icsActive) {
1759             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1760             return;
1761           }
1762           AnalyzeModeEvent();
1763         } else if (initialMode == AnalyzeFile) {
1764           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765           ShowThinkingEvent();
1766           AnalyzeFileEvent();
1767           AnalysisPeriodicEvent(1);
1768         } else if (initialMode == MachinePlaysWhite) {
1769           if (appData.noChessProgram) {
1770             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1771                               0, 2);
1772             return;
1773           }
1774           if (appData.icsActive) {
1775             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1776                               0, 2);
1777             return;
1778           }
1779           MachineWhiteEvent();
1780         } else if (initialMode == MachinePlaysBlack) {
1781           if (appData.noChessProgram) {
1782             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1783                               0, 2);
1784             return;
1785           }
1786           if (appData.icsActive) {
1787             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1788                               0, 2);
1789             return;
1790           }
1791           MachineBlackEvent();
1792         } else if (initialMode == TwoMachinesPlay) {
1793           if (appData.noChessProgram) {
1794             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1795                               0, 2);
1796             return;
1797           }
1798           if (appData.icsActive) {
1799             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1800                               0, 2);
1801             return;
1802           }
1803           TwoMachinesEvent();
1804         } else if (initialMode == EditGame) {
1805           EditGameEvent();
1806         } else if (initialMode == EditPosition) {
1807           EditPositionEvent();
1808         } else if (initialMode == Training) {
1809           if (*appData.loadGameFile == NULLCHAR) {
1810             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811             return;
1812           }
1813           TrainingEvent();
1814         }
1815     }
1816 }
1817
1818 void
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 {
1821     DisplayBook(current+1);
1822
1823     MoveHistorySet( movelist, first, last, current, pvInfoList );
1824
1825     EvalGraphSet( first, last, current, pvInfoList );
1826
1827     MakeEngineOutputTitle();
1828 }
1829
1830 /*
1831  * Establish will establish a contact to a remote host.port.
1832  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833  *  used to talk to the host.
1834  * Returns 0 if okay, error code if not.
1835  */
1836 int
1837 establish ()
1838 {
1839     char buf[MSG_SIZ];
1840
1841     if (*appData.icsCommPort != NULLCHAR) {
1842         /* Talk to the host through a serial comm port */
1843         return OpenCommPort(appData.icsCommPort, &icsPR);
1844
1845     } else if (*appData.gateway != NULLCHAR) {
1846         if (*appData.remoteShell == NULLCHAR) {
1847             /* Use the rcmd protocol to run telnet program on a gateway host */
1848             snprintf(buf, sizeof(buf), "%s %s %s",
1849                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1850             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1851
1852         } else {
1853             /* Use the rsh program to run telnet program on a gateway host */
1854             if (*appData.remoteUser == NULLCHAR) {
1855                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856                         appData.gateway, appData.telnetProgram,
1857                         appData.icsHost, appData.icsPort);
1858             } else {
1859                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860                         appData.remoteShell, appData.gateway,
1861                         appData.remoteUser, appData.telnetProgram,
1862                         appData.icsHost, appData.icsPort);
1863             }
1864             return StartChildProcess(buf, "", &icsPR);
1865
1866         }
1867     } else if (appData.useTelnet) {
1868         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1869
1870     } else {
1871         /* TCP socket interface differs somewhat between
1872            Unix and NT; handle details in the front end.
1873            */
1874         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1875     }
1876 }
1877
1878 void
1879 EscapeExpand (char *p, char *q)
1880 {       // [HGM] initstring: routine to shape up string arguments
1881         while(*p++ = *q++) if(p[-1] == '\\')
1882             switch(*q++) {
1883                 case 'n': p[-1] = '\n'; break;
1884                 case 'r': p[-1] = '\r'; break;
1885                 case 't': p[-1] = '\t'; break;
1886                 case '\\': p[-1] = '\\'; break;
1887                 case 0: *p = 0; return;
1888                 default: p[-1] = q[-1]; break;
1889             }
1890 }
1891
1892 void
1893 show_bytes (FILE *fp, char *buf, int count)
1894 {
1895     while (count--) {
1896         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897             fprintf(fp, "\\%03o", *buf & 0xff);
1898         } else {
1899             putc(*buf, fp);
1900         }
1901         buf++;
1902     }
1903     fflush(fp);
1904 }
1905
1906 /* Returns an errno value */
1907 int
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 {
1910     char buf[8192], *p, *q, *buflim;
1911     int left, newcount, outcount;
1912
1913     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914         *appData.gateway != NULLCHAR) {
1915         if (appData.debugMode) {
1916             fprintf(debugFP, ">ICS: ");
1917             show_bytes(debugFP, message, count);
1918             fprintf(debugFP, "\n");
1919         }
1920         return OutputToProcess(pr, message, count, outError);
1921     }
1922
1923     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1924     p = message;
1925     q = buf;
1926     left = count;
1927     newcount = 0;
1928     while (left) {
1929         if (q >= buflim) {
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             q = buf;
1938             newcount = 0;
1939         }
1940         if (*p == '\n') {
1941             *q++ = '\r';
1942             newcount++;
1943         } else if (((unsigned char) *p) == TN_IAC) {
1944             *q++ = (char) TN_IAC;
1945             newcount ++;
1946         }
1947         *q++ = *p++;
1948         newcount++;
1949         left--;
1950     }
1951     if (appData.debugMode) {
1952         fprintf(debugFP, ">ICS: ");
1953         show_bytes(debugFP, buf, newcount);
1954         fprintf(debugFP, "\n");
1955     }
1956     outcount = OutputToProcess(pr, buf, newcount, outError);
1957     if (outcount < newcount) return -1; /* to be sure */
1958     return count;
1959 }
1960
1961 void
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 {
1964     int outError, outCount;
1965     static int gotEof = 0;
1966     static FILE *ini;
1967
1968     /* Pass data read from player on to ICS */
1969     if (count > 0) {
1970         gotEof = 0;
1971         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972         if (outCount < count) {
1973             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974         }
1975         if(have_sent_ICS_logon == 2) {
1976           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977             fprintf(ini, "%s", message);
1978             have_sent_ICS_logon = 3;
1979           } else
1980             have_sent_ICS_logon = 1;
1981         } else if(have_sent_ICS_logon == 3) {
1982             fprintf(ini, "%s", message);
1983             fclose(ini);
1984           have_sent_ICS_logon = 1;
1985         }
1986     } else if (count < 0) {
1987         RemoveInputSource(isr);
1988         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989     } else if (gotEof++ > 0) {
1990         RemoveInputSource(isr);
1991         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1992     }
1993 }
1994
1995 void
1996 KeepAlive ()
1997 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000     SendToICS("date\n");
2001     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2002 }
2003
2004 /* added routine for printf style output to ics */
2005 void
2006 ics_printf (char *format, ...)
2007 {
2008     char buffer[MSG_SIZ];
2009     va_list args;
2010
2011     va_start(args, format);
2012     vsnprintf(buffer, sizeof(buffer), format, args);
2013     buffer[sizeof(buffer)-1] = '\0';
2014     SendToICS(buffer);
2015     va_end(args);
2016 }
2017
2018 void
2019 SendToICS (char *s)
2020 {
2021     int count, outCount, outError;
2022
2023     if (icsPR == NoProc) return;
2024
2025     count = strlen(s);
2026     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 /* This is used for sending logon scripts to the ICS. Sending
2033    without a delay causes problems when using timestamp on ICC
2034    (at least on my machine). */
2035 void
2036 SendToICSDelayed (char *s, long msdelay)
2037 {
2038     int count, outCount, outError;
2039
2040     if (icsPR == NoProc) return;
2041
2042     count = strlen(s);
2043     if (appData.debugMode) {
2044         fprintf(debugFP, ">ICS: ");
2045         show_bytes(debugFP, s, count);
2046         fprintf(debugFP, "\n");
2047     }
2048     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049                                       msdelay);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055
2056 /* Remove all highlighting escape sequences in s
2057    Also deletes any suffix starting with '('
2058    */
2059 char *
2060 StripHighlightAndTitle (char *s)
2061 {
2062     static char retbuf[MSG_SIZ];
2063     char *p = retbuf;
2064
2065     while (*s != NULLCHAR) {
2066         while (*s == '\033') {
2067             while (*s != NULLCHAR && !isalpha(*s)) s++;
2068             if (*s != NULLCHAR) s++;
2069         }
2070         while (*s != NULLCHAR && *s != '\033') {
2071             if (*s == '(' || *s == '[') {
2072                 *p = NULLCHAR;
2073                 return retbuf;
2074             }
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 /* Remove all highlighting escape sequences in s */
2083 char *
2084 StripHighlight (char *s)
2085 {
2086     static char retbuf[MSG_SIZ];
2087     char *p = retbuf;
2088
2089     while (*s != NULLCHAR) {
2090         while (*s == '\033') {
2091             while (*s != NULLCHAR && !isalpha(*s)) s++;
2092             if (*s != NULLCHAR) s++;
2093         }
2094         while (*s != NULLCHAR && *s != '\033') {
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2104 char *
2105 VariantName (VariantClass v)
2106 {
2107     if(v == VariantUnknown || *engineVariant) return engineVariant;
2108     return variantNames[v];
2109 }
2110
2111
2112 /* Identify a variant from the strings the chess servers use or the
2113    PGN Variant tag names we use. */
2114 VariantClass
2115 StringToVariant (char *e)
2116 {
2117     char *p;
2118     int wnum = -1;
2119     VariantClass v = VariantNormal;
2120     int i, found = FALSE;
2121     char buf[MSG_SIZ], c;
2122     int len;
2123
2124     if (!e) return v;
2125
2126     /* [HGM] skip over optional board-size prefixes */
2127     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129         while( *e++ != '_');
2130     }
2131
2132     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2133         v = VariantNormal;
2134         found = TRUE;
2135     } else
2136     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137       if (p = StrCaseStr(e, variantNames[i])) {
2138         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139         v = (VariantClass) i;
2140         found = TRUE;
2141         break;
2142       }
2143     }
2144
2145     if (!found) {
2146       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147           || StrCaseStr(e, "wild/fr")
2148           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149         v = VariantFischeRandom;
2150       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151                  (i = 1, p = StrCaseStr(e, "w"))) {
2152         p += i;
2153         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2154         if (isdigit(*p)) {
2155           wnum = atoi(p);
2156         } else {
2157           wnum = -1;
2158         }
2159         switch (wnum) {
2160         case 0: /* FICS only, actually */
2161         case 1:
2162           /* Castling legal even if K starts on d-file */
2163           v = VariantWildCastle;
2164           break;
2165         case 2:
2166         case 3:
2167         case 4:
2168           /* Castling illegal even if K & R happen to start in
2169              normal positions. */
2170           v = VariantNoCastle;
2171           break;
2172         case 5:
2173         case 7:
2174         case 8:
2175         case 10:
2176         case 11:
2177         case 12:
2178         case 13:
2179         case 14:
2180         case 15:
2181         case 18:
2182         case 19:
2183           /* Castling legal iff K & R start in normal positions */
2184           v = VariantNormal;
2185           break;
2186         case 6:
2187         case 20:
2188         case 21:
2189           /* Special wilds for position setup; unclear what to do here */
2190           v = VariantLoadable;
2191           break;
2192         case 9:
2193           /* Bizarre ICC game */
2194           v = VariantTwoKings;
2195           break;
2196         case 16:
2197           v = VariantKriegspiel;
2198           break;
2199         case 17:
2200           v = VariantLosers;
2201           break;
2202         case 22:
2203           v = VariantFischeRandom;
2204           break;
2205         case 23:
2206           v = VariantCrazyhouse;
2207           break;
2208         case 24:
2209           v = VariantBughouse;
2210           break;
2211         case 25:
2212           v = Variant3Check;
2213           break;
2214         case 26:
2215           /* Not quite the same as FICS suicide! */
2216           v = VariantGiveaway;
2217           break;
2218         case 27:
2219           v = VariantAtomic;
2220           break;
2221         case 28:
2222           v = VariantShatranj;
2223           break;
2224
2225         /* Temporary names for future ICC types.  The name *will* change in
2226            the next xboard/WinBoard release after ICC defines it. */
2227         case 29:
2228           v = Variant29;
2229           break;
2230         case 30:
2231           v = Variant30;
2232           break;
2233         case 31:
2234           v = Variant31;
2235           break;
2236         case 32:
2237           v = Variant32;
2238           break;
2239         case 33:
2240           v = Variant33;
2241           break;
2242         case 34:
2243           v = Variant34;
2244           break;
2245         case 35:
2246           v = Variant35;
2247           break;
2248         case 36:
2249           v = Variant36;
2250           break;
2251         case 37:
2252           v = VariantShogi;
2253           break;
2254         case 38:
2255           v = VariantXiangqi;
2256           break;
2257         case 39:
2258           v = VariantCourier;
2259           break;
2260         case 40:
2261           v = VariantGothic;
2262           break;
2263         case 41:
2264           v = VariantCapablanca;
2265           break;
2266         case 42:
2267           v = VariantKnightmate;
2268           break;
2269         case 43:
2270           v = VariantFairy;
2271           break;
2272         case 44:
2273           v = VariantCylinder;
2274           break;
2275         case 45:
2276           v = VariantFalcon;
2277           break;
2278         case 46:
2279           v = VariantCapaRandom;
2280           break;
2281         case 47:
2282           v = VariantBerolina;
2283           break;
2284         case 48:
2285           v = VariantJanus;
2286           break;
2287         case 49:
2288           v = VariantSuper;
2289           break;
2290         case 50:
2291           v = VariantGreat;
2292           break;
2293         case -1:
2294           /* Found "wild" or "w" in the string but no number;
2295              must assume it's normal chess. */
2296           v = VariantNormal;
2297           break;
2298         default:
2299           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300           if( (len >= MSG_SIZ) && appData.debugMode )
2301             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302
2303           DisplayError(buf, 0);
2304           v = VariantUnknown;
2305           break;
2306         }
2307       }
2308     }
2309     if (appData.debugMode) {
2310       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311               e, wnum, VariantName(v));
2312     }
2313     return v;
2314 }
2315
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2318
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320    advance *index beyond it, and set leftover_start to the new value of
2321    *index; else return FALSE.  If pattern contains the character '*', it
2322    matches any sequence of characters not containing '\r', '\n', or the
2323    character following the '*' (if any), and the matched sequence(s) are
2324    copied into star_match.
2325    */
2326 int
2327 looking_at ( char *buf, int *index, char *pattern)
2328 {
2329     char *bufp = &buf[*index], *patternp = pattern;
2330     int star_count = 0;
2331     char *matchp = star_match[0];
2332
2333     for (;;) {
2334         if (*patternp == NULLCHAR) {
2335             *index = leftover_start = bufp - buf;
2336             *matchp = NULLCHAR;
2337             return TRUE;
2338         }
2339         if (*bufp == NULLCHAR) return FALSE;
2340         if (*patternp == '*') {
2341             if (*bufp == *(patternp + 1)) {
2342                 *matchp = NULLCHAR;
2343                 matchp = star_match[++star_count];
2344                 patternp += 2;
2345                 bufp++;
2346                 continue;
2347             } else if (*bufp == '\n' || *bufp == '\r') {
2348                 patternp++;
2349                 if (*patternp == NULLCHAR)
2350                   continue;
2351                 else
2352                   return FALSE;
2353             } else {
2354                 *matchp++ = *bufp++;
2355                 continue;
2356             }
2357         }
2358         if (*patternp != *bufp) return FALSE;
2359         patternp++;
2360         bufp++;
2361     }
2362 }
2363
2364 void
2365 SendToPlayer (char *data, int length)
2366 {
2367     int error, outCount;
2368     outCount = OutputToProcess(NoProc, data, length, &error);
2369     if (outCount < length) {
2370         DisplayFatalError(_("Error writing to display"), error, 1);
2371     }
2372 }
2373
2374 void
2375 PackHolding (char packed[], char *holding)
2376 {
2377     char *p = holding;
2378     char *q = packed;
2379     int runlength = 0;
2380     int curr = 9999;
2381     do {
2382         if (*p == curr) {
2383             runlength++;
2384         } else {
2385             switch (runlength) {
2386               case 0:
2387                 break;
2388               case 1:
2389                 *q++ = curr;
2390                 break;
2391               case 2:
2392                 *q++ = curr;
2393                 *q++ = curr;
2394                 break;
2395               default:
2396                 sprintf(q, "%d", runlength);
2397                 while (*q) q++;
2398                 *q++ = curr;
2399                 break;
2400             }
2401             runlength = 1;
2402             curr = *p;
2403         }
2404     } while (*p++);
2405     *q = NULLCHAR;
2406 }
2407
2408 /* Telnet protocol requests from the front end */
2409 void
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2411 {
2412     unsigned char msg[3];
2413     int outCount, outError;
2414
2415     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416
2417     if (appData.debugMode) {
2418         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2419         switch (ddww) {
2420           case TN_DO:
2421             ddwwStr = "DO";
2422             break;
2423           case TN_DONT:
2424             ddwwStr = "DONT";
2425             break;
2426           case TN_WILL:
2427             ddwwStr = "WILL";
2428             break;
2429           case TN_WONT:
2430             ddwwStr = "WONT";
2431             break;
2432           default:
2433             ddwwStr = buf1;
2434             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435             break;
2436         }
2437         switch (option) {
2438           case TN_ECHO:
2439             optionStr = "ECHO";
2440             break;
2441           default:
2442             optionStr = buf2;
2443             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2444             break;
2445         }
2446         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2447     }
2448     msg[0] = TN_IAC;
2449     msg[1] = ddww;
2450     msg[2] = option;
2451     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452     if (outCount < 3) {
2453         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2454     }
2455 }
2456
2457 void
2458 DoEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DO, TN_ECHO);
2462 }
2463
2464 void
2465 DontEcho ()
2466 {
2467     if (!appData.icsActive) return;
2468     TelnetRequest(TN_DONT, TN_ECHO);
2469 }
2470
2471 void
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 {
2474     /* put the holdings sent to us by the server on the board holdings area */
2475     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2476     char p;
2477     ChessSquare piece;
2478
2479     if(gameInfo.holdingsWidth < 2)  return;
2480     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481         return; // prevent overwriting by pre-board holdings
2482
2483     if( (int)lowestPiece >= BlackPawn ) {
2484         holdingsColumn = 0;
2485         countsColumn = 1;
2486         holdingsStartRow = BOARD_HEIGHT-1;
2487         direction = -1;
2488     } else {
2489         holdingsColumn = BOARD_WIDTH-1;
2490         countsColumn = BOARD_WIDTH-2;
2491         holdingsStartRow = 0;
2492         direction = 1;
2493     }
2494
2495     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496         board[i][holdingsColumn] = EmptySquare;
2497         board[i][countsColumn]   = (ChessSquare) 0;
2498     }
2499     while( (p=*holdings++) != NULLCHAR ) {
2500         piece = CharToPiece( ToUpper(p) );
2501         if(piece == EmptySquare) continue;
2502         /*j = (int) piece - (int) WhitePawn;*/
2503         j = PieceToNumber(piece);
2504         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505         if(j < 0) continue;               /* should not happen */
2506         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508         board[holdingsStartRow+j*direction][countsColumn]++;
2509     }
2510 }
2511
2512
2513 void
2514 VariantSwitch (Board board, VariantClass newVariant)
2515 {
2516    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517    static Board oldBoard;
2518
2519    startedFromPositionFile = FALSE;
2520    if(gameInfo.variant == newVariant) return;
2521
2522    /* [HGM] This routine is called each time an assignment is made to
2523     * gameInfo.variant during a game, to make sure the board sizes
2524     * are set to match the new variant. If that means adding or deleting
2525     * holdings, we shift the playing board accordingly
2526     * This kludge is needed because in ICS observe mode, we get boards
2527     * of an ongoing game without knowing the variant, and learn about the
2528     * latter only later. This can be because of the move list we requested,
2529     * in which case the game history is refilled from the beginning anyway,
2530     * but also when receiving holdings of a crazyhouse game. In the latter
2531     * case we want to add those holdings to the already received position.
2532     */
2533
2534
2535    if (appData.debugMode) {
2536      fprintf(debugFP, "Switch board from %s to %s\n",
2537              VariantName(gameInfo.variant), VariantName(newVariant));
2538      setbuf(debugFP, NULL);
2539    }
2540    shuffleOpenings = 0;       /* [HGM] shuffle */
2541    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2542    switch(newVariant)
2543      {
2544      case VariantShogi:
2545        newWidth = 9;  newHeight = 9;
2546        gameInfo.holdingsSize = 7;
2547      case VariantBughouse:
2548      case VariantCrazyhouse:
2549        newHoldingsWidth = 2; break;
2550      case VariantGreat:
2551        newWidth = 10;
2552      case VariantSuper:
2553        newHoldingsWidth = 2;
2554        gameInfo.holdingsSize = 8;
2555        break;
2556      case VariantGothic:
2557      case VariantCapablanca:
2558      case VariantCapaRandom:
2559        newWidth = 10;
2560      default:
2561        newHoldingsWidth = gameInfo.holdingsSize = 0;
2562      };
2563
2564    if(newWidth  != gameInfo.boardWidth  ||
2565       newHeight != gameInfo.boardHeight ||
2566       newHoldingsWidth != gameInfo.holdingsWidth ) {
2567
2568      /* shift position to new playing area, if needed */
2569      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574        for(i=0; i<newHeight; i++) {
2575          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577        }
2578      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583      }
2584      board[HOLDINGS_SET] = 0;
2585      gameInfo.boardWidth  = newWidth;
2586      gameInfo.boardHeight = newHeight;
2587      gameInfo.holdingsWidth = newHoldingsWidth;
2588      gameInfo.variant = newVariant;
2589      InitDrawingSizes(-2, 0);
2590    } else gameInfo.variant = newVariant;
2591    CopyBoard(oldBoard, board);   // remember correctly formatted board
2592      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2593    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2594 }
2595
2596 static int loggedOn = FALSE;
2597
2598 /*-- Game start info cache: --*/
2599 int gs_gamenum;
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\   ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2607
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2610
2611 // [HGM] seekgraph
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2615 #define SQUARE 0x80
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2624
2625 void
2626 PlotSeekAd (int i)
2627 {
2628         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630         if(r < minRating+100 && r >=0 ) r = minRating+100;
2631         if(r > maxRating) r = maxRating;
2632         if(tc < 1.f) tc = 1.f;
2633         if(tc > 95.f) tc = 95.f;
2634         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635         y = ((double)r - minRating)/(maxRating - minRating)
2636             * (h-vMargin-squareSize/8-1) + vMargin;
2637         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638         if(strstr(seekAdList[i], " u ")) color = 1;
2639         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640            !strstr(seekAdList[i], "bullet") &&
2641            !strstr(seekAdList[i], "blitz") &&
2642            !strstr(seekAdList[i], "standard") ) color = 2;
2643         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2645 }
2646
2647 void
2648 PlotSingleSeekAd (int i)
2649 {
2650         PlotSeekAd(i);
2651 }
2652
2653 void
2654 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2655 {
2656         char buf[MSG_SIZ], *ext = "";
2657         VariantClass v = StringToVariant(type);
2658         if(strstr(type, "wild")) {
2659             ext = type + 4; // append wild number
2660             if(v == VariantFischeRandom) type = "chess960"; else
2661             if(v == VariantLoadable) type = "setup"; else
2662             type = VariantName(v);
2663         }
2664         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670             seekNrList[nrOfSeekAds] = nr;
2671             zList[nrOfSeekAds] = 0;
2672             seekAdList[nrOfSeekAds++] = StrSave(buf);
2673             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2674         }
2675 }
2676
2677 void
2678 EraseSeekDot (int i)
2679 {
2680     int x = xList[i], y = yList[i], d=squareSize/4, k;
2681     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683     // now replot every dot that overlapped
2684     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685         int xx = xList[k], yy = yList[k];
2686         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687             DrawSeekDot(xx, yy, colorList[k]);
2688     }
2689 }
2690
2691 void
2692 RemoveSeekAd (int nr)
2693 {
2694         int i;
2695         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696             EraseSeekDot(i);
2697             if(seekAdList[i]) free(seekAdList[i]);
2698             seekAdList[i] = seekAdList[--nrOfSeekAds];
2699             seekNrList[i] = seekNrList[nrOfSeekAds];
2700             ratingList[i] = ratingList[nrOfSeekAds];
2701             colorList[i]  = colorList[nrOfSeekAds];
2702             tcList[i] = tcList[nrOfSeekAds];
2703             xList[i]  = xList[nrOfSeekAds];
2704             yList[i]  = yList[nrOfSeekAds];
2705             zList[i]  = zList[nrOfSeekAds];
2706             seekAdList[nrOfSeekAds] = NULL;
2707             break;
2708         }
2709 }
2710
2711 Boolean
2712 MatchSoughtLine (char *line)
2713 {
2714     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715     int nr, base, inc, u=0; char dummy;
2716
2717     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719        (u=1) &&
2720        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2722         // match: compact and save the line
2723         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2724         return TRUE;
2725     }
2726     return FALSE;
2727 }
2728
2729 int
2730 DrawSeekGraph ()
2731 {
2732     int i;
2733     if(!seekGraphUp) return FALSE;
2734     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2736
2737     DrawSeekBackground(0, 0, w, h);
2738     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742         yy = h-1-yy;
2743         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2744         if(i%500 == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2748         }
2749     }
2750     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751     for(i=1; i<100; i+=(i<10?1:5)) {
2752         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755             char buf[MSG_SIZ];
2756             snprintf(buf, MSG_SIZ, "%d", i);
2757             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2758         }
2759     }
2760     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2761     return TRUE;
2762 }
2763
2764 int
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 {
2767     static int lastDown = 0, displayed = 0, lastSecond;
2768     if(y < 0) return FALSE;
2769     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771         if(!seekGraphUp) return FALSE;
2772         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773         DrawPosition(TRUE, NULL);
2774         return TRUE;
2775     }
2776     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777         if(click == Release || moving) return FALSE;
2778         nrOfSeekAds = 0;
2779         soughtPending = TRUE;
2780         SendToICS(ics_prefix);
2781         SendToICS("sought\n"); // should this be "sought all"?
2782     } else { // issue challenge based on clicked ad
2783         int dist = 10000; int i, closest = 0, second = 0;
2784         for(i=0; i<nrOfSeekAds; i++) {
2785             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2786             if(d < dist) { dist = d; closest = i; }
2787             second += (d - zList[i] < 120); // count in-range ads
2788             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2789         }
2790         if(dist < 120) {
2791             char buf[MSG_SIZ];
2792             second = (second > 1);
2793             if(displayed != closest || second != lastSecond) {
2794                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795                 lastSecond = second; displayed = closest;
2796             }
2797             if(click == Press) {
2798                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2799                 lastDown = closest;
2800                 return TRUE;
2801             } // on press 'hit', only show info
2802             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804             SendToICS(ics_prefix);
2805             SendToICS(buf);
2806             return TRUE; // let incoming board of started game pop down the graph
2807         } else if(click == Release) { // release 'miss' is ignored
2808             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809             if(moving == 2) { // right up-click
2810                 nrOfSeekAds = 0; // refresh graph
2811                 soughtPending = TRUE;
2812                 SendToICS(ics_prefix);
2813                 SendToICS("sought\n"); // should this be "sought all"?
2814             }
2815             return TRUE;
2816         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817         // press miss or release hit 'pop down' seek graph
2818         seekGraphUp = FALSE;
2819         DrawPosition(TRUE, NULL);
2820     }
2821     return TRUE;
2822 }
2823
2824 void
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 {
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2836
2837     static int started = STARTED_NONE;
2838     static char parse[20000];
2839     static int parse_pos = 0;
2840     static char buf[BUF_SIZE + 1];
2841     static int firstTime = TRUE, intfSet = FALSE;
2842     static ColorClass prevColor = ColorNormal;
2843     static int savingComment = FALSE;
2844     static int cmatch = 0; // continuation sequence match
2845     char *bp;
2846     char str[MSG_SIZ];
2847     int i, oldi;
2848     int buf_len;
2849     int next_out;
2850     int tkind;
2851     int backup;    /* [DM] For zippy color lines */
2852     char *p;
2853     char talker[MSG_SIZ]; // [HGM] chat
2854     int channel, collective=0;
2855
2856     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857
2858     if (appData.debugMode) {
2859       if (!error) {
2860         fprintf(debugFP, "<ICS: ");
2861         show_bytes(debugFP, data, count);
2862         fprintf(debugFP, "\n");
2863       }
2864     }
2865
2866     if (appData.debugMode) { int f = forwardMostMove;
2867         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2870     }
2871     if (count > 0) {
2872         /* If last read ended with a partial line that we couldn't parse,
2873            prepend it to the new read and try again. */
2874         if (leftover_len > 0) {
2875             for (i=0; i<leftover_len; i++)
2876               buf[i] = buf[leftover_start + i];
2877         }
2878
2879     /* copy new characters into the buffer */
2880     bp = buf + leftover_len;
2881     buf_len=leftover_len;
2882     for (i=0; i<count; i++)
2883     {
2884         // ignore these
2885         if (data[i] == '\r')
2886             continue;
2887
2888         // join lines split by ICS?
2889         if (!appData.noJoin)
2890         {
2891             /*
2892                 Joining just consists of finding matches against the
2893                 continuation sequence, and discarding that sequence
2894                 if found instead of copying it.  So, until a match
2895                 fails, there's nothing to do since it might be the
2896                 complete sequence, and thus, something we don't want
2897                 copied.
2898             */
2899             if (data[i] == cont_seq[cmatch])
2900             {
2901                 cmatch++;
2902                 if (cmatch == strlen(cont_seq))
2903                 {
2904                     cmatch = 0; // complete match.  just reset the counter
2905
2906                     /*
2907                         it's possible for the ICS to not include the space
2908                         at the end of the last word, making our [correct]
2909                         join operation fuse two separate words.  the server
2910                         does this when the space occurs at the width setting.
2911                     */
2912                     if (!buf_len || buf[buf_len-1] != ' ')
2913                     {
2914                         *bp++ = ' ';
2915                         buf_len++;
2916                     }
2917                 }
2918                 continue;
2919             }
2920             else if (cmatch)
2921             {
2922                 /*
2923                     match failed, so we have to copy what matched before
2924                     falling through and copying this character.  In reality,
2925                     this will only ever be just the newline character, but
2926                     it doesn't hurt to be precise.
2927                 */
2928                 strncpy(bp, cont_seq, cmatch);
2929                 bp += cmatch;
2930                 buf_len += cmatch;
2931                 cmatch = 0;
2932             }
2933         }
2934
2935         // copy this char
2936         *bp++ = data[i];
2937         buf_len++;
2938     }
2939
2940         buf[buf_len] = NULLCHAR;
2941 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2942         next_out = 0;
2943         leftover_start = 0;
2944
2945         i = 0;
2946         while (i < buf_len) {
2947             /* Deal with part of the TELNET option negotiation
2948                protocol.  We refuse to do anything beyond the
2949                defaults, except that we allow the WILL ECHO option,
2950                which ICS uses to turn off password echoing when we are
2951                directly connected to it.  We reject this option
2952                if localLineEditing mode is on (always on in xboard)
2953                and we are talking to port 23, which might be a real
2954                telnet server that will try to keep WILL ECHO on permanently.
2955              */
2956             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958                 unsigned char option;
2959                 oldi = i;
2960                 switch ((unsigned char) buf[++i]) {
2961                   case TN_WILL:
2962                     if (appData.debugMode)
2963                       fprintf(debugFP, "\n<WILL ");
2964                     switch (option = (unsigned char) buf[++i]) {
2965                       case TN_ECHO:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "ECHO ");
2968                         /* Reply only if this is a change, according
2969                            to the protocol rules. */
2970                         if (remoteEchoOption) break;
2971                         if (appData.localLineEditing &&
2972                             atoi(appData.icsPort) == TN_PORT) {
2973                             TelnetRequest(TN_DONT, TN_ECHO);
2974                         } else {
2975                             EchoOff();
2976                             TelnetRequest(TN_DO, TN_ECHO);
2977                             remoteEchoOption = TRUE;
2978                         }
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", option);
2983                         /* Whatever this is, we don't want it. */
2984                         TelnetRequest(TN_DONT, option);
2985                         break;
2986                     }
2987                     break;
2988                   case TN_WONT:
2989                     if (appData.debugMode)
2990                       fprintf(debugFP, "\n<WONT ");
2991                     switch (option = (unsigned char) buf[++i]) {
2992                       case TN_ECHO:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "ECHO ");
2995                         /* Reply only if this is a change, according
2996                            to the protocol rules. */
2997                         if (!remoteEchoOption) break;
2998                         EchoOn();
2999                         TelnetRequest(TN_DONT, TN_ECHO);
3000                         remoteEchoOption = FALSE;
3001                         break;
3002                       default:
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", (unsigned char) option);
3005                         /* Whatever this is, it must already be turned
3006                            off, because we never agree to turn on
3007                            anything non-default, so according to the
3008                            protocol rules, we don't reply. */
3009                         break;
3010                     }
3011                     break;
3012                   case TN_DO:
3013                     if (appData.debugMode)
3014                       fprintf(debugFP, "\n<DO ");
3015                     switch (option = (unsigned char) buf[++i]) {
3016                       default:
3017                         /* Whatever this is, we refuse to do it. */
3018                         if (appData.debugMode)
3019                           fprintf(debugFP, "%d ", option);
3020                         TelnetRequest(TN_WONT, option);
3021                         break;
3022                     }
3023                     break;
3024                   case TN_DONT:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<DONT ");
3027                     switch (option = (unsigned char) buf[++i]) {
3028                       default:
3029                         if (appData.debugMode)
3030                           fprintf(debugFP, "%d ", option);
3031                         /* Whatever this is, we are already not doing
3032                            it, because we never agree to do anything
3033                            non-default, so according to the protocol
3034                            rules, we don't reply. */
3035                         break;
3036                     }
3037                     break;
3038                   case TN_IAC:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<IAC ");
3041                     /* Doubled IAC; pass it through */
3042                     i--;
3043                     break;
3044                   default:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047                     /* Drop all other telnet commands on the floor */
3048                     break;
3049                 }
3050                 if (oldi > next_out)
3051                   SendToPlayer(&buf[next_out], oldi - next_out);
3052                 if (++i > next_out)
3053                   next_out = i;
3054                 continue;
3055             }
3056
3057             /* OK, this at least will *usually* work */
3058             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3059                 loggedOn = TRUE;
3060             }
3061
3062             if (loggedOn && !intfSet) {
3063                 if (ics_type == ICS_ICC) {
3064                   snprintf(str, MSG_SIZ,
3065                           "/set-quietly interface %s\n/set-quietly style 12\n",
3066                           programVersion);
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3069                 } else if (ics_type == ICS_CHESSNET) {
3070                   snprintf(str, MSG_SIZ, "/style 12\n");
3071                 } else {
3072                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073                   strcat(str, programVersion);
3074                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 #ifdef WIN32
3078                   strcat(str, "$iset nohighlight 1\n");
3079 #endif
3080                   strcat(str, "$iset lock 1\n$style 12\n");
3081                 }
3082                 SendToICS(str);
3083                 NotifyFrontendLogin();
3084                 intfSet = TRUE;
3085             }
3086
3087             if (started == STARTED_COMMENT) {
3088                 /* Accumulate characters in comment */
3089                 parse[parse_pos++] = buf[i];
3090                 if (buf[i] == '\n') {
3091                     parse[parse_pos] = NULLCHAR;
3092                     if(chattingPartner>=0) {
3093                         char mess[MSG_SIZ];
3094                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095                         OutputChatMessage(chattingPartner, mess);
3096                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097                             int p;
3098                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101                                 OutputChatMessage(p, mess);
3102                                 break;
3103                             }
3104                         }
3105                         chattingPartner = -1;
3106                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3107                         collective = 0;
3108                     } else
3109                     if(!suppressKibitz) // [HGM] kibitz
3110                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112                         int nrDigit = 0, nrAlph = 0, j;
3113                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115                         parse[parse_pos] = NULLCHAR;
3116                         // try to be smart: if it does not look like search info, it should go to
3117                         // ICS interaction window after all, not to engine-output window.
3118                         for(j=0; j<parse_pos; j++) { // count letters and digits
3119                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3121                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3122                         }
3123                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124                             int depth=0; float score;
3125                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127                                 pvInfoList[forwardMostMove-1].depth = depth;
3128                                 pvInfoList[forwardMostMove-1].score = 100*score;
3129                             }
3130                             OutputKibitz(suppressKibitz, parse);
3131                         } else {
3132                             char tmp[MSG_SIZ];
3133                             if(gameMode == IcsObserving) // restore original ICS messages
3134                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136                             else
3137                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139                             SendToPlayer(tmp, strlen(tmp));
3140                         }
3141                         next_out = i+1; // [HGM] suppress printing in ICS window
3142                     }
3143                     started = STARTED_NONE;
3144                 } else {
3145                     /* Don't match patterns against characters in comment */
3146                     i++;
3147                     continue;
3148                 }
3149             }
3150             if (started == STARTED_CHATTER) {
3151                 if (buf[i] != '\n') {
3152                     /* Don't match patterns against characters in chatter */
3153                     i++;
3154                     continue;
3155                 }
3156                 started = STARTED_NONE;
3157                 if(suppressKibitz) next_out = i+1;
3158             }
3159
3160             /* Kludge to deal with rcmd protocol */
3161             if (firstTime && looking_at(buf, &i, "\001*")) {
3162                 DisplayFatalError(&buf[1], 0, 1);
3163                 continue;
3164             } else {
3165                 firstTime = FALSE;
3166             }
3167
3168             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3169                 ics_type = ICS_ICC;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176                 ics_type = ICS_FICS;
3177                 ics_prefix = "$";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183                 ics_type = ICS_CHESSNET;
3184                 ics_prefix = "/";
3185                 if (appData.debugMode)
3186                   fprintf(debugFP, "ics_type %d\n", ics_type);
3187                 continue;
3188             }
3189
3190             if (!loggedOn &&
3191                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3193                  looking_at(buf, &i, "will be \"*\""))) {
3194               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3195               continue;
3196             }
3197
3198             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199               char buf[MSG_SIZ];
3200               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201               DisplayIcsInteractionTitle(buf);
3202               have_set_title = TRUE;
3203             }
3204
3205             /* skip finger notes */
3206             if (started == STARTED_NONE &&
3207                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208                  (buf[i] == '1' && buf[i+1] == '0')) &&
3209                 buf[i+2] == ':' && buf[i+3] == ' ') {
3210               started = STARTED_CHATTER;
3211               i += 3;
3212               continue;
3213             }
3214
3215             oldi = i;
3216             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217             if(appData.seekGraph) {
3218                 if(soughtPending && MatchSoughtLine(buf+i)) {
3219                     i = strstr(buf+i, "rated") - buf;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     next_out = leftover_start = i;
3222                     started = STARTED_CHATTER;
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227                         && looking_at(buf, &i, "* ads displayed")) {
3228                     soughtPending = FALSE;
3229                     seekGraphUp = TRUE;
3230                     DrawSeekGraph();
3231                     continue;
3232                 }
3233                 if(appData.autoRefresh) {
3234                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235                         int s = (ics_type == ICS_ICC); // ICC format differs
3236                         if(seekGraphUp)
3237                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239                         looking_at(buf, &i, "*% "); // eat prompt
3240                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = i; // suppress
3243                         continue;
3244                     }
3245                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246                         char *p = star_match[0];
3247                         while(*p) {
3248                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3249                             while(*p && *p++ != ' '); // next
3250                         }
3251                         looking_at(buf, &i, "*% "); // eat prompt
3252                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = i;
3254                         continue;
3255                     }
3256                 }
3257             }
3258
3259             /* skip formula vars */
3260             if (started == STARTED_NONE &&
3261                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262               started = STARTED_CHATTER;
3263               i += 3;
3264               continue;
3265             }
3266
3267             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268             if (appData.autoKibitz && started == STARTED_NONE &&
3269                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3270                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3275                         suppressKibitz = TRUE;
3276                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = i;
3278                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279                                 && (gameMode == IcsPlayingWhite)) ||
3280                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3282                             started = STARTED_CHATTER; // own kibitz we simply discard
3283                         else {
3284                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285                             parse_pos = 0; parse[0] = NULLCHAR;
3286                             savingComment = TRUE;
3287                             suppressKibitz = gameMode != IcsObserving ? 2 :
3288                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3289                         }
3290                         continue;
3291                 } else
3292                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294                          && atoi(star_match[0])) {
3295                     // suppress the acknowledgements of our own autoKibitz
3296                     char *p;
3297                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299                     SendToPlayer(star_match[0], strlen(star_match[0]));
3300                     if(looking_at(buf, &i, "*% ")) // eat prompt
3301                         suppressKibitz = FALSE;
3302                     next_out = i;
3303                     continue;
3304                 }
3305             } // [HGM] kibitz: end of patch
3306
3307             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308
3309             // [HGM] chat: intercept tells by users for which we have an open chat window
3310             channel = -1;
3311             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312                                            looking_at(buf, &i, "* whispers:") ||
3313                                            looking_at(buf, &i, "* kibitzes:") ||
3314                                            looking_at(buf, &i, "* shouts:") ||
3315                                            looking_at(buf, &i, "* c-shouts:") ||
3316                                            looking_at(buf, &i, "--> * ") ||
3317                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321                 int p;
3322                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323                 chattingPartner = -1; collective = 0;
3324
3325                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329                     talker[0] = '['; strcat(talker, "] ");
3330                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331                     chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("kibitzes", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343                 for(p=0; p<MAX_CHAT; p++) {
3344                     collective = 1;
3345                     if(!strcmp("whispers", chatPartner[p])) {
3346                         talker[0] = '['; strcat(talker, "] ");
3347                         chattingPartner = p; break;
3348                     }
3349                 } else
3350                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351                   if(buf[i-8] == '-' && buf[i-3] == 't')
3352                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353                     collective = 1;
3354                     if(!strcmp("c-shouts", chatPartner[p])) {
3355                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                   if(chattingPartner < 0)
3360                   for(p=0; p<MAX_CHAT; p++) {
3361                     collective = 1;
3362                     if(!strcmp("shouts", chatPartner[p])) {
3363                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366                         chattingPartner = p; break;
3367                     }
3368                   }
3369                 }
3370                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372                     talker[0] = 0;
3373                     Colorize(ColorTell, FALSE);
3374                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375                     collective |= 2;
3376                     chattingPartner = p; break;
3377                 }
3378                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380                     started = STARTED_COMMENT;
3381                     parse_pos = 0; parse[0] = NULLCHAR;
3382                     savingComment = 3 + chattingPartner; // counts as TRUE
3383                     if(collective == 3) i = oldi; else {
3384                         suppressKibitz = TRUE;
3385                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3387                         continue;
3388                     }
3389                 }
3390             } // [HGM] chat: end of patch
3391
3392           backup = i;
3393             if (appData.zippyTalk || appData.zippyPlay) {
3394                 /* [DM] Backup address for color zippy lines */
3395 #if ZIPPY
3396                if (loggedOn == TRUE)
3397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3399                        ;
3400 #endif
3401             } // [DM] 'else { ' deleted
3402                 if (
3403                     /* Regular tells and says */
3404                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3405                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3406                     looking_at(buf, &i, "* says: ") ||
3407                     /* Don't color "message" or "messages" output */
3408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3409                     looking_at(buf, &i, "*. * at *:*: ") ||
3410                     looking_at(buf, &i, "--* (*:*): ") ||
3411                     /* Message notifications (same color as tells) */
3412                     looking_at(buf, &i, "* has left a message ") ||
3413                     looking_at(buf, &i, "* just sent you a message:\n") ||
3414                     /* Whispers and kibitzes */
3415                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3416                     looking_at(buf, &i, "* kibitzes: ") ||
3417                     /* Channel tells */
3418                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3419
3420                   if (tkind == 1 && strchr(star_match[0], ':')) {
3421                       /* Avoid "tells you:" spoofs in channels */
3422                      tkind = 3;
3423                   }
3424                   if (star_match[0][0] == NULLCHAR ||
3425                       strchr(star_match[0], ' ') ||
3426                       (tkind == 3 && strchr(star_match[1], ' '))) {
3427                     /* Reject bogus matches */
3428                     i = oldi;
3429                   } else {
3430                     if (appData.colorize) {
3431                       if (oldi > next_out) {
3432                         SendToPlayer(&buf[next_out], oldi - next_out);
3433                         next_out = oldi;
3434                       }
3435                       switch (tkind) {
3436                       case 1:
3437                         Colorize(ColorTell, FALSE);
3438                         curColor = ColorTell;
3439                         break;
3440                       case 2:
3441                         Colorize(ColorKibitz, FALSE);
3442                         curColor = ColorKibitz;
3443                         break;
3444                       case 3:
3445                         p = strrchr(star_match[1], '(');
3446                         if (p == NULL) {
3447                           p = star_match[1];
3448                         } else {
3449                           p++;
3450                         }
3451                         if (atoi(p) == 1) {
3452                           Colorize(ColorChannel1, FALSE);
3453                           curColor = ColorChannel1;
3454                         } else {
3455                           Colorize(ColorChannel, FALSE);
3456                           curColor = ColorChannel;
3457                         }
3458                         break;
3459                       case 5:
3460                         curColor = ColorNormal;
3461                         break;
3462                       }
3463                     }
3464                     if (started == STARTED_NONE && appData.autoComment &&
3465                         (gameMode == IcsObserving ||
3466                          gameMode == IcsPlayingWhite ||
3467                          gameMode == IcsPlayingBlack)) {
3468                       parse_pos = i - oldi;
3469                       memcpy(parse, &buf[oldi], parse_pos);
3470                       parse[parse_pos] = NULLCHAR;
3471                       started = STARTED_COMMENT;
3472                       savingComment = TRUE;
3473                     } else if(collective != 3) {
3474                       started = STARTED_CHATTER;
3475                       savingComment = FALSE;
3476                     }
3477                     loggedOn = TRUE;
3478                     continue;
3479                   }
3480                 }
3481
3482                 if (looking_at(buf, &i, "* s-shouts: ") ||
3483                     looking_at(buf, &i, "* c-shouts: ")) {
3484                     if (appData.colorize) {
3485                         if (oldi > next_out) {
3486                             SendToPlayer(&buf[next_out], oldi - next_out);
3487                             next_out = oldi;
3488                         }
3489                         Colorize(ColorSShout, FALSE);
3490                         curColor = ColorSShout;
3491                     }
3492                     loggedOn = TRUE;
3493                     started = STARTED_CHATTER;
3494                     continue;
3495                 }
3496
3497                 if (looking_at(buf, &i, "--->")) {
3498                     loggedOn = TRUE;
3499                     continue;
3500                 }
3501
3502                 if (looking_at(buf, &i, "* shouts: ") ||
3503                     looking_at(buf, &i, "--> ")) {
3504                     if (appData.colorize) {
3505                         if (oldi > next_out) {
3506                             SendToPlayer(&buf[next_out], oldi - next_out);
3507                             next_out = oldi;
3508                         }
3509                         Colorize(ColorShout, FALSE);
3510                         curColor = ColorShout;
3511                     }
3512                     loggedOn = TRUE;
3513                     started = STARTED_CHATTER;
3514                     continue;
3515                 }
3516
3517                 if (looking_at( buf, &i, "Challenge:")) {
3518                     if (appData.colorize) {
3519                         if (oldi > next_out) {
3520                             SendToPlayer(&buf[next_out], oldi - next_out);
3521                             next_out = oldi;
3522                         }
3523                         Colorize(ColorChallenge, FALSE);
3524                         curColor = ColorChallenge;
3525                     }
3526                     loggedOn = TRUE;
3527                     continue;
3528                 }
3529
3530                 if (looking_at(buf, &i, "* offers you") ||
3531                     looking_at(buf, &i, "* offers to be") ||
3532                     looking_at(buf, &i, "* would like to") ||
3533                     looking_at(buf, &i, "* requests to") ||
3534                     looking_at(buf, &i, "Your opponent offers") ||
3535                     looking_at(buf, &i, "Your opponent requests")) {
3536
3537                     if (appData.colorize) {
3538                         if (oldi > next_out) {
3539                             SendToPlayer(&buf[next_out], oldi - next_out);
3540                             next_out = oldi;
3541                         }
3542                         Colorize(ColorRequest, FALSE);
3543                         curColor = ColorRequest;
3544                     }
3545                     continue;
3546                 }
3547
3548                 if (looking_at(buf, &i, "* (*) seeking")) {
3549                     if (appData.colorize) {
3550                         if (oldi > next_out) {
3551                             SendToPlayer(&buf[next_out], oldi - next_out);
3552                             next_out = oldi;
3553                         }
3554                         Colorize(ColorSeek, FALSE);
3555                         curColor = ColorSeek;
3556                     }
3557                     continue;
3558             }
3559
3560           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3561
3562             if (looking_at(buf, &i, "\\   ")) {
3563                 if (prevColor != ColorNormal) {
3564                     if (oldi > next_out) {
3565                         SendToPlayer(&buf[next_out], oldi - next_out);
3566                         next_out = oldi;
3567                     }
3568                     Colorize(prevColor, TRUE);
3569                     curColor = prevColor;
3570                 }
3571                 if (savingComment) {
3572                     parse_pos = i - oldi;
3573                     memcpy(parse, &buf[oldi], parse_pos);
3574                     parse[parse_pos] = NULLCHAR;
3575                     started = STARTED_COMMENT;
3576                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3577                         chattingPartner = savingComment - 3; // kludge to remember the box
3578                 } else {
3579                     started = STARTED_CHATTER;
3580                 }
3581                 continue;
3582             }
3583
3584             if (looking_at(buf, &i, "Black Strength :") ||
3585                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3586                 looking_at(buf, &i, "<10>") ||
3587                 looking_at(buf, &i, "#@#")) {
3588                 /* Wrong board style */
3589                 loggedOn = TRUE;
3590                 SendToICS(ics_prefix);
3591                 SendToICS("set style 12\n");
3592                 SendToICS(ics_prefix);
3593                 SendToICS("refresh\n");
3594                 continue;
3595             }
3596
3597             if (looking_at(buf, &i, "login:")) {
3598               if (!have_sent_ICS_logon) {
3599                 if(ICSInitScript())
3600                   have_sent_ICS_logon = 1;
3601                 else // no init script was found
3602                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3603               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3605               }
3606                 continue;
3607             }
3608
3609             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3610                 (looking_at(buf, &i, "\n<12> ") ||
3611                  looking_at(buf, &i, "<12> "))) {
3612                 loggedOn = TRUE;
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_BOARD;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3623                 looking_at(buf, &i, "<b1> ")) {
3624                 if (oldi > next_out) {
3625                     SendToPlayer(&buf[next_out], oldi - next_out);
3626                 }
3627                 next_out = i;
3628                 started = STARTED_HOLDINGS;
3629                 parse_pos = 0;
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3634                 loggedOn = TRUE;
3635                 /* Header for a move list -- first line */
3636
3637                 switch (ics_getting_history) {
3638                   case H_FALSE:
3639                     switch (gameMode) {
3640                       case IcsIdle:
3641                       case BeginningOfGame:
3642                         /* User typed "moves" or "oldmoves" while we
3643                            were idle.  Pretend we asked for these
3644                            moves and soak them up so user can step
3645                            through them and/or save them.
3646                            */
3647                         Reset(FALSE, TRUE);
3648                         gameMode = IcsObserving;
3649                         ModeHighlight();
3650                         ics_gamenum = -1;
3651                         ics_getting_history = H_GOT_UNREQ_HEADER;
3652                         break;
3653                       case EditGame: /*?*/
3654                       case EditPosition: /*?*/
3655                         /* Should above feature work in these modes too? */
3656                         /* For now it doesn't */
3657                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3658                         break;
3659                       default:
3660                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3661                         break;
3662                     }
3663                     break;
3664                   case H_REQUESTED:
3665                     /* Is this the right one? */
3666                     if (gameInfo.white && gameInfo.black &&
3667                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3668                         strcmp(gameInfo.black, star_match[2]) == 0) {
3669                         /* All is well */
3670                         ics_getting_history = H_GOT_REQ_HEADER;
3671                     }
3672                     break;
3673                   case H_GOT_REQ_HEADER:
3674                   case H_GOT_UNREQ_HEADER:
3675                   case H_GOT_UNWANTED_HEADER:
3676                   case H_GETTING_MOVES:
3677                     /* Should not happen */
3678                     DisplayError(_("Error gathering move list: two headers"), 0);
3679                     ics_getting_history = H_FALSE;
3680                     break;
3681                 }
3682
3683                 /* Save player ratings into gameInfo if needed */
3684                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3685                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3686                     (gameInfo.whiteRating == -1 ||
3687                      gameInfo.blackRating == -1)) {
3688
3689                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3690                     gameInfo.blackRating = string_to_rating(star_match[3]);
3691                     if (appData.debugMode)
3692                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3693                               gameInfo.whiteRating, gameInfo.blackRating);
3694                 }
3695                 continue;
3696             }
3697
3698             if (looking_at(buf, &i,
3699               "* * match, initial time: * minute*, increment: * second")) {
3700                 /* Header for a move list -- second line */
3701                 /* Initial board will follow if this is a wild game */
3702                 if (gameInfo.event != NULL) free(gameInfo.event);
3703                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3704                 gameInfo.event = StrSave(str);
3705                 /* [HGM] we switched variant. Translate boards if needed. */
3706                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3707                 continue;
3708             }
3709
3710             if (looking_at(buf, &i, "Move  ")) {
3711                 /* Beginning of a move list */
3712                 switch (ics_getting_history) {
3713                   case H_FALSE:
3714                     /* Normally should not happen */
3715                     /* Maybe user hit reset while we were parsing */
3716                     break;
3717                   case H_REQUESTED:
3718                     /* Happens if we are ignoring a move list that is not
3719                      * the one we just requested.  Common if the user
3720                      * tries to observe two games without turning off
3721                      * getMoveList */
3722                     break;
3723                   case H_GETTING_MOVES:
3724                     /* Should not happen */
3725                     DisplayError(_("Error gathering move list: nested"), 0);
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                   case H_GOT_REQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES;
3731                     parse_pos = 0;
3732                     if (oldi > next_out) {
3733                         SendToPlayer(&buf[next_out], oldi - next_out);
3734                     }
3735                     break;
3736                   case H_GOT_UNREQ_HEADER:
3737                     ics_getting_history = H_GETTING_MOVES;
3738                     started = STARTED_MOVES_NOHIDE;
3739                     parse_pos = 0;
3740                     break;
3741                   case H_GOT_UNWANTED_HEADER:
3742                     ics_getting_history = H_FALSE;
3743                     break;
3744                 }
3745                 continue;
3746             }
3747
3748             if (looking_at(buf, &i, "% ") ||
3749                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3750                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3751                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3752                     soughtPending = FALSE;
3753                     seekGraphUp = TRUE;
3754                     DrawSeekGraph();
3755                 }
3756                 if(suppressKibitz) next_out = i;
3757                 savingComment = FALSE;
3758                 suppressKibitz = 0;
3759                 switch (started) {
3760                   case STARTED_MOVES:
3761                   case STARTED_MOVES_NOHIDE:
3762                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3763                     parse[parse_pos + i - oldi] = NULLCHAR;
3764                     ParseGameHistory(parse);
3765 #if ZIPPY
3766                     if (appData.zippyPlay && first.initDone) {
3767                         FeedMovesToProgram(&first, forwardMostMove);
3768                         if (gameMode == IcsPlayingWhite) {
3769                             if (WhiteOnMove(forwardMostMove)) {
3770                                 if (first.sendTime) {
3771                                   if (first.useColors) {
3772                                     SendToProgram("black\n", &first);
3773                                   }
3774                                   SendTimeRemaining(&first, TRUE);
3775                                 }
3776                                 if (first.useColors) {
3777                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3778                                 }
3779                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3780                                 first.maybeThinking = TRUE;
3781                             } else {
3782                                 if (first.usePlayother) {
3783                                   if (first.sendTime) {
3784                                     SendTimeRemaining(&first, TRUE);
3785                                   }
3786                                   SendToProgram("playother\n", &first);
3787                                   firstMove = FALSE;
3788                                 } else {
3789                                   firstMove = TRUE;
3790                                 }
3791                             }
3792                         } else if (gameMode == IcsPlayingBlack) {
3793                             if (!WhiteOnMove(forwardMostMove)) {
3794                                 if (first.sendTime) {
3795                                   if (first.useColors) {
3796                                     SendToProgram("white\n", &first);
3797                                   }
3798                                   SendTimeRemaining(&first, FALSE);
3799                                 }
3800                                 if (first.useColors) {
3801                                   SendToProgram("black\n", &first);
3802                                 }
3803                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3804                                 first.maybeThinking = TRUE;
3805                             } else {
3806                                 if (first.usePlayother) {
3807                                   if (first.sendTime) {
3808                                     SendTimeRemaining(&first, FALSE);
3809                                   }
3810                                   SendToProgram("playother\n", &first);
3811                                   firstMove = FALSE;
3812                                 } else {
3813                                   firstMove = TRUE;
3814                                 }
3815                             }
3816                         }
3817                     }
3818 #endif
3819                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3820                         /* Moves came from oldmoves or moves command
3821                            while we weren't doing anything else.
3822                            */
3823                         currentMove = forwardMostMove;
3824                         ClearHighlights();/*!!could figure this out*/
3825                         flipView = appData.flipView;
3826                         DrawPosition(TRUE, boards[currentMove]);
3827                         DisplayBothClocks();
3828                         snprintf(str, MSG_SIZ, "%s %s %s",
3829                                 gameInfo.white, _("vs."),  gameInfo.black);
3830                         DisplayTitle(str);
3831                         gameMode = IcsIdle;
3832                     } else {
3833                         /* Moves were history of an active game */
3834                         if (gameInfo.resultDetails != NULL) {
3835                             free(gameInfo.resultDetails);
3836                             gameInfo.resultDetails = NULL;
3837                         }
3838                     }
3839                     HistorySet(parseList, backwardMostMove,
3840                                forwardMostMove, currentMove-1);
3841                     DisplayMove(currentMove - 1);
3842                     if (started == STARTED_MOVES) next_out = i;
3843                     started = STARTED_NONE;
3844                     ics_getting_history = H_FALSE;
3845                     break;
3846
3847                   case STARTED_OBSERVE:
3848                     started = STARTED_NONE;
3849                     SendToICS(ics_prefix);
3850                     SendToICS("refresh\n");
3851                     break;
3852
3853                   default:
3854                     break;
3855                 }
3856                 if(bookHit) { // [HGM] book: simulate book reply
3857                     static char bookMove[MSG_SIZ]; // a bit generous?
3858
3859                     programStats.nodes = programStats.depth = programStats.time =
3860                     programStats.score = programStats.got_only_move = 0;
3861                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3862
3863                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3864                     strcat(bookMove, bookHit);
3865                     HandleMachineMove(bookMove, &first);
3866                 }
3867                 continue;
3868             }
3869
3870             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3871                  started == STARTED_HOLDINGS ||
3872                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3873                 /* Accumulate characters in move list or board */
3874                 parse[parse_pos++] = buf[i];
3875             }
3876
3877             /* Start of game messages.  Mostly we detect start of game
3878                when the first board image arrives.  On some versions
3879                of the ICS, though, we need to do a "refresh" after starting
3880                to observe in order to get the current board right away. */
3881             if (looking_at(buf, &i, "Adding game * to observation list")) {
3882                 started = STARTED_OBSERVE;
3883                 continue;
3884             }
3885
3886             /* Handle auto-observe */
3887             if (appData.autoObserve &&
3888                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3889                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3890                 char *player;
3891                 /* Choose the player that was highlighted, if any. */
3892                 if (star_match[0][0] == '\033' ||
3893                     star_match[1][0] != '\033') {
3894                     player = star_match[0];
3895                 } else {
3896                     player = star_match[2];
3897                 }
3898                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3899                         ics_prefix, StripHighlightAndTitle(player));
3900                 SendToICS(str);
3901
3902                 /* Save ratings from notify string */
3903                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3904                 player1Rating = string_to_rating(star_match[1]);
3905                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3906                 player2Rating = string_to_rating(star_match[3]);
3907
3908                 if (appData.debugMode)
3909                   fprintf(debugFP,
3910                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3911                           player1Name, player1Rating,
3912                           player2Name, player2Rating);
3913
3914                 continue;
3915             }
3916
3917             /* Deal with automatic examine mode after a game,
3918                and with IcsObserving -> IcsExamining transition */
3919             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3920                 looking_at(buf, &i, "has made you an examiner of game *")) {
3921
3922                 int gamenum = atoi(star_match[0]);
3923                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3924                     gamenum == ics_gamenum) {
3925                     /* We were already playing or observing this game;
3926                        no need to refetch history */
3927                     gameMode = IcsExamining;
3928                     if (pausing) {
3929                         pauseExamForwardMostMove = forwardMostMove;
3930                     } else if (currentMove < forwardMostMove) {
3931                         ForwardInner(forwardMostMove);
3932                     }
3933                 } else {
3934                     /* I don't think this case really can happen */
3935                     SendToICS(ics_prefix);
3936                     SendToICS("refresh\n");
3937                 }
3938                 continue;
3939             }
3940
3941             /* Error messages */
3942 //          if (ics_user_moved) {
3943             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3944                 if (looking_at(buf, &i, "Illegal move") ||
3945                     looking_at(buf, &i, "Not a legal move") ||
3946                     looking_at(buf, &i, "Your king is in check") ||
3947                     looking_at(buf, &i, "It isn't your turn") ||
3948                     looking_at(buf, &i, "It is not your move")) {
3949                     /* Illegal move */
3950                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3951                         currentMove = forwardMostMove-1;
3952                         DisplayMove(currentMove - 1); /* before DMError */
3953                         DrawPosition(FALSE, boards[currentMove]);
3954                         SwitchClocks(forwardMostMove-1); // [HGM] race
3955                         DisplayBothClocks();
3956                     }
3957                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3958                     ics_user_moved = 0;
3959                     continue;
3960                 }
3961             }
3962
3963             if (looking_at(buf, &i, "still have time") ||
3964                 looking_at(buf, &i, "not out of time") ||
3965                 looking_at(buf, &i, "either player is out of time") ||
3966                 looking_at(buf, &i, "has timeseal; checking")) {
3967                 /* We must have called his flag a little too soon */
3968                 whiteFlag = blackFlag = FALSE;
3969                 continue;
3970             }
3971
3972             if (looking_at(buf, &i, "added * seconds to") ||
3973                 looking_at(buf, &i, "seconds were added to")) {
3974                 /* Update the clocks */
3975                 SendToICS(ics_prefix);
3976                 SendToICS("refresh\n");
3977                 continue;
3978             }
3979
3980             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3981                 ics_clock_paused = TRUE;
3982                 StopClocks();
3983                 continue;
3984             }
3985
3986             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3987                 ics_clock_paused = FALSE;
3988                 StartClocks();
3989                 continue;
3990             }
3991
3992             /* Grab player ratings from the Creating: message.
3993                Note we have to check for the special case when
3994                the ICS inserts things like [white] or [black]. */
3995             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3996                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3997                 /* star_matches:
3998                    0    player 1 name (not necessarily white)
3999                    1    player 1 rating
4000                    2    empty, white, or black (IGNORED)
4001                    3    player 2 name (not necessarily black)
4002                    4    player 2 rating
4003
4004                    The names/ratings are sorted out when the game
4005                    actually starts (below).
4006                 */
4007                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4008                 player1Rating = string_to_rating(star_match[1]);
4009                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4010                 player2Rating = string_to_rating(star_match[4]);
4011
4012                 if (appData.debugMode)
4013                   fprintf(debugFP,
4014                           "Ratings from 'Creating:' %s %d, %s %d\n",
4015                           player1Name, player1Rating,
4016                           player2Name, player2Rating);
4017
4018                 continue;
4019             }
4020
4021             /* Improved generic start/end-of-game messages */
4022             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4023                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4024                 /* If tkind == 0: */
4025                 /* star_match[0] is the game number */
4026                 /*           [1] is the white player's name */
4027                 /*           [2] is the black player's name */
4028                 /* For end-of-game: */
4029                 /*           [3] is the reason for the game end */
4030                 /*           [4] is a PGN end game-token, preceded by " " */
4031                 /* For start-of-game: */
4032                 /*           [3] begins with "Creating" or "Continuing" */
4033                 /*           [4] is " *" or empty (don't care). */
4034                 int gamenum = atoi(star_match[0]);
4035                 char *whitename, *blackname, *why, *endtoken;
4036                 ChessMove endtype = EndOfFile;
4037
4038                 if (tkind == 0) {
4039                   whitename = star_match[1];
4040                   blackname = star_match[2];
4041                   why = star_match[3];
4042                   endtoken = star_match[4];
4043                 } else {
4044                   whitename = star_match[1];
4045                   blackname = star_match[3];
4046                   why = star_match[5];
4047                   endtoken = star_match[6];
4048                 }
4049
4050                 /* Game start messages */
4051                 if (strncmp(why, "Creating ", 9) == 0 ||
4052                     strncmp(why, "Continuing ", 11) == 0) {
4053                     gs_gamenum = gamenum;
4054                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4055                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4056                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4057 #if ZIPPY
4058                     if (appData.zippyPlay) {
4059                         ZippyGameStart(whitename, blackname);
4060                     }
4061 #endif /*ZIPPY*/
4062                     partnerBoardValid = FALSE; // [HGM] bughouse
4063                     continue;
4064                 }
4065
4066                 /* Game end messages */
4067                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4068                     ics_gamenum != gamenum) {
4069                     continue;
4070                 }
4071                 while (endtoken[0] == ' ') endtoken++;
4072                 switch (endtoken[0]) {
4073                   case '*':
4074                   default:
4075                     endtype = GameUnfinished;
4076                     break;
4077                   case '0':
4078                     endtype = BlackWins;
4079                     break;
4080                   case '1':
4081                     if (endtoken[1] == '/')
4082                       endtype = GameIsDrawn;
4083                     else
4084                       endtype = WhiteWins;
4085                     break;
4086                 }
4087                 GameEnds(endtype, why, GE_ICS);
4088 #if ZIPPY
4089                 if (appData.zippyPlay && first.initDone) {
4090                     ZippyGameEnd(endtype, why);
4091                     if (first.pr == NoProc) {
4092                       /* Start the next process early so that we'll
4093                          be ready for the next challenge */
4094                       StartChessProgram(&first);
4095                     }
4096                     /* Send "new" early, in case this command takes
4097                        a long time to finish, so that we'll be ready
4098                        for the next challenge. */
4099                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4100                     Reset(TRUE, TRUE);
4101                 }
4102 #endif /*ZIPPY*/
4103                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4104                 continue;
4105             }
4106
4107             if (looking_at(buf, &i, "Removing game * from observation") ||
4108                 looking_at(buf, &i, "no longer observing game *") ||
4109                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4110                 if (gameMode == IcsObserving &&
4111                     atoi(star_match[0]) == ics_gamenum)
4112                   {
4113                       /* icsEngineAnalyze */
4114                       if (appData.icsEngineAnalyze) {
4115                             ExitAnalyzeMode();
4116                             ModeHighlight();
4117                       }
4118                       StopClocks();
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             if (looking_at(buf, &i, "no longer examining game *")) {
4127                 if (gameMode == IcsExamining &&
4128                     atoi(star_match[0]) == ics_gamenum)
4129                   {
4130                       gameMode = IcsIdle;
4131                       ics_gamenum = -1;
4132                       ics_user_moved = FALSE;
4133                   }
4134                 continue;
4135             }
4136
4137             /* Advance leftover_start past any newlines we find,
4138                so only partial lines can get reparsed */
4139             if (looking_at(buf, &i, "\n")) {
4140                 prevColor = curColor;
4141                 if (curColor != ColorNormal) {
4142                     if (oldi > next_out) {
4143                         SendToPlayer(&buf[next_out], oldi - next_out);
4144                         next_out = oldi;
4145                     }
4146                     Colorize(ColorNormal, FALSE);
4147                     curColor = ColorNormal;
4148                 }
4149                 if (started == STARTED_BOARD) {
4150                     started = STARTED_NONE;
4151                     parse[parse_pos] = NULLCHAR;
4152                     ParseBoard12(parse);
4153                     ics_user_moved = 0;
4154
4155                     /* Send premove here */
4156                     if (appData.premove) {
4157                       char str[MSG_SIZ];
4158                       if (currentMove == 0 &&
4159                           gameMode == IcsPlayingWhite &&
4160                           appData.premoveWhite) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (currentMove == 1 &&
4166                                  gameMode == IcsPlayingBlack &&
4167                                  appData.premoveBlack) {
4168                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                         SendToICS(str);
4172                       } else if (gotPremove) {
4173                         int oldFMM = forwardMostMove;
4174                         gotPremove = 0;
4175                         ClearPremoveHighlights();
4176                         if (appData.debugMode)
4177                           fprintf(debugFP, "Sending premove:\n");
4178                           UserMoveEvent(premoveFromX, premoveFromY,
4179                                         premoveToX, premoveToY,
4180                                         premovePromoChar);
4181                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4182                           if(moveList[oldFMM-1][1] != '@')
4183                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4184                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4185                           else // (drop)
4186                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                         }
4188                       }
4189                     }
4190
4191                     /* Usually suppress following prompt */
4192                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4193                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4194                         if (looking_at(buf, &i, "*% ")) {
4195                             savingComment = FALSE;
4196                             suppressKibitz = 0;
4197                         }
4198                     }
4199                     next_out = i;
4200                 } else if (started == STARTED_HOLDINGS) {
4201                     int gamenum;
4202                     char new_piece[MSG_SIZ];
4203                     started = STARTED_NONE;
4204                     parse[parse_pos] = NULLCHAR;
4205                     if (appData.debugMode)
4206                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4207                                                         parse, currentMove);
4208                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4209                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4210                         if (gameInfo.variant == VariantNormal) {
4211                           /* [HGM] We seem to switch variant during a game!
4212                            * Presumably no holdings were displayed, so we have
4213                            * to move the position two files to the right to
4214                            * create room for them!
4215                            */
4216                           VariantClass newVariant;
4217                           switch(gameInfo.boardWidth) { // base guess on board width
4218                                 case 9:  newVariant = VariantShogi; break;
4219                                 case 10: newVariant = VariantGreat; break;
4220                                 default: newVariant = VariantCrazyhouse; break;
4221                           }
4222                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4223                           /* Get a move list just to see the header, which
4224                              will tell us whether this is really bug or zh */
4225                           if (ics_getting_history == H_FALSE) {
4226                             ics_getting_history = H_REQUESTED;
4227                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4228                             SendToICS(str);
4229                           }
4230                         }
4231                         new_piece[0] = NULLCHAR;
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to board holdings area */
4238                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4239                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4240                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4241 #if ZIPPY
4242                         if (appData.zippyPlay && first.initDone) {
4243                             ZippyHoldings(white_holding, black_holding,
4244                                           new_piece);
4245                         }
4246 #endif /*ZIPPY*/
4247                         if (tinyLayout || smallLayout) {
4248                             char wh[16], bh[16];
4249                             PackHolding(wh, white_holding);
4250                             PackHolding(bh, black_holding);
4251                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4252                                     gameInfo.white, gameInfo.black);
4253                         } else {
4254                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4255                                     gameInfo.white, white_holding, _("vs."),
4256                                     gameInfo.black, black_holding);
4257                         }
4258                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4259                         DrawPosition(FALSE, boards[currentMove]);
4260                         DisplayTitle(str);
4261                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4262                         sscanf(parse, "game %d white [%s black [%s <- %s",
4263                                &gamenum, white_holding, black_holding,
4264                                new_piece);
4265                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4266                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4267                         /* [HGM] copy holdings to partner-board holdings area */
4268                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4269                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4270                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4271                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4273                       }
4274                     }
4275                     /* Suppress following prompt */
4276                     if (looking_at(buf, &i, "*% ")) {
4277                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4278                         savingComment = FALSE;
4279                         suppressKibitz = 0;
4280                     }
4281                     next_out = i;
4282                 }
4283                 continue;
4284             }
4285
4286             i++;                /* skip unparsed character and loop back */
4287         }
4288
4289         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4290 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4291 //          SendToPlayer(&buf[next_out], i - next_out);
4292             started != STARTED_HOLDINGS && leftover_start > next_out) {
4293             SendToPlayer(&buf[next_out], leftover_start - next_out);
4294             next_out = i;
4295         }
4296
4297         leftover_len = buf_len - leftover_start;
4298         /* if buffer ends with something we couldn't parse,
4299            reparse it after appending the next read */
4300
4301     } else if (count == 0) {
4302         RemoveInputSource(isr);
4303         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4304     } else {
4305         DisplayFatalError(_("Error reading from ICS"), error, 1);
4306     }
4307 }
4308
4309
4310 /* Board style 12 looks like this:
4311
4312    <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
4313
4314  * The "<12> " is stripped before it gets to this routine.  The two
4315  * trailing 0's (flip state and clock ticking) are later addition, and
4316  * some chess servers may not have them, or may have only the first.
4317  * Additional trailing fields may be added in the future.
4318  */
4319
4320 #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"
4321
4322 #define RELATION_OBSERVING_PLAYED    0
4323 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4324 #define RELATION_PLAYING_MYMOVE      1
4325 #define RELATION_PLAYING_NOTMYMOVE  -1
4326 #define RELATION_EXAMINING           2
4327 #define RELATION_ISOLATED_BOARD     -3
4328 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4329
4330 void
4331 ParseBoard12 (char *string)
4332 {
4333 #if ZIPPY
4334     int i, takeback;
4335     char *bookHit = NULL; // [HGM] book
4336 #endif
4337     GameMode newGameMode;
4338     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4339     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4340     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4341     char to_play, board_chars[200];
4342     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4343     char black[32], white[32];
4344     Board board;
4345     int prevMove = currentMove;
4346     int ticking = 2;
4347     ChessMove moveType;
4348     int fromX, fromY, toX, toY;
4349     char promoChar;
4350     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4351     Boolean weird = FALSE, reqFlag = FALSE;
4352
4353     fromX = fromY = toX = toY = -1;
4354
4355     newGame = FALSE;
4356
4357     if (appData.debugMode)
4358       fprintf(debugFP, "Parsing board: %s\n", string);
4359
4360     move_str[0] = NULLCHAR;
4361     elapsed_time[0] = NULLCHAR;
4362     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4363         int  i = 0, j;
4364         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4365             if(string[i] == ' ') { ranks++; files = 0; }
4366             else files++;
4367             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4368             i++;
4369         }
4370         for(j = 0; j <i; j++) board_chars[j] = string[j];
4371         board_chars[i] = '\0';
4372         string += i + 1;
4373     }
4374     n = sscanf(string, PATTERN, &to_play, &double_push,
4375                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4376                &gamenum, white, black, &relation, &basetime, &increment,
4377                &white_stren, &black_stren, &white_time, &black_time,
4378                &moveNum, str, elapsed_time, move_str, &ics_flip,
4379                &ticking);
4380
4381     if (n < 21) {
4382         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4383         DisplayError(str, 0);
4384         return;
4385     }
4386
4387     /* Convert the move number to internal form */
4388     moveNum = (moveNum - 1) * 2;
4389     if (to_play == 'B') moveNum++;
4390     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4391       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4392                         0, 1);
4393       return;
4394     }
4395
4396     switch (relation) {
4397       case RELATION_OBSERVING_PLAYED:
4398       case RELATION_OBSERVING_STATIC:
4399         if (gamenum == -1) {
4400             /* Old ICC buglet */
4401             relation = RELATION_OBSERVING_STATIC;
4402         }
4403         newGameMode = IcsObserving;
4404         break;
4405       case RELATION_PLAYING_MYMOVE:
4406       case RELATION_PLAYING_NOTMYMOVE:
4407         newGameMode =
4408           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4409             IcsPlayingWhite : IcsPlayingBlack;
4410         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4411         break;
4412       case RELATION_EXAMINING:
4413         newGameMode = IcsExamining;
4414         break;
4415       case RELATION_ISOLATED_BOARD:
4416       default:
4417         /* Just display this board.  If user was doing something else,
4418            we will forget about it until the next board comes. */
4419         newGameMode = IcsIdle;
4420         break;
4421       case RELATION_STARTING_POSITION:
4422         newGameMode = gameMode;
4423         break;
4424     }
4425
4426     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4427         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4428          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4429       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4430       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4431       static int lastBgGame = -1;
4432       char *toSqr;
4433       for (k = 0; k < ranks; k++) {
4434         for (j = 0; j < files; j++)
4435           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436         if(gameInfo.holdingsWidth > 1) {
4437              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439         }
4440       }
4441       CopyBoard(partnerBoard, board);
4442       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4443         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4444         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4445       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4446       if(toSqr = strchr(str, '-')) {
4447         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4448         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4449       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4450       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4451       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4452       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4453       if(twoBoards) {
4454           DisplayWhiteClock(white_time*fac, to_play == 'W');
4455           DisplayBlackClock(black_time*fac, to_play != 'W');
4456           activePartner = to_play;
4457           if(gamenum != lastBgGame) {
4458               char buf[MSG_SIZ];
4459               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4460               DisplayTitle(buf);
4461           }
4462           lastBgGame = gamenum;
4463           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4464                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4465       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4466                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4467       if(!twoBoards) DisplayMessage(partnerStatus, "");
4468         partnerBoardValid = TRUE;
4469       return;
4470     }
4471
4472     if(appData.dualBoard && appData.bgObserve) {
4473         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4474             SendToICS(ics_prefix), SendToICS("pobserve\n");
4475         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4476             char buf[MSG_SIZ];
4477             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4478             SendToICS(buf);
4479         }
4480     }
4481
4482     /* Modify behavior for initial board display on move listing
4483        of wild games.
4484        */
4485     switch (ics_getting_history) {
4486       case H_FALSE:
4487       case H_REQUESTED:
4488         break;
4489       case H_GOT_REQ_HEADER:
4490       case H_GOT_UNREQ_HEADER:
4491         /* This is the initial position of the current game */
4492         gamenum = ics_gamenum;
4493         moveNum = 0;            /* old ICS bug workaround */
4494         if (to_play == 'B') {
4495           startedFromSetupPosition = TRUE;
4496           blackPlaysFirst = TRUE;
4497           moveNum = 1;
4498           if (forwardMostMove == 0) forwardMostMove = 1;
4499           if (backwardMostMove == 0) backwardMostMove = 1;
4500           if (currentMove == 0) currentMove = 1;
4501         }
4502         newGameMode = gameMode;
4503         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4504         break;
4505       case H_GOT_UNWANTED_HEADER:
4506         /* This is an initial board that we don't want */
4507         return;
4508       case H_GETTING_MOVES:
4509         /* Should not happen */
4510         DisplayError(_("Error gathering move list: extra board"), 0);
4511         ics_getting_history = H_FALSE;
4512         return;
4513     }
4514
4515    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4516                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4517                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4518      /* [HGM] We seem to have switched variant unexpectedly
4519       * Try to guess new variant from board size
4520       */
4521           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4522           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4523           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4524           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4525           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4526           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4527           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4528           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4529           /* Get a move list just to see the header, which
4530              will tell us whether this is really bug or zh */
4531           if (ics_getting_history == H_FALSE) {
4532             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535           }
4536     }
4537
4538     /* Take action if this is the first board of a new game, or of a
4539        different game than is currently being displayed.  */
4540     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4541         relation == RELATION_ISOLATED_BOARD) {
4542
4543         /* Forget the old game and get the history (if any) of the new one */
4544         if (gameMode != BeginningOfGame) {
4545           Reset(TRUE, TRUE);
4546         }
4547         newGame = TRUE;
4548         if (appData.autoRaiseBoard) BoardToTop();
4549         prevMove = -3;
4550         if (gamenum == -1) {
4551             newGameMode = IcsIdle;
4552         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4553                    appData.getMoveList && !reqFlag) {
4554             /* Need to get game history */
4555             ics_getting_history = H_REQUESTED;
4556             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4557             SendToICS(str);
4558         }
4559
4560         /* Initially flip the board to have black on the bottom if playing
4561            black or if the ICS flip flag is set, but let the user change
4562            it with the Flip View button. */
4563         flipView = appData.autoFlipView ?
4564           (newGameMode == IcsPlayingBlack) || ics_flip :
4565           appData.flipView;
4566
4567         /* Done with values from previous mode; copy in new ones */
4568         gameMode = newGameMode;
4569         ModeHighlight();
4570         ics_gamenum = gamenum;
4571         if (gamenum == gs_gamenum) {
4572             int klen = strlen(gs_kind);
4573             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4574             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4575             gameInfo.event = StrSave(str);
4576         } else {
4577             gameInfo.event = StrSave("ICS game");
4578         }
4579         gameInfo.site = StrSave(appData.icsHost);
4580         gameInfo.date = PGNDate();
4581         gameInfo.round = StrSave("-");
4582         gameInfo.white = StrSave(white);
4583         gameInfo.black = StrSave(black);
4584         timeControl = basetime * 60 * 1000;
4585         timeControl_2 = 0;
4586         timeIncrement = increment * 1000;
4587         movesPerSession = 0;
4588         gameInfo.timeControl = TimeControlTagValue();
4589         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4590   if (appData.debugMode) {
4591     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4592     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4593     setbuf(debugFP, NULL);
4594   }
4595
4596         gameInfo.outOfBook = NULL;
4597
4598         /* Do we have the ratings? */
4599         if (strcmp(player1Name, white) == 0 &&
4600             strcmp(player2Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player1Rating, player2Rating);
4604             gameInfo.whiteRating = player1Rating;
4605             gameInfo.blackRating = player2Rating;
4606         } else if (strcmp(player2Name, white) == 0 &&
4607                    strcmp(player1Name, black) == 0) {
4608             if (appData.debugMode)
4609               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4610                       player2Rating, player1Rating);
4611             gameInfo.whiteRating = player2Rating;
4612             gameInfo.blackRating = player1Rating;
4613         }
4614         player1Name[0] = player2Name[0] = NULLCHAR;
4615
4616         /* Silence shouts if requested */
4617         if (appData.quietPlay &&
4618             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4619             SendToICS(ics_prefix);
4620             SendToICS("set shout 0\n");
4621         }
4622     }
4623
4624     /* Deal with midgame name changes */
4625     if (!newGame) {
4626         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4627             if (gameInfo.white) free(gameInfo.white);
4628             gameInfo.white = StrSave(white);
4629         }
4630         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4631             if (gameInfo.black) free(gameInfo.black);
4632             gameInfo.black = StrSave(black);
4633         }
4634     }
4635
4636     /* Throw away game result if anything actually changes in examine mode */
4637     if (gameMode == IcsExamining && !newGame) {
4638         gameInfo.result = GameUnfinished;
4639         if (gameInfo.resultDetails != NULL) {
4640             free(gameInfo.resultDetails);
4641             gameInfo.resultDetails = NULL;
4642         }
4643     }
4644
4645     /* In pausing && IcsExamining mode, we ignore boards coming
4646        in if they are in a different variation than we are. */
4647     if (pauseExamInvalid) return;
4648     if (pausing && gameMode == IcsExamining) {
4649         if (moveNum <= pauseExamForwardMostMove) {
4650             pauseExamInvalid = TRUE;
4651             forwardMostMove = pauseExamForwardMostMove;
4652             return;
4653         }
4654     }
4655
4656   if (appData.debugMode) {
4657     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4658   }
4659     /* Parse the board */
4660     for (k = 0; k < ranks; k++) {
4661       for (j = 0; j < files; j++)
4662         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4663       if(gameInfo.holdingsWidth > 1) {
4664            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4665            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4666       }
4667     }
4668     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4669       board[5][BOARD_RGHT+1] = WhiteAngel;
4670       board[6][BOARD_RGHT+1] = WhiteMarshall;
4671       board[1][0] = BlackMarshall;
4672       board[2][0] = BlackAngel;
4673       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4674     }
4675     CopyBoard(boards[moveNum], board);
4676     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4677     if (moveNum == 0) {
4678         startedFromSetupPosition =
4679           !CompareBoards(board, initialPosition);
4680         if(startedFromSetupPosition)
4681             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4682     }
4683
4684     /* [HGM] Set castling rights. Take the outermost Rooks,
4685        to make it also work for FRC opening positions. Note that board12
4686        is really defective for later FRC positions, as it has no way to
4687        indicate which Rook can castle if they are on the same side of King.
4688        For the initial position we grant rights to the outermost Rooks,
4689        and remember thos rights, and we then copy them on positions
4690        later in an FRC game. This means WB might not recognize castlings with
4691        Rooks that have moved back to their original position as illegal,
4692        but in ICS mode that is not its job anyway.
4693     */
4694     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4695     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4696
4697         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4698             if(board[0][i] == WhiteRook) j = i;
4699         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4700         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4701             if(board[0][i] == WhiteRook) j = i;
4702         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4703         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4705         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4706         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4707             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4708         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4709
4710         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4711         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4715             if(board[BOARD_HEIGHT-1][k] == bKing)
4716                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4717         if(gameInfo.variant == VariantTwoKings) {
4718             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4719             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4720             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4721         }
4722     } else { int r;
4723         r = boards[moveNum][CASTLING][0] = initialRights[0];
4724         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4725         r = boards[moveNum][CASTLING][1] = initialRights[1];
4726         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4727         r = boards[moveNum][CASTLING][3] = initialRights[3];
4728         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4729         r = boards[moveNum][CASTLING][4] = initialRights[4];
4730         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4731         /* wildcastle kludge: always assume King has rights */
4732         r = boards[moveNum][CASTLING][2] = initialRights[2];
4733         r = boards[moveNum][CASTLING][5] = initialRights[5];
4734     }
4735     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4736     boards[moveNum][EP_STATUS] = EP_NONE;
4737     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4738     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4739     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4740
4741
4742     if (ics_getting_history == H_GOT_REQ_HEADER ||
4743         ics_getting_history == H_GOT_UNREQ_HEADER) {
4744         /* This was an initial position from a move list, not
4745            the current position */
4746         return;
4747     }
4748
4749     /* Update currentMove and known move number limits */
4750     newMove = newGame || moveNum > forwardMostMove;
4751
4752     if (newGame) {
4753         forwardMostMove = backwardMostMove = currentMove = moveNum;
4754         if (gameMode == IcsExamining && moveNum == 0) {
4755           /* Workaround for ICS limitation: we are not told the wild
4756              type when starting to examine a game.  But if we ask for
4757              the move list, the move list header will tell us */
4758             ics_getting_history = H_REQUESTED;
4759             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4760             SendToICS(str);
4761         }
4762     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4763                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4764 #if ZIPPY
4765         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4766         /* [HGM] applied this also to an engine that is silently watching        */
4767         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4768             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4769             gameInfo.variant == currentlyInitializedVariant) {
4770           takeback = forwardMostMove - moveNum;
4771           for (i = 0; i < takeback; i++) {
4772             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4773             SendToProgram("undo\n", &first);
4774           }
4775         }
4776 #endif
4777
4778         forwardMostMove = moveNum;
4779         if (!pausing || currentMove > forwardMostMove)
4780           currentMove = forwardMostMove;
4781     } else {
4782         /* New part of history that is not contiguous with old part */
4783         if (pausing && gameMode == IcsExamining) {
4784             pauseExamInvalid = TRUE;
4785             forwardMostMove = pauseExamForwardMostMove;
4786             return;
4787         }
4788         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4789 #if ZIPPY
4790             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4791                 // [HGM] when we will receive the move list we now request, it will be
4792                 // fed to the engine from the first move on. So if the engine is not
4793                 // in the initial position now, bring it there.
4794                 InitChessProgram(&first, 0);
4795             }
4796 #endif
4797             ics_getting_history = H_REQUESTED;
4798             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4799             SendToICS(str);
4800         }
4801         forwardMostMove = backwardMostMove = currentMove = moveNum;
4802     }
4803
4804     /* Update the clocks */
4805     if (strchr(elapsed_time, '.')) {
4806       /* Time is in ms */
4807       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4808       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4809     } else {
4810       /* Time is in seconds */
4811       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4812       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4813     }
4814
4815
4816 #if ZIPPY
4817     if (appData.zippyPlay && newGame &&
4818         gameMode != IcsObserving && gameMode != IcsIdle &&
4819         gameMode != IcsExamining)
4820       ZippyFirstBoard(moveNum, basetime, increment);
4821 #endif
4822
4823     /* Put the move on the move list, first converting
4824        to canonical algebraic form. */
4825     if (moveNum > 0) {
4826   if (appData.debugMode) {
4827     int f = forwardMostMove;
4828     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4829             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4830             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4831     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4832     fprintf(debugFP, "moveNum = %d\n", moveNum);
4833     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4834     setbuf(debugFP, NULL);
4835   }
4836         if (moveNum <= backwardMostMove) {
4837             /* We don't know what the board looked like before
4838                this move.  Punt. */
4839           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4840             strcat(parseList[moveNum - 1], " ");
4841             strcat(parseList[moveNum - 1], elapsed_time);
4842             moveList[moveNum - 1][0] = NULLCHAR;
4843         } else if (strcmp(move_str, "none") == 0) {
4844             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4845             /* Again, we don't know what the board looked like;
4846                this is really the start of the game. */
4847             parseList[moveNum - 1][0] = NULLCHAR;
4848             moveList[moveNum - 1][0] = NULLCHAR;
4849             backwardMostMove = moveNum;
4850             startedFromSetupPosition = TRUE;
4851             fromX = fromY = toX = toY = -1;
4852         } else {
4853           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4854           //                 So we parse the long-algebraic move string in stead of the SAN move
4855           int valid; char buf[MSG_SIZ], *prom;
4856
4857           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4858                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4859           // str looks something like "Q/a1-a2"; kill the slash
4860           if(str[1] == '/')
4861             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4862           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4863           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4864                 strcat(buf, prom); // long move lacks promo specification!
4865           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4866                 if(appData.debugMode)
4867                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4868                 safeStrCpy(move_str, buf, MSG_SIZ);
4869           }
4870           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4871                                 &fromX, &fromY, &toX, &toY, &promoChar)
4872                || ParseOneMove(buf, moveNum - 1, &moveType,
4873                                 &fromX, &fromY, &toX, &toY, &promoChar);
4874           // end of long SAN patch
4875           if (valid) {
4876             (void) CoordsToAlgebraic(boards[moveNum - 1],
4877                                      PosFlags(moveNum - 1),
4878                                      fromY, fromX, toY, toX, promoChar,
4879                                      parseList[moveNum-1]);
4880             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4881               case MT_NONE:
4882               case MT_STALEMATE:
4883               default:
4884                 break;
4885               case MT_CHECK:
4886                 if(!IS_SHOGI(gameInfo.variant))
4887                     strcat(parseList[moveNum - 1], "+");
4888                 break;
4889               case MT_CHECKMATE:
4890               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4891                 strcat(parseList[moveNum - 1], "#");
4892                 break;
4893             }
4894             strcat(parseList[moveNum - 1], " ");
4895             strcat(parseList[moveNum - 1], elapsed_time);
4896             /* currentMoveString is set as a side-effect of ParseOneMove */
4897             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4898             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4899             strcat(moveList[moveNum - 1], "\n");
4900
4901             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4902                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4903               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4904                 ChessSquare old, new = boards[moveNum][k][j];
4905                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4906                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4907                   if(old == new) continue;
4908                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4909                   else if(new == WhiteWazir || new == BlackWazir) {
4910                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4911                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4912                       else boards[moveNum][k][j] = old; // preserve type of Gold
4913                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4914                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4915               }
4916           } else {
4917             /* Move from ICS was illegal!?  Punt. */
4918             if (appData.debugMode) {
4919               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4920               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4921             }
4922             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4923             strcat(parseList[moveNum - 1], " ");
4924             strcat(parseList[moveNum - 1], elapsed_time);
4925             moveList[moveNum - 1][0] = NULLCHAR;
4926             fromX = fromY = toX = toY = -1;
4927           }
4928         }
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4931     setbuf(debugFP, NULL);
4932   }
4933
4934 #if ZIPPY
4935         /* Send move to chess program (BEFORE animating it). */
4936         if (appData.zippyPlay && !newGame && newMove &&
4937            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4938
4939             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4940                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4941                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4942                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4943                             move_str);
4944                     DisplayError(str, 0);
4945                 } else {
4946                     if (first.sendTime) {
4947                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4948                     }
4949                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4950                     if (firstMove && !bookHit) {
4951                         firstMove = FALSE;
4952                         if (first.useColors) {
4953                           SendToProgram(gameMode == IcsPlayingWhite ?
4954                                         "white\ngo\n" :
4955                                         "black\ngo\n", &first);
4956                         } else {
4957                           SendToProgram("go\n", &first);
4958                         }
4959                         first.maybeThinking = TRUE;
4960                     }
4961                 }
4962             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4963               if (moveList[moveNum - 1][0] == NULLCHAR) {
4964                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4965                 DisplayError(str, 0);
4966               } else {
4967                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4968                 SendMoveToProgram(moveNum - 1, &first);
4969               }
4970             }
4971         }
4972 #endif
4973     }
4974
4975     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4976         /* If move comes from a remote source, animate it.  If it
4977            isn't remote, it will have already been animated. */
4978         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4979             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4980         }
4981         if (!pausing && appData.highlightLastMove) {
4982             SetHighlights(fromX, fromY, toX, toY);
4983         }
4984     }
4985
4986     /* Start the clocks */
4987     whiteFlag = blackFlag = FALSE;
4988     appData.clockMode = !(basetime == 0 && increment == 0);
4989     if (ticking == 0) {
4990       ics_clock_paused = TRUE;
4991       StopClocks();
4992     } else if (ticking == 1) {
4993       ics_clock_paused = FALSE;
4994     }
4995     if (gameMode == IcsIdle ||
4996         relation == RELATION_OBSERVING_STATIC ||
4997         relation == RELATION_EXAMINING ||
4998         ics_clock_paused)
4999       DisplayBothClocks();
5000     else
5001       StartClocks();
5002
5003     /* Display opponents and material strengths */
5004     if (gameInfo.variant != VariantBughouse &&
5005         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5006         if (tinyLayout || smallLayout) {
5007             if(gameInfo.variant == VariantNormal)
5008               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5009                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5010                     basetime, increment);
5011             else
5012               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5013                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5014                     basetime, increment, (int) gameInfo.variant);
5015         } else {
5016             if(gameInfo.variant == VariantNormal)
5017               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5018                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5019                     basetime, increment);
5020             else
5021               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5022                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023                     basetime, increment, VariantName(gameInfo.variant));
5024         }
5025         DisplayTitle(str);
5026   if (appData.debugMode) {
5027     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5028   }
5029     }
5030
5031
5032     /* Display the board */
5033     if (!pausing && !appData.noGUI) {
5034
5035       if (appData.premove)
5036           if (!gotPremove ||
5037              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5038              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5039               ClearPremoveHighlights();
5040
5041       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5042         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5043       DrawPosition(j, boards[currentMove]);
5044
5045       DisplayMove(moveNum - 1);
5046       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5047             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5048               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5049         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5050       }
5051     }
5052
5053     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5054 #if ZIPPY
5055     if(bookHit) { // [HGM] book: simulate book reply
5056         static char bookMove[MSG_SIZ]; // a bit generous?
5057
5058         programStats.nodes = programStats.depth = programStats.time =
5059         programStats.score = programStats.got_only_move = 0;
5060         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5061
5062         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5063         strcat(bookMove, bookHit);
5064         HandleMachineMove(bookMove, &first);
5065     }
5066 #endif
5067 }
5068
5069 void
5070 GetMoveListEvent ()
5071 {
5072     char buf[MSG_SIZ];
5073     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5074         ics_getting_history = H_REQUESTED;
5075         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5076         SendToICS(buf);
5077     }
5078 }
5079
5080 void
5081 SendToBoth (char *msg)
5082 {   // to make it easy to keep two engines in step in dual analysis
5083     SendToProgram(msg, &first);
5084     if(second.analyzing) SendToProgram(msg, &second);
5085 }
5086
5087 void
5088 AnalysisPeriodicEvent (int force)
5089 {
5090     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5091          && !force) || !appData.periodicUpdates)
5092       return;
5093
5094     /* Send . command to Crafty to collect stats */
5095     SendToBoth(".\n");
5096
5097     /* Don't send another until we get a response (this makes
5098        us stop sending to old Crafty's which don't understand
5099        the "." command (sending illegal cmds resets node count & time,
5100        which looks bad)) */
5101     programStats.ok_to_send = 0;
5102 }
5103
5104 void
5105 ics_update_width (int new_width)
5106 {
5107         ics_printf("set width %d\n", new_width);
5108 }
5109
5110 void
5111 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5112 {
5113     char buf[MSG_SIZ];
5114
5115     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5116         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5117             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5118             SendToProgram(buf, cps);
5119             return;
5120         }
5121         // null move in variant where engine does not understand it (for analysis purposes)
5122         SendBoard(cps, moveNum + 1); // send position after move in stead.
5123         return;
5124     }
5125     if (cps->useUsermove) {
5126       SendToProgram("usermove ", cps);
5127     }
5128     if (cps->useSAN) {
5129       char *space;
5130       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5131         int len = space - parseList[moveNum];
5132         memcpy(buf, parseList[moveNum], len);
5133         buf[len++] = '\n';
5134         buf[len] = NULLCHAR;
5135       } else {
5136         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5137       }
5138       SendToProgram(buf, cps);
5139     } else {
5140       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5141         AlphaRank(moveList[moveNum], 4);
5142         SendToProgram(moveList[moveNum], cps);
5143         AlphaRank(moveList[moveNum], 4); // and back
5144       } else
5145       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5146        * the engine. It would be nice to have a better way to identify castle
5147        * moves here. */
5148       if(appData.fischerCastling && cps->useOOCastle) {
5149         int fromX = moveList[moveNum][0] - AAA;
5150         int fromY = moveList[moveNum][1] - ONE;
5151         int toX = moveList[moveNum][2] - AAA;
5152         int toY = moveList[moveNum][3] - ONE;
5153         if((boards[moveNum][fromY][fromX] == WhiteKing
5154             && boards[moveNum][toY][toX] == WhiteRook)
5155            || (boards[moveNum][fromY][fromX] == BlackKing
5156                && boards[moveNum][toY][toX] == BlackRook)) {
5157           if(toX > fromX) SendToProgram("O-O\n", cps);
5158           else SendToProgram("O-O-O\n", cps);
5159         }
5160         else SendToProgram(moveList[moveNum], cps);
5161       } else
5162       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5163         char *m = moveList[moveNum];
5164         static char c[2];
5165         *c = m[7]; // promoChar
5166         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5168                                                m[2], m[3] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5171         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5172           *c = m[9];
5173           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5174                                                m[7], m[8] - '0',
5175                                                m[7], m[8] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[2], m[3] - '0', c);
5179         } else
5180           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184           SendToProgram(buf, cps);
5185       } else
5186       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5187         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5188           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5189           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5190                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5191         } else
5192           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5193                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5194         SendToProgram(buf, cps);
5195       }
5196       else SendToProgram(moveList[moveNum], cps);
5197       /* End of additions by Tord */
5198     }
5199
5200     /* [HGM] setting up the opening has brought engine in force mode! */
5201     /*       Send 'go' if we are in a mode where machine should play. */
5202     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5203         (gameMode == TwoMachinesPlay   ||
5204 #if ZIPPY
5205          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5206 #endif
5207          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5208         SendToProgram("go\n", cps);
5209   if (appData.debugMode) {
5210     fprintf(debugFP, "(extra)\n");
5211   }
5212     }
5213     setboardSpoiledMachineBlack = 0;
5214 }
5215
5216 void
5217 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5218 {
5219     char user_move[MSG_SIZ];
5220     char suffix[4];
5221
5222     if(gameInfo.variant == VariantSChess && promoChar) {
5223         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5224         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5225     } else suffix[0] = NULLCHAR;
5226
5227     switch (moveType) {
5228       default:
5229         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5230                 (int)moveType, fromX, fromY, toX, toY);
5231         DisplayError(user_move + strlen("say "), 0);
5232         break;
5233       case WhiteKingSideCastle:
5234       case BlackKingSideCastle:
5235       case WhiteQueenSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* PUSH Fabien */
5238       case WhiteHSideCastleFR:
5239       case BlackHSideCastleFR:
5240       /* POP Fabien */
5241         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5242         break;
5243       case WhiteQueenSideCastle:
5244       case BlackQueenSideCastle:
5245       case WhiteKingSideCastleWild:
5246       case BlackKingSideCastleWild:
5247       /* PUSH Fabien */
5248       case WhiteASideCastleFR:
5249       case BlackASideCastleFR:
5250       /* POP Fabien */
5251         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5252         break;
5253       case WhiteNonPromotion:
5254       case BlackNonPromotion:
5255         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5256         break;
5257       case WhitePromotion:
5258       case BlackPromotion:
5259         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5260            gameInfo.variant == VariantMakruk)
5261           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5262                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5263                 PieceToChar(WhiteFerz));
5264         else if(gameInfo.variant == VariantGreat)
5265           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5266                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267                 PieceToChar(WhiteMan));
5268         else
5269           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5271                 promoChar);
5272         break;
5273       case WhiteDrop:
5274       case BlackDrop:
5275       drop:
5276         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5277                  ToUpper(PieceToChar((ChessSquare) fromX)),
5278                  AAA + toX, ONE + toY);
5279         break;
5280       case IllegalMove:  /* could be a variant we don't quite understand */
5281         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5282       case NormalMove:
5283       case WhiteCapturesEnPassant:
5284       case BlackCapturesEnPassant:
5285         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5286                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5287         break;
5288     }
5289     SendToICS(user_move);
5290     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5291         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5292 }
5293
5294 void
5295 UploadGameEvent ()
5296 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5297     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5298     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5299     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5300       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5301       return;
5302     }
5303     if(gameMode != IcsExamining) { // is this ever not the case?
5304         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5305
5306         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5307           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5308         } else { // on FICS we must first go to general examine mode
5309           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5310         }
5311         if(gameInfo.variant != VariantNormal) {
5312             // try figure out wild number, as xboard names are not always valid on ICS
5313             for(i=1; i<=36; i++) {
5314               snprintf(buf, MSG_SIZ, "wild/%d", i);
5315                 if(StringToVariant(buf) == gameInfo.variant) break;
5316             }
5317             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5318             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5319             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5320         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5321         SendToICS(ics_prefix);
5322         SendToICS(buf);
5323         if(startedFromSetupPosition || backwardMostMove != 0) {
5324           fen = PositionToFEN(backwardMostMove, NULL, 1);
5325           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5326             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5327             SendToICS(buf);
5328           } else { // FICS: everything has to set by separate bsetup commands
5329             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5330             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5331             SendToICS(buf);
5332             if(!WhiteOnMove(backwardMostMove)) {
5333                 SendToICS("bsetup tomove black\n");
5334             }
5335             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5336             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5337             SendToICS(buf);
5338             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5339             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5340             SendToICS(buf);
5341             i = boards[backwardMostMove][EP_STATUS];
5342             if(i >= 0) { // set e.p.
5343               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5344                 SendToICS(buf);
5345             }
5346             bsetup++;
5347           }
5348         }
5349       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5350             SendToICS("bsetup done\n"); // switch to normal examining.
5351     }
5352     for(i = backwardMostMove; i<last; i++) {
5353         char buf[20];
5354         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5355         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5356             int len = strlen(moveList[i]);
5357             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5358             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5359         }
5360         SendToICS(buf);
5361     }
5362     SendToICS(ics_prefix);
5363     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5364 }
5365
5366 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5367 int legNr = 1;
5368
5369 void
5370 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5371 {
5372     if (rf == DROP_RANK) {
5373       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5374       sprintf(move, "%c@%c%c\n",
5375                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5376     } else {
5377         if (promoChar == 'x' || promoChar == NULLCHAR) {
5378           sprintf(move, "%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5383           }
5384         } else {
5385             sprintf(move, "%c%c%c%c%c\n",
5386                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5387           if(killX >= 0 && killY >= 0) {
5388             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5389             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5390           }
5391         }
5392     }
5393 }
5394
5395 void
5396 ProcessICSInitScript (FILE *f)
5397 {
5398     char buf[MSG_SIZ];
5399
5400     while (fgets(buf, MSG_SIZ, f)) {
5401         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5402     }
5403
5404     fclose(f);
5405 }
5406
5407
5408 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5409 int dragging;
5410 static ClickType lastClickType;
5411
5412 int
5413 PieceInString (char *s, ChessSquare piece)
5414 {
5415   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5416   while((p = strchr(s, ID))) {
5417     if(!suffix || p[1] == suffix) return TRUE;
5418     s = p;
5419   }
5420   return FALSE;
5421 }
5422
5423 int
5424 Partner (ChessSquare *p)
5425 { // change piece into promotion partner if one shogi-promotes to the other
5426   ChessSquare partner = promoPartner[*p];
5427   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5428   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5429   *p = partner;
5430   return 1;
5431 }
5432
5433 void
5434 Sweep (int step)
5435 {
5436     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5437     static int toggleFlag;
5438     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5439     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5440     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5441     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5442     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5443     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5444     do {
5445         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5446         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5447         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5448         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5449         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5450         if(!step) step = -1;
5451     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5452             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5453             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5454             promoSweep == pawn ||
5455             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5456             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5457     if(toX >= 0) {
5458         int victim = boards[currentMove][toY][toX];
5459         boards[currentMove][toY][toX] = promoSweep;
5460         DrawPosition(FALSE, boards[currentMove]);
5461         boards[currentMove][toY][toX] = victim;
5462     } else
5463     ChangeDragPiece(promoSweep);
5464 }
5465
5466 int
5467 PromoScroll (int x, int y)
5468 {
5469   int step = 0;
5470
5471   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5472   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5473   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5474   if(!step) return FALSE;
5475   lastX = x; lastY = y;
5476   if((promoSweep < BlackPawn) == flipView) step = -step;
5477   if(step > 0) selectFlag = 1;
5478   if(!selectFlag) Sweep(step);
5479   return FALSE;
5480 }
5481
5482 void
5483 NextPiece (int step)
5484 {
5485     ChessSquare piece = boards[currentMove][toY][toX];
5486     do {
5487         pieceSweep -= step;
5488         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5489         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5490         if(!step) step = -1;
5491     } while(PieceToChar(pieceSweep) == '.');
5492     boards[currentMove][toY][toX] = pieceSweep;
5493     DrawPosition(FALSE, boards[currentMove]);
5494     boards[currentMove][toY][toX] = piece;
5495 }
5496 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5497 void
5498 AlphaRank (char *move, int n)
5499 {
5500 //    char *p = move, c; int x, y;
5501
5502     if (appData.debugMode) {
5503         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5504     }
5505
5506     if(move[1]=='*' &&
5507        move[2]>='0' && move[2]<='9' &&
5508        move[3]>='a' && move[3]<='x'    ) {
5509         move[1] = '@';
5510         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5511         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5512     } else
5513     if(move[0]>='0' && move[0]<='9' &&
5514        move[1]>='a' && move[1]<='x' &&
5515        move[2]>='0' && move[2]<='9' &&
5516        move[3]>='a' && move[3]<='x'    ) {
5517         /* input move, Shogi -> normal */
5518         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5519         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5520         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5522     } else
5523     if(move[1]=='@' &&
5524        move[3]>='0' && move[3]<='9' &&
5525        move[2]>='a' && move[2]<='x'    ) {
5526         move[1] = '*';
5527         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5528         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5529     } else
5530     if(
5531        move[0]>='a' && move[0]<='x' &&
5532        move[3]>='0' && move[3]<='9' &&
5533        move[2]>='a' && move[2]<='x'    ) {
5534          /* output move, normal -> Shogi */
5535         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5536         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5537         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5538         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5539         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5540     }
5541     if (appData.debugMode) {
5542         fprintf(debugFP, "   out = '%s'\n", move);
5543     }
5544 }
5545
5546 char yy_textstr[8000];
5547
5548 /* Parser for moves from gnuchess, ICS, or user typein box */
5549 Boolean
5550 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5551 {
5552     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5553
5554     switch (*moveType) {
5555       case WhitePromotion:
5556       case BlackPromotion:
5557       case WhiteNonPromotion:
5558       case BlackNonPromotion:
5559       case NormalMove:
5560       case FirstLeg:
5561       case WhiteCapturesEnPassant:
5562       case BlackCapturesEnPassant:
5563       case WhiteKingSideCastle:
5564       case WhiteQueenSideCastle:
5565       case BlackKingSideCastle:
5566       case BlackQueenSideCastle:
5567       case WhiteKingSideCastleWild:
5568       case WhiteQueenSideCastleWild:
5569       case BlackKingSideCastleWild:
5570       case BlackQueenSideCastleWild:
5571       /* Code added by Tord: */
5572       case WhiteHSideCastleFR:
5573       case WhiteASideCastleFR:
5574       case BlackHSideCastleFR:
5575       case BlackASideCastleFR:
5576       /* End of code added by Tord */
5577       case IllegalMove:         /* bug or odd chess variant */
5578         if(currentMoveString[1] == '@') { // illegal drop
5579           *fromX = WhiteOnMove(moveNum) ?
5580             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5581             (int) CharToPiece(ToLower(currentMoveString[0]));
5582           goto drop;
5583         }
5584         *fromX = currentMoveString[0] - AAA;
5585         *fromY = currentMoveString[1] - ONE;
5586         *toX = currentMoveString[2] - AAA;
5587         *toY = currentMoveString[3] - ONE;
5588         *promoChar = currentMoveString[4];
5589         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5590         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5591             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5592     if (appData.debugMode) {
5593         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5594     }
5595             *fromX = *fromY = *toX = *toY = 0;
5596             return FALSE;
5597         }
5598         if (appData.testLegality) {
5599           return (*moveType != IllegalMove);
5600         } else {
5601           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5602                          // [HGM] lion: if this is a double move we are less critical
5603                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5604         }
5605
5606       case WhiteDrop:
5607       case BlackDrop:
5608         *fromX = *moveType == WhiteDrop ?
5609           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5610           (int) CharToPiece(ToLower(currentMoveString[0]));
5611       drop:
5612         *fromY = DROP_RANK;
5613         *toX = currentMoveString[2] - AAA;
5614         *toY = currentMoveString[3] - ONE;
5615         *promoChar = NULLCHAR;
5616         return TRUE;
5617
5618       case AmbiguousMove:
5619       case ImpossibleMove:
5620       case EndOfFile:
5621       case ElapsedTime:
5622       case Comment:
5623       case PGNTag:
5624       case NAG:
5625       case WhiteWins:
5626       case BlackWins:
5627       case GameIsDrawn:
5628       default:
5629     if (appData.debugMode) {
5630         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5631     }
5632         /* bug? */
5633         *fromX = *fromY = *toX = *toY = 0;
5634         *promoChar = NULLCHAR;
5635         return FALSE;
5636     }
5637 }
5638
5639 Boolean pushed = FALSE;
5640 char *lastParseAttempt;
5641
5642 void
5643 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5644 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5645   int fromX, fromY, toX, toY; char promoChar;
5646   ChessMove moveType;
5647   Boolean valid;
5648   int nr = 0;
5649
5650   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5651   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5652     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5653     pushed = TRUE;
5654   }
5655   endPV = forwardMostMove;
5656   do {
5657     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5658     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5659     lastParseAttempt = pv;
5660     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5661     if(!valid && nr == 0 &&
5662        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5663         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5664         // Hande case where played move is different from leading PV move
5665         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5666         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5667         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5668         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5669           endPV += 2; // if position different, keep this
5670           moveList[endPV-1][0] = fromX + AAA;
5671           moveList[endPV-1][1] = fromY + ONE;
5672           moveList[endPV-1][2] = toX + AAA;
5673           moveList[endPV-1][3] = toY + ONE;
5674           parseList[endPV-1][0] = NULLCHAR;
5675           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5676         }
5677       }
5678     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5679     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5680     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5681     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5682         valid++; // allow comments in PV
5683         continue;
5684     }
5685     nr++;
5686     if(endPV+1 > framePtr) break; // no space, truncate
5687     if(!valid) break;
5688     endPV++;
5689     CopyBoard(boards[endPV], boards[endPV-1]);
5690     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5691     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5692     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5693     CoordsToAlgebraic(boards[endPV - 1],
5694                              PosFlags(endPV - 1),
5695                              fromY, fromX, toY, toX, promoChar,
5696                              parseList[endPV - 1]);
5697   } while(valid);
5698   if(atEnd == 2) return; // used hidden, for PV conversion
5699   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5700   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5701   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5702                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5703   DrawPosition(TRUE, boards[currentMove]);
5704 }
5705
5706 int
5707 MultiPV (ChessProgramState *cps, int kind)
5708 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5709         int i;
5710         for(i=0; i<cps->nrOptions; i++) {
5711             char *s = cps->option[i].name;
5712             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5713             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5714                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5715         }
5716         return -1;
5717 }
5718
5719 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5720 static int multi, pv_margin;
5721 static ChessProgramState *activeCps;
5722
5723 Boolean
5724 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5725 {
5726         int startPV, lineStart, origIndex = index;
5727         char *p, buf2[MSG_SIZ];
5728         ChessProgramState *cps = (pane ? &second : &first);
5729
5730         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5731         lastX = x; lastY = y;
5732         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5733         lineStart = startPV = index;
5734         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5735         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5736         index = startPV;
5737         do{ while(buf[index] && buf[index] != '\n') index++;
5738         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5739         buf[index] = 0;
5740         if(lineStart == 0 && gameMode == AnalyzeMode) {
5741             int n = 0;
5742             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5743             if(n == 0) { // click not on "fewer" or "more"
5744                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5745                     pv_margin = cps->option[multi].value;
5746                     activeCps = cps; // non-null signals margin adjustment
5747                 }
5748             } else if((multi = MultiPV(cps, 1)) >= 0) {
5749                 n += cps->option[multi].value; if(n < 1) n = 1;
5750                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5751                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5752                 cps->option[multi].value = n;
5753                 *start = *end = 0;
5754                 return FALSE;
5755             }
5756         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5757                 ExcludeClick(origIndex - lineStart);
5758                 return FALSE;
5759         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5760                 Collapse(origIndex - lineStart);
5761                 return FALSE;
5762         }
5763         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5764         *start = startPV; *end = index-1;
5765         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5766         return TRUE;
5767 }
5768
5769 char *
5770 PvToSAN (char *pv)
5771 {
5772         static char buf[10*MSG_SIZ];
5773         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5774         *buf = NULLCHAR;
5775         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5776         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5777         for(i = forwardMostMove; i<endPV; i++){
5778             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5779             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5780             k += strlen(buf+k);
5781         }
5782         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5783         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5784         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5785         endPV = savedEnd;
5786         return buf;
5787 }
5788
5789 Boolean
5790 LoadPV (int x, int y)
5791 { // called on right mouse click to load PV
5792   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5793   lastX = x; lastY = y;
5794   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5795   extendGame = FALSE;
5796   return TRUE;
5797 }
5798
5799 void
5800 UnLoadPV ()
5801 {
5802   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5803   if(activeCps) {
5804     if(pv_margin != activeCps->option[multi].value) {
5805       char buf[MSG_SIZ];
5806       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5807       SendToProgram(buf, activeCps);
5808       activeCps->option[multi].value = pv_margin;
5809     }
5810     activeCps = NULL;
5811     return;
5812   }
5813   if(endPV < 0) return;
5814   if(appData.autoCopyPV) CopyFENToClipboard();
5815   endPV = -1;
5816   if(extendGame && currentMove > forwardMostMove) {
5817         Boolean saveAnimate = appData.animate;
5818         if(pushed) {
5819             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5820                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5821             } else storedGames--; // abandon shelved tail of original game
5822         }
5823         pushed = FALSE;
5824         forwardMostMove = currentMove;
5825         currentMove = oldFMM;
5826         appData.animate = FALSE;
5827         ToNrEvent(forwardMostMove);
5828         appData.animate = saveAnimate;
5829   }
5830   currentMove = forwardMostMove;
5831   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5832   ClearPremoveHighlights();
5833   DrawPosition(TRUE, boards[currentMove]);
5834 }
5835
5836 void
5837 MovePV (int x, int y, int h)
5838 { // step through PV based on mouse coordinates (called on mouse move)
5839   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5840
5841   if(activeCps) { // adjusting engine's multi-pv margin
5842     if(x > lastX) pv_margin++; else
5843     if(x < lastX) pv_margin -= (pv_margin > 0);
5844     if(x != lastX) {
5845       char buf[MSG_SIZ];
5846       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5847       DisplayMessage(buf, "");
5848     }
5849     lastX = x;
5850     return;
5851   }
5852   // we must somehow check if right button is still down (might be released off board!)
5853   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5854   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5855   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5856   if(!step) return;
5857   lastX = x; lastY = y;
5858
5859   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5860   if(endPV < 0) return;
5861   if(y < margin) step = 1; else
5862   if(y > h - margin) step = -1;
5863   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5864   currentMove += step;
5865   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5866   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5867                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5868   DrawPosition(FALSE, boards[currentMove]);
5869 }
5870
5871
5872 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5873 // All positions will have equal probability, but the current method will not provide a unique
5874 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5875 #define DARK 1
5876 #define LITE 2
5877 #define ANY 3
5878
5879 int squaresLeft[4];
5880 int piecesLeft[(int)BlackPawn];
5881 int seed, nrOfShuffles;
5882
5883 void
5884 GetPositionNumber ()
5885 {       // sets global variable seed
5886         int i;
5887
5888         seed = appData.defaultFrcPosition;
5889         if(seed < 0) { // randomize based on time for negative FRC position numbers
5890                 for(i=0; i<50; i++) seed += random();
5891                 seed = random() ^ random() >> 8 ^ random() << 8;
5892                 if(seed<0) seed = -seed;
5893         }
5894 }
5895
5896 int
5897 put (Board board, int pieceType, int rank, int n, int shade)
5898 // put the piece on the (n-1)-th empty squares of the given shade
5899 {
5900         int i;
5901
5902         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5903                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5904                         board[rank][i] = (ChessSquare) pieceType;
5905                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5906                         squaresLeft[ANY]--;
5907                         piecesLeft[pieceType]--;
5908                         return i;
5909                 }
5910         }
5911         return -1;
5912 }
5913
5914
5915 void
5916 AddOnePiece (Board board, int pieceType, int rank, int shade)
5917 // calculate where the next piece goes, (any empty square), and put it there
5918 {
5919         int i;
5920
5921         i = seed % squaresLeft[shade];
5922         nrOfShuffles *= squaresLeft[shade];
5923         seed /= squaresLeft[shade];
5924         put(board, pieceType, rank, i, shade);
5925 }
5926
5927 void
5928 AddTwoPieces (Board board, int pieceType, int rank)
5929 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5930 {
5931         int i, n=squaresLeft[ANY], j=n-1, k;
5932
5933         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5934         i = seed % k;  // pick one
5935         nrOfShuffles *= k;
5936         seed /= k;
5937         while(i >= j) i -= j--;
5938         j = n - 1 - j; i += j;
5939         put(board, pieceType, rank, j, ANY);
5940         put(board, pieceType, rank, i, ANY);
5941 }
5942
5943 void
5944 SetUpShuffle (Board board, int number)
5945 {
5946         int i, p, first=1;
5947
5948         GetPositionNumber(); nrOfShuffles = 1;
5949
5950         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5951         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5952         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5953
5954         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5955
5956         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5957             p = (int) board[0][i];
5958             if(p < (int) BlackPawn) piecesLeft[p] ++;
5959             board[0][i] = EmptySquare;
5960         }
5961
5962         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5963             // shuffles restricted to allow normal castling put KRR first
5964             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5965                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5966             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5967                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5968             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5969                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5970             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5971                 put(board, WhiteRook, 0, 0, ANY);
5972             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5973         }
5974
5975         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5976             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5977             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5978                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5979                 while(piecesLeft[p] >= 2) {
5980                     AddOnePiece(board, p, 0, LITE);
5981                     AddOnePiece(board, p, 0, DARK);
5982                 }
5983                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5984             }
5985
5986         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5987             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5988             // but we leave King and Rooks for last, to possibly obey FRC restriction
5989             if(p == (int)WhiteRook) continue;
5990             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5991             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5992         }
5993
5994         // now everything is placed, except perhaps King (Unicorn) and Rooks
5995
5996         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5997             // Last King gets castling rights
5998             while(piecesLeft[(int)WhiteUnicorn]) {
5999                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6000                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6001             }
6002
6003             while(piecesLeft[(int)WhiteKing]) {
6004                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008
6009         } else {
6010             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6011             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6012         }
6013
6014         // Only Rooks can be left; simply place them all
6015         while(piecesLeft[(int)WhiteRook]) {
6016                 i = put(board, WhiteRook, 0, 0, ANY);
6017                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6018                         if(first) {
6019                                 first=0;
6020                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6021                         }
6022                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6023                 }
6024         }
6025         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6026             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6027         }
6028
6029         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6030 }
6031
6032 int
6033 ptclen (const char *s, char *escapes)
6034 {
6035     int n = 0;
6036     if(!*escapes) return strlen(s);
6037     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6038     return n;
6039 }
6040
6041 int
6042 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6043 /* [HGM] moved here from winboard.c because of its general usefulness */
6044 /*       Basically a safe strcpy that uses the last character as King */
6045 {
6046     int result = FALSE; int NrPieces;
6047     unsigned char partner[EmptySquare];
6048
6049     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6050                     && NrPieces >= 12 && !(NrPieces&1)) {
6051         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6052
6053         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6054         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6055             char *p, c=0;
6056             if(map[j] == '/') offs = WhitePBishop - i, j++;
6057             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6058             table[i+offs] = map[j++];
6059             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6060             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6061             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6062         }
6063         table[(int) WhiteKing]  = map[j++];
6064         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6065             char *p, c=0;
6066             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6067             i = WHITE_TO_BLACK ii;
6068             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069             table[i+offs] = map[j++];
6070             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6073         }
6074         table[(int) BlackKing]  = map[j++];
6075
6076
6077         if(*escapes) { // set up promotion pairing
6078             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6079             // pieceToChar entirely filled, so we can look up specified partners
6080             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6081                 int c = table[i];
6082                 if(c == '^' || c == '-') { // has specified partner
6083                     int p;
6084                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6085                     if(c == '^') table[i] = '+';
6086                     if(p < EmptySquare) {
6087                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6088                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6089                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6090                     }
6091                 } else if(c == '*') {
6092                     table[i] = partner[i];
6093                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6094                 }
6095             }
6096         }
6097
6098         result = TRUE;
6099     }
6100
6101     return result;
6102 }
6103
6104 int
6105 SetCharTable (unsigned char *table, const char * map)
6106 {
6107     return SetCharTableEsc(table, map, "");
6108 }
6109
6110 void
6111 Prelude (Board board)
6112 {       // [HGM] superchess: random selection of exo-pieces
6113         int i, j, k; ChessSquare p;
6114         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6115
6116         GetPositionNumber(); // use FRC position number
6117
6118         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6119             SetCharTable(pieceToChar, appData.pieceToCharTable);
6120             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6121                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6122         }
6123
6124         j = seed%4;                 seed /= 4;
6125         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6126         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6127         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6128         j = seed%3 + (seed%3 >= j); seed /= 3;
6129         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6131         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6132         j = seed%3;                 seed /= 3;
6133         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6134         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6135         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6136         j = seed%2 + (seed%2 >= j); seed /= 2;
6137         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6139         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6140         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6141         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6142         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6143         put(board, exoPieces[0],    0, 0, ANY);
6144         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6145 }
6146
6147 void
6148 InitPosition (int redraw)
6149 {
6150     ChessSquare (* pieces)[BOARD_FILES];
6151     int i, j, pawnRow=1, pieceRows=1, overrule,
6152     oldx = gameInfo.boardWidth,
6153     oldy = gameInfo.boardHeight,
6154     oldh = gameInfo.holdingsWidth;
6155     static int oldv;
6156
6157     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6158
6159     /* [AS] Initialize pv info list [HGM] and game status */
6160     {
6161         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6162             pvInfoList[i].depth = 0;
6163             boards[i][EP_STATUS] = EP_NONE;
6164             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6165         }
6166
6167         initialRulePlies = 0; /* 50-move counter start */
6168
6169         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6170         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6171     }
6172
6173
6174     /* [HGM] logic here is completely changed. In stead of full positions */
6175     /* the initialized data only consist of the two backranks. The switch */
6176     /* selects which one we will use, which is than copied to the Board   */
6177     /* initialPosition, which for the rest is initialized by Pawns and    */
6178     /* empty squares. This initial position is then copied to boards[0],  */
6179     /* possibly after shuffling, so that it remains available.            */
6180
6181     gameInfo.holdingsWidth = 0; /* default board sizes */
6182     gameInfo.boardWidth    = 8;
6183     gameInfo.boardHeight   = 8;
6184     gameInfo.holdingsSize  = 0;
6185     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6186     for(i=0; i<BOARD_FILES-6; i++)
6187       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6188     initialPosition[EP_STATUS] = EP_NONE;
6189     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6190     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6191     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6192          SetCharTable(pieceNickName, appData.pieceNickNames);
6193     else SetCharTable(pieceNickName, "............");
6194     pieces = FIDEArray;
6195
6196     switch (gameInfo.variant) {
6197     case VariantFischeRandom:
6198       shuffleOpenings = TRUE;
6199       appData.fischerCastling = TRUE;
6200     default:
6201       break;
6202     case VariantShatranj:
6203       pieces = ShatranjArray;
6204       nrCastlingRights = 0;
6205       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6206       break;
6207     case VariantMakruk:
6208       pieces = makrukArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6211       break;
6212     case VariantASEAN:
6213       pieces = aseanArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6216       break;
6217     case VariantTwoKings:
6218       pieces = twoKingsArray;
6219       break;
6220     case VariantGrand:
6221       pieces = GrandArray;
6222       nrCastlingRights = 0;
6223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224       gameInfo.boardWidth = 10;
6225       gameInfo.boardHeight = 10;
6226       gameInfo.holdingsSize = 7;
6227       break;
6228     case VariantCapaRandom:
6229       shuffleOpenings = TRUE;
6230       appData.fischerCastling = TRUE;
6231     case VariantCapablanca:
6232       pieces = CapablancaArray;
6233       gameInfo.boardWidth = 10;
6234       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235       break;
6236     case VariantGothic:
6237       pieces = GothicArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantSChess:
6242       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6243       gameInfo.holdingsSize = 7;
6244       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6245       break;
6246     case VariantJanus:
6247       pieces = JanusArray;
6248       gameInfo.boardWidth = 10;
6249       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6250       nrCastlingRights = 6;
6251         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6252         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6253         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6254         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6255         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6256         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6257       break;
6258     case VariantFalcon:
6259       pieces = FalconArray;
6260       gameInfo.boardWidth = 10;
6261       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6262       break;
6263     case VariantXiangqi:
6264       pieces = XiangqiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 10;
6267       nrCastlingRights = 0;
6268       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6269       break;
6270     case VariantShogi:
6271       pieces = ShogiArray;
6272       gameInfo.boardWidth  = 9;
6273       gameInfo.boardHeight = 9;
6274       gameInfo.holdingsSize = 7;
6275       nrCastlingRights = 0;
6276       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6277       break;
6278     case VariantChu:
6279       pieces = ChuArray; pieceRows = 3;
6280       gameInfo.boardWidth  = 12;
6281       gameInfo.boardHeight = 12;
6282       nrCastlingRights = 0;
6283       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6284                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6285       break;
6286     case VariantCourier:
6287       pieces = CourierArray;
6288       gameInfo.boardWidth  = 12;
6289       nrCastlingRights = 0;
6290       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6291       break;
6292     case VariantKnightmate:
6293       pieces = KnightmateArray;
6294       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6295       break;
6296     case VariantSpartan:
6297       pieces = SpartanArray;
6298       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6299       break;
6300     case VariantLion:
6301       pieces = lionArray;
6302       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6303       break;
6304     case VariantChuChess:
6305       pieces = ChuChessArray;
6306       gameInfo.boardWidth = 10;
6307       gameInfo.boardHeight = 10;
6308       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6309       break;
6310     case VariantFairy:
6311       pieces = fairyArray;
6312       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6313       break;
6314     case VariantGreat:
6315       pieces = GreatArray;
6316       gameInfo.boardWidth = 10;
6317       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6318       gameInfo.holdingsSize = 8;
6319       break;
6320     case VariantSuper:
6321       pieces = FIDEArray;
6322       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6323       gameInfo.holdingsSize = 8;
6324       startedFromSetupPosition = TRUE;
6325       break;
6326     case VariantCrazyhouse:
6327     case VariantBughouse:
6328       pieces = FIDEArray;
6329       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6330       gameInfo.holdingsSize = 5;
6331       break;
6332     case VariantWildCastle:
6333       pieces = FIDEArray;
6334       /* !!?shuffle with kings guaranteed to be on d or e file */
6335       shuffleOpenings = 1;
6336       break;
6337     case VariantNoCastle:
6338       pieces = FIDEArray;
6339       nrCastlingRights = 0;
6340       /* !!?unconstrained back-rank shuffle */
6341       shuffleOpenings = 1;
6342       break;
6343     }
6344
6345     overrule = 0;
6346     if(appData.NrFiles >= 0) {
6347         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6348         gameInfo.boardWidth = appData.NrFiles;
6349     }
6350     if(appData.NrRanks >= 0) {
6351         gameInfo.boardHeight = appData.NrRanks;
6352     }
6353     if(appData.holdingsSize >= 0) {
6354         i = appData.holdingsSize;
6355         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6356         gameInfo.holdingsSize = i;
6357     }
6358     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6359     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6360         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6361
6362     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6363     if(pawnRow < 1) pawnRow = 1;
6364     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6365        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6366     if(gameInfo.variant == VariantChu) pawnRow = 3;
6367
6368     /* User pieceToChar list overrules defaults */
6369     if(appData.pieceToCharTable != NULL)
6370         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6371
6372     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6373
6374         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6375             s = (ChessSquare) 0; /* account holding counts in guard band */
6376         for( i=0; i<BOARD_HEIGHT; i++ )
6377             initialPosition[i][j] = s;
6378
6379         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6380         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6381         initialPosition[pawnRow][j] = WhitePawn;
6382         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6383         if(gameInfo.variant == VariantXiangqi) {
6384             if(j&1) {
6385                 initialPosition[pawnRow][j] =
6386                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6387                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6388                    initialPosition[2][j] = WhiteCannon;
6389                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6390                 }
6391             }
6392         }
6393         if(gameInfo.variant == VariantChu) {
6394              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6395                initialPosition[pawnRow+1][j] = WhiteCobra,
6396                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6397              for(i=1; i<pieceRows; i++) {
6398                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6399                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6400              }
6401         }
6402         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6403             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6404                initialPosition[0][j] = WhiteRook;
6405                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6406             }
6407         }
6408         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6409     }
6410     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6411     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6412
6413             j=BOARD_LEFT+1;
6414             initialPosition[1][j] = WhiteBishop;
6415             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6416             j=BOARD_RGHT-2;
6417             initialPosition[1][j] = WhiteRook;
6418             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6419     }
6420
6421     if( nrCastlingRights == -1) {
6422         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6423         /*       This sets default castling rights from none to normal corners   */
6424         /* Variants with other castling rights must set them themselves above    */
6425         nrCastlingRights = 6;
6426
6427         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6428         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6429         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6430         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6431         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6432         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6433      }
6434
6435      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6436      if(gameInfo.variant == VariantGreat) { // promotion commoners
6437         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6438         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6439         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6440         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6441      }
6442      if( gameInfo.variant == VariantSChess ) {
6443       initialPosition[1][0] = BlackMarshall;
6444       initialPosition[2][0] = BlackAngel;
6445       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6446       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6447       initialPosition[1][1] = initialPosition[2][1] =
6448       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6449      }
6450   if (appData.debugMode) {
6451     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6452   }
6453     if(shuffleOpenings) {
6454         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6455         startedFromSetupPosition = TRUE;
6456     }
6457     if(startedFromPositionFile) {
6458       /* [HGM] loadPos: use PositionFile for every new game */
6459       CopyBoard(initialPosition, filePosition);
6460       for(i=0; i<nrCastlingRights; i++)
6461           initialRights[i] = filePosition[CASTLING][i];
6462       startedFromSetupPosition = TRUE;
6463     }
6464     if(*appData.men) LoadPieceDesc(appData.men);
6465
6466     CopyBoard(boards[0], initialPosition);
6467
6468     if(oldx != gameInfo.boardWidth ||
6469        oldy != gameInfo.boardHeight ||
6470        oldv != gameInfo.variant ||
6471        oldh != gameInfo.holdingsWidth
6472                                          )
6473             InitDrawingSizes(-2 ,0);
6474
6475     oldv = gameInfo.variant;
6476     if (redraw)
6477       DrawPosition(TRUE, boards[currentMove]);
6478 }
6479
6480 void
6481 SendBoard (ChessProgramState *cps, int moveNum)
6482 {
6483     char message[MSG_SIZ];
6484
6485     if (cps->useSetboard) {
6486       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6487       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6488       SendToProgram(message, cps);
6489       free(fen);
6490
6491     } else {
6492       ChessSquare *bp;
6493       int i, j, left=0, right=BOARD_WIDTH;
6494       /* Kludge to set black to move, avoiding the troublesome and now
6495        * deprecated "black" command.
6496        */
6497       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6498         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6499
6500       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6501
6502       SendToProgram("edit\n", cps);
6503       SendToProgram("#\n", cps);
6504       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6505         bp = &boards[moveNum][i][left];
6506         for (j = left; j < right; j++, bp++) {
6507           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6508           if ((int) *bp < (int) BlackPawn) {
6509             if(j == BOARD_RGHT+1)
6510                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6511             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6512             if(message[0] == '+' || message[0] == '~') {
6513               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6514                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6515                         AAA + j, ONE + i - '0');
6516             }
6517             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6518                 message[1] = BOARD_RGHT   - 1 - j + '1';
6519                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6520             }
6521             SendToProgram(message, cps);
6522           }
6523         }
6524       }
6525
6526       SendToProgram("c\n", cps);
6527       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6528         bp = &boards[moveNum][i][left];
6529         for (j = left; j < right; j++, bp++) {
6530           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6531           if (((int) *bp != (int) EmptySquare)
6532               && ((int) *bp >= (int) BlackPawn)) {
6533             if(j == BOARD_LEFT-2)
6534                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6535             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6536                     AAA + j, ONE + i - '0');
6537             if(message[0] == '+' || message[0] == '~') {
6538               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6539                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6540                         AAA + j, ONE + i - '0');
6541             }
6542             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6543                 message[1] = BOARD_RGHT   - 1 - j + '1';
6544                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6545             }
6546             SendToProgram(message, cps);
6547           }
6548         }
6549       }
6550
6551       SendToProgram(".\n", cps);
6552     }
6553     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6554 }
6555
6556 char exclusionHeader[MSG_SIZ];
6557 int exCnt, excludePtr;
6558 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6559 static Exclusion excluTab[200];
6560 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6561
6562 static void
6563 WriteMap (int s)
6564 {
6565     int j;
6566     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6567     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6568 }
6569
6570 static void
6571 ClearMap ()
6572 {
6573     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6574     excludePtr = 24; exCnt = 0;
6575     WriteMap(0);
6576 }
6577
6578 static void
6579 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6580 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6581     char buf[2*MOVE_LEN], *p;
6582     Exclusion *e = excluTab;
6583     int i;
6584     for(i=0; i<exCnt; i++)
6585         if(e[i].ff == fromX && e[i].fr == fromY &&
6586            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6587     if(i == exCnt) { // was not in exclude list; add it
6588         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6589         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6590             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6591             return; // abort
6592         }
6593         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6594         excludePtr++; e[i].mark = excludePtr++;
6595         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6596         exCnt++;
6597     }
6598     exclusionHeader[e[i].mark] = state;
6599 }
6600
6601 static int
6602 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6603 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6604     char buf[MSG_SIZ];
6605     int j, k;
6606     ChessMove moveType;
6607     if((signed char)promoChar == -1) { // kludge to indicate best move
6608         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6609             return 1; // if unparsable, abort
6610     }
6611     // update exclusion map (resolving toggle by consulting existing state)
6612     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6613     j = k%8; k >>= 3;
6614     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6615     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6616          excludeMap[k] |=   1<<j;
6617     else excludeMap[k] &= ~(1<<j);
6618     // update header
6619     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6620     // inform engine
6621     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6622     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6623     SendToBoth(buf);
6624     return (state == '+');
6625 }
6626
6627 static void
6628 ExcludeClick (int index)
6629 {
6630     int i, j;
6631     Exclusion *e = excluTab;
6632     if(index < 25) { // none, best or tail clicked
6633         if(index < 13) { // none: include all
6634             WriteMap(0); // clear map
6635             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6636             SendToBoth("include all\n"); // and inform engine
6637         } else if(index > 18) { // tail
6638             if(exclusionHeader[19] == '-') { // tail was excluded
6639                 SendToBoth("include all\n");
6640                 WriteMap(0); // clear map completely
6641                 // now re-exclude selected moves
6642                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6643                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6644             } else { // tail was included or in mixed state
6645                 SendToBoth("exclude all\n");
6646                 WriteMap(0xFF); // fill map completely
6647                 // now re-include selected moves
6648                 j = 0; // count them
6649                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6650                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6651                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6652             }
6653         } else { // best
6654             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6655         }
6656     } else {
6657         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6658             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6659             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6660             break;
6661         }
6662     }
6663 }
6664
6665 ChessSquare
6666 DefaultPromoChoice (int white)
6667 {
6668     ChessSquare result;
6669     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6670        gameInfo.variant == VariantMakruk)
6671         result = WhiteFerz; // no choice
6672     else if(gameInfo.variant == VariantASEAN)
6673         result = WhiteRook; // no choice
6674     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6675         result= WhiteKing; // in Suicide Q is the last thing we want
6676     else if(gameInfo.variant == VariantSpartan)
6677         result = white ? WhiteQueen : WhiteAngel;
6678     else result = WhiteQueen;
6679     if(!white) result = WHITE_TO_BLACK result;
6680     return result;
6681 }
6682
6683 static int autoQueen; // [HGM] oneclick
6684
6685 int
6686 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6687 {
6688     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6689     /* [HGM] add Shogi promotions */
6690     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6691     ChessSquare piece, partner;
6692     ChessMove moveType;
6693     Boolean premove;
6694
6695     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6696     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6697
6698     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6699       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6700         return FALSE;
6701
6702     piece = boards[currentMove][fromY][fromX];
6703     if(gameInfo.variant == VariantChu) {
6704         promotionZoneSize = BOARD_HEIGHT/3;
6705         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6706     } else if(gameInfo.variant == VariantShogi) {
6707         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6708         highestPromotingPiece = (int)WhiteAlfil;
6709     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6710         promotionZoneSize = 3;
6711     }
6712
6713     // Treat Lance as Pawn when it is not representing Amazon or Lance
6714     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6715         if(piece == WhiteLance) piece = WhitePawn; else
6716         if(piece == BlackLance) piece = BlackPawn;
6717     }
6718
6719     // next weed out all moves that do not touch the promotion zone at all
6720     if((int)piece >= BlackPawn) {
6721         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6722              return FALSE;
6723         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6724         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6725     } else {
6726         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6727            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6728         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6729              return FALSE;
6730     }
6731
6732     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6733
6734     // weed out mandatory Shogi promotions
6735     if(gameInfo.variant == VariantShogi) {
6736         if(piece >= BlackPawn) {
6737             if(toY == 0 && piece == BlackPawn ||
6738                toY == 0 && piece == BlackQueen ||
6739                toY <= 1 && piece == BlackKnight) {
6740                 *promoChoice = '+';
6741                 return FALSE;
6742             }
6743         } else {
6744             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6745                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6746                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6747                 *promoChoice = '+';
6748                 return FALSE;
6749             }
6750         }
6751     }
6752
6753     // weed out obviously illegal Pawn moves
6754     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6755         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6756         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6757         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6758         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6759         // note we are not allowed to test for valid (non-)capture, due to premove
6760     }
6761
6762     // we either have a choice what to promote to, or (in Shogi) whether to promote
6763     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6764        gameInfo.variant == VariantMakruk) {
6765         ChessSquare p=BlackFerz;  // no choice
6766         while(p < EmptySquare) {  //but make sure we use piece that exists
6767             *promoChoice = PieceToChar(p++);
6768             if(*promoChoice != '.') break;
6769         }
6770         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6771     }
6772     // no sense asking what we must promote to if it is going to explode...
6773     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6774         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6775         return FALSE;
6776     }
6777     // give caller the default choice even if we will not make it
6778     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6779     partner = piece; // pieces can promote if the pieceToCharTable says so
6780     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6781     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6782     if(        sweepSelect && gameInfo.variant != VariantGreat
6783                            && gameInfo.variant != VariantGrand
6784                            && gameInfo.variant != VariantSuper) return FALSE;
6785     if(autoQueen) return FALSE; // predetermined
6786
6787     // suppress promotion popup on illegal moves that are not premoves
6788     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6789               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6790     if(appData.testLegality && !premove) {
6791         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6792                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6793         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6794         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6795             return FALSE;
6796     }
6797
6798     return TRUE;
6799 }
6800
6801 int
6802 InPalace (int row, int column)
6803 {   /* [HGM] for Xiangqi */
6804     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6805          column < (BOARD_WIDTH + 4)/2 &&
6806          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6807     return FALSE;
6808 }
6809
6810 int
6811 PieceForSquare (int x, int y)
6812 {
6813   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6814      return -1;
6815   else
6816      return boards[currentMove][y][x];
6817 }
6818
6819 int
6820 OKToStartUserMove (int x, int y)
6821 {
6822     ChessSquare from_piece;
6823     int white_piece;
6824
6825     if (matchMode) return FALSE;
6826     if (gameMode == EditPosition) return TRUE;
6827
6828     if (x >= 0 && y >= 0)
6829       from_piece = boards[currentMove][y][x];
6830     else
6831       from_piece = EmptySquare;
6832
6833     if (from_piece == EmptySquare) return FALSE;
6834
6835     white_piece = (int)from_piece >= (int)WhitePawn &&
6836       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6837
6838     switch (gameMode) {
6839       case AnalyzeFile:
6840       case TwoMachinesPlay:
6841       case EndOfGame:
6842         return FALSE;
6843
6844       case IcsObserving:
6845       case IcsIdle:
6846         return FALSE;
6847
6848       case MachinePlaysWhite:
6849       case IcsPlayingBlack:
6850         if (appData.zippyPlay) return FALSE;
6851         if (white_piece) {
6852             DisplayMoveError(_("You are playing Black"));
6853             return FALSE;
6854         }
6855         break;
6856
6857       case MachinePlaysBlack:
6858       case IcsPlayingWhite:
6859         if (appData.zippyPlay) return FALSE;
6860         if (!white_piece) {
6861             DisplayMoveError(_("You are playing White"));
6862             return FALSE;
6863         }
6864         break;
6865
6866       case PlayFromGameFile:
6867             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6868       case EditGame:
6869       case AnalyzeMode:
6870         if (!white_piece && WhiteOnMove(currentMove)) {
6871             DisplayMoveError(_("It is White's turn"));
6872             return FALSE;
6873         }
6874         if (white_piece && !WhiteOnMove(currentMove)) {
6875             DisplayMoveError(_("It is Black's turn"));
6876             return FALSE;
6877         }
6878         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6879             /* Editing correspondence game history */
6880             /* Could disallow this or prompt for confirmation */
6881             cmailOldMove = -1;
6882         }
6883         break;
6884
6885       case BeginningOfGame:
6886         if (appData.icsActive) return FALSE;
6887         if (!appData.noChessProgram) {
6888             if (!white_piece) {
6889                 DisplayMoveError(_("You are playing White"));
6890                 return FALSE;
6891             }
6892         }
6893         break;
6894
6895       case Training:
6896         if (!white_piece && WhiteOnMove(currentMove)) {
6897             DisplayMoveError(_("It is White's turn"));
6898             return FALSE;
6899         }
6900         if (white_piece && !WhiteOnMove(currentMove)) {
6901             DisplayMoveError(_("It is Black's turn"));
6902             return FALSE;
6903         }
6904         break;
6905
6906       default:
6907       case IcsExamining:
6908         break;
6909     }
6910     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6911         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6912         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6913         && gameMode != AnalyzeFile && gameMode != Training) {
6914         DisplayMoveError(_("Displayed position is not current"));
6915         return FALSE;
6916     }
6917     return TRUE;
6918 }
6919
6920 Boolean
6921 OnlyMove (int *x, int *y, Boolean captures)
6922 {
6923     DisambiguateClosure cl;
6924     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6925     switch(gameMode) {
6926       case MachinePlaysBlack:
6927       case IcsPlayingWhite:
6928       case BeginningOfGame:
6929         if(!WhiteOnMove(currentMove)) return FALSE;
6930         break;
6931       case MachinePlaysWhite:
6932       case IcsPlayingBlack:
6933         if(WhiteOnMove(currentMove)) return FALSE;
6934         break;
6935       case EditGame:
6936         break;
6937       default:
6938         return FALSE;
6939     }
6940     cl.pieceIn = EmptySquare;
6941     cl.rfIn = *y;
6942     cl.ffIn = *x;
6943     cl.rtIn = -1;
6944     cl.ftIn = -1;
6945     cl.promoCharIn = NULLCHAR;
6946     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6947     if( cl.kind == NormalMove ||
6948         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6949         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6950         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6951       fromX = cl.ff;
6952       fromY = cl.rf;
6953       *x = cl.ft;
6954       *y = cl.rt;
6955       return TRUE;
6956     }
6957     if(cl.kind != ImpossibleMove) return FALSE;
6958     cl.pieceIn = EmptySquare;
6959     cl.rfIn = -1;
6960     cl.ffIn = -1;
6961     cl.rtIn = *y;
6962     cl.ftIn = *x;
6963     cl.promoCharIn = NULLCHAR;
6964     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6965     if( cl.kind == NormalMove ||
6966         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6967         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6968         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6969       fromX = cl.ff;
6970       fromY = cl.rf;
6971       *x = cl.ft;
6972       *y = cl.rt;
6973       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6974       return TRUE;
6975     }
6976     return FALSE;
6977 }
6978
6979 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6980 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6981 int lastLoadGameUseList = FALSE;
6982 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6983 ChessMove lastLoadGameStart = EndOfFile;
6984 int doubleClick;
6985 Boolean addToBookFlag;
6986 static Board rightsBoard, nullBoard;
6987
6988 void
6989 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6990 {
6991     ChessMove moveType;
6992     ChessSquare pup;
6993     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6994
6995     /* Check if the user is playing in turn.  This is complicated because we
6996        let the user "pick up" a piece before it is his turn.  So the piece he
6997        tried to pick up may have been captured by the time he puts it down!
6998        Therefore we use the color the user is supposed to be playing in this
6999        test, not the color of the piece that is currently on the starting
7000        square---except in EditGame mode, where the user is playing both
7001        sides; fortunately there the capture race can't happen.  (It can
7002        now happen in IcsExamining mode, but that's just too bad.  The user
7003        will get a somewhat confusing message in that case.)
7004        */
7005
7006     switch (gameMode) {
7007       case AnalyzeFile:
7008       case TwoMachinesPlay:
7009       case EndOfGame:
7010       case IcsObserving:
7011       case IcsIdle:
7012         /* We switched into a game mode where moves are not accepted,
7013            perhaps while the mouse button was down. */
7014         return;
7015
7016       case MachinePlaysWhite:
7017         /* User is moving for Black */
7018         if (WhiteOnMove(currentMove)) {
7019             DisplayMoveError(_("It is White's turn"));
7020             return;
7021         }
7022         break;
7023
7024       case MachinePlaysBlack:
7025         /* User is moving for White */
7026         if (!WhiteOnMove(currentMove)) {
7027             DisplayMoveError(_("It is Black's turn"));
7028             return;
7029         }
7030         break;
7031
7032       case PlayFromGameFile:
7033             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7034       case EditGame:
7035       case IcsExamining:
7036       case BeginningOfGame:
7037       case AnalyzeMode:
7038       case Training:
7039         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7040         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7041             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7042             /* User is moving for Black */
7043             if (WhiteOnMove(currentMove)) {
7044                 DisplayMoveError(_("It is White's turn"));
7045                 return;
7046             }
7047         } else {
7048             /* User is moving for White */
7049             if (!WhiteOnMove(currentMove)) {
7050                 DisplayMoveError(_("It is Black's turn"));
7051                 return;
7052             }
7053         }
7054         break;
7055
7056       case IcsPlayingBlack:
7057         /* User is moving for Black */
7058         if (WhiteOnMove(currentMove)) {
7059             if (!appData.premove) {
7060                 DisplayMoveError(_("It is White's turn"));
7061             } else if (toX >= 0 && toY >= 0) {
7062                 premoveToX = toX;
7063                 premoveToY = toY;
7064                 premoveFromX = fromX;
7065                 premoveFromY = fromY;
7066                 premovePromoChar = promoChar;
7067                 gotPremove = 1;
7068                 if (appData.debugMode)
7069                     fprintf(debugFP, "Got premove: fromX %d,"
7070                             "fromY %d, toX %d, toY %d\n",
7071                             fromX, fromY, toX, toY);
7072             }
7073             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7074             return;
7075         }
7076         break;
7077
7078       case IcsPlayingWhite:
7079         /* User is moving for White */
7080         if (!WhiteOnMove(currentMove)) {
7081             if (!appData.premove) {
7082                 DisplayMoveError(_("It is Black's turn"));
7083             } else if (toX >= 0 && toY >= 0) {
7084                 premoveToX = toX;
7085                 premoveToY = toY;
7086                 premoveFromX = fromX;
7087                 premoveFromY = fromY;
7088                 premovePromoChar = promoChar;
7089                 gotPremove = 1;
7090                 if (appData.debugMode)
7091                     fprintf(debugFP, "Got premove: fromX %d,"
7092                             "fromY %d, toX %d, toY %d\n",
7093                             fromX, fromY, toX, toY);
7094             }
7095             DrawPosition(TRUE, boards[currentMove]);
7096             return;
7097         }
7098         break;
7099
7100       default:
7101         break;
7102
7103       case EditPosition:
7104         /* EditPosition, empty square, or different color piece;
7105            click-click move is possible */
7106         if (toX == -2 || toY == -2) {
7107             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7108             DrawPosition(FALSE, boards[currentMove]);
7109             return;
7110         } else if (toX >= 0 && toY >= 0) {
7111             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7112                 ChessSquare p = boards[0][rf][ff];
7113                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7114                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7115                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7116                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7117                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7118                     gatingPiece = p;
7119                 }
7120             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7121             boards[0][toY][toX] = boards[0][fromY][fromX];
7122             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7123                 if(boards[0][fromY][0] != EmptySquare) {
7124                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7125                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7126                 }
7127             } else
7128             if(fromX == BOARD_RGHT+1) {
7129                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7130                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7131                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7132                 }
7133             } else
7134             boards[0][fromY][fromX] = gatingPiece;
7135             ClearHighlights();
7136             DrawPosition(FALSE, boards[currentMove]);
7137             return;
7138         }
7139         return;
7140     }
7141
7142     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7143     pup = boards[currentMove][toY][toX];
7144
7145     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7146     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7147          if( pup != EmptySquare ) return;
7148          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7149            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7150                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7151            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7152            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7153            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7154            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7155          fromY = DROP_RANK;
7156     }
7157
7158     /* [HGM] always test for legality, to get promotion info */
7159     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7160                                          fromY, fromX, toY, toX, promoChar);
7161
7162     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7163
7164     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7165
7166     /* [HGM] but possibly ignore an IllegalMove result */
7167     if (appData.testLegality) {
7168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7169             DisplayMoveError(_("Illegal move"));
7170             return;
7171         }
7172     }
7173
7174     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7175         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7176              ClearPremoveHighlights(); // was included
7177         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7178         DrawPosition(FALSE, NULL);
7179         return;
7180     }
7181
7182     if(addToBookFlag) { // adding moves to book
7183         char buf[MSG_SIZ], move[MSG_SIZ];
7184         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7185         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7186                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7187         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7188         AddBookMove(buf);
7189         addToBookFlag = FALSE;
7190         ClearHighlights();
7191         return;
7192     }
7193
7194     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7195 }
7196
7197 /* Common tail of UserMoveEvent and DropMenuEvent */
7198 int
7199 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7200 {
7201     char *bookHit = 0;
7202
7203     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7204         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7205         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7206         if(WhiteOnMove(currentMove)) {
7207             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7208         } else {
7209             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7210         }
7211     }
7212
7213     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7214        move type in caller when we know the move is a legal promotion */
7215     if(moveType == NormalMove && promoChar)
7216         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7217
7218     /* [HGM] <popupFix> The following if has been moved here from
7219        UserMoveEvent(). Because it seemed to belong here (why not allow
7220        piece drops in training games?), and because it can only be
7221        performed after it is known to what we promote. */
7222     if (gameMode == Training) {
7223       /* compare the move played on the board to the next move in the
7224        * game. If they match, display the move and the opponent's response.
7225        * If they don't match, display an error message.
7226        */
7227       int saveAnimate;
7228       Board testBoard;
7229       CopyBoard(testBoard, boards[currentMove]);
7230       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7231
7232       if (CompareBoards(testBoard, boards[currentMove+1])) {
7233         ForwardInner(currentMove+1);
7234
7235         /* Autoplay the opponent's response.
7236          * if appData.animate was TRUE when Training mode was entered,
7237          * the response will be animated.
7238          */
7239         saveAnimate = appData.animate;
7240         appData.animate = animateTraining;
7241         ForwardInner(currentMove+1);
7242         appData.animate = saveAnimate;
7243
7244         /* check for the end of the game */
7245         if (currentMove >= forwardMostMove) {
7246           gameMode = PlayFromGameFile;
7247           ModeHighlight();
7248           SetTrainingModeOff();
7249           DisplayInformation(_("End of game"));
7250         }
7251       } else {
7252         DisplayError(_("Incorrect move"), 0);
7253       }
7254       return 1;
7255     }
7256
7257   /* Ok, now we know that the move is good, so we can kill
7258      the previous line in Analysis Mode */
7259   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7260                                 && currentMove < forwardMostMove) {
7261     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7262     else forwardMostMove = currentMove;
7263   }
7264
7265   ClearMap();
7266
7267   /* If we need the chess program but it's dead, restart it */
7268   ResurrectChessProgram();
7269
7270   /* A user move restarts a paused game*/
7271   if (pausing)
7272     PauseEvent();
7273
7274   thinkOutput[0] = NULLCHAR;
7275
7276   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7277
7278   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7279     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7280     return 1;
7281   }
7282
7283   if (gameMode == BeginningOfGame) {
7284     if (appData.noChessProgram) {
7285       gameMode = EditGame;
7286       SetGameInfo();
7287     } else {
7288       char buf[MSG_SIZ];
7289       gameMode = MachinePlaysBlack;
7290       StartClocks();
7291       SetGameInfo();
7292       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7293       DisplayTitle(buf);
7294       if (first.sendName) {
7295         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7296         SendToProgram(buf, &first);
7297       }
7298       StartClocks();
7299     }
7300     ModeHighlight();
7301   }
7302
7303   /* Relay move to ICS or chess engine */
7304   if (appData.icsActive) {
7305     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7306         gameMode == IcsExamining) {
7307       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7308         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7309         SendToICS("draw ");
7310         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7311       }
7312       // also send plain move, in case ICS does not understand atomic claims
7313       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7314       ics_user_moved = 1;
7315     }
7316   } else {
7317     if (first.sendTime && (gameMode == BeginningOfGame ||
7318                            gameMode == MachinePlaysWhite ||
7319                            gameMode == MachinePlaysBlack)) {
7320       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7321     }
7322     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7323          // [HGM] book: if program might be playing, let it use book
7324         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7325         first.maybeThinking = TRUE;
7326     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7327         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7328         SendBoard(&first, currentMove+1);
7329         if(second.analyzing) {
7330             if(!second.useSetboard) SendToProgram("undo\n", &second);
7331             SendBoard(&second, currentMove+1);
7332         }
7333     } else {
7334         SendMoveToProgram(forwardMostMove-1, &first);
7335         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7336     }
7337     if (currentMove == cmailOldMove + 1) {
7338       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7339     }
7340   }
7341
7342   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7343
7344   switch (gameMode) {
7345   case EditGame:
7346     if(appData.testLegality)
7347     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7348     case MT_NONE:
7349     case MT_CHECK:
7350       break;
7351     case MT_CHECKMATE:
7352     case MT_STAINMATE:
7353       if (WhiteOnMove(currentMove)) {
7354         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7355       } else {
7356         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7357       }
7358       break;
7359     case MT_STALEMATE:
7360       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7361       break;
7362     }
7363     break;
7364
7365   case MachinePlaysBlack:
7366   case MachinePlaysWhite:
7367     /* disable certain menu options while machine is thinking */
7368     SetMachineThinkingEnables();
7369     break;
7370
7371   default:
7372     break;
7373   }
7374
7375   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7376   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7377
7378   if(bookHit) { // [HGM] book: simulate book reply
7379         static char bookMove[MSG_SIZ]; // a bit generous?
7380
7381         programStats.nodes = programStats.depth = programStats.time =
7382         programStats.score = programStats.got_only_move = 0;
7383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7384
7385         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7386         strcat(bookMove, bookHit);
7387         HandleMachineMove(bookMove, &first);
7388   }
7389   return 1;
7390 }
7391
7392 void
7393 MarkByFEN(char *fen)
7394 {
7395         int r, f;
7396         if(!appData.markers || !appData.highlightDragging) return;
7397         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7398         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7399         while(*fen) {
7400             int s = 0;
7401             marker[r][f] = 0;
7402             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7403             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7404             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7405             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7406             if(*fen == 'T') marker[r][f++] = 0; else
7407             if(*fen == 'Y') marker[r][f++] = 1; else
7408             if(*fen == 'G') marker[r][f++] = 3; else
7409             if(*fen == 'B') marker[r][f++] = 4; else
7410             if(*fen == 'C') marker[r][f++] = 5; else
7411             if(*fen == 'M') marker[r][f++] = 6; else
7412             if(*fen == 'W') marker[r][f++] = 7; else
7413             if(*fen == 'D') marker[r][f++] = 8; else
7414             if(*fen == 'R') marker[r][f++] = 2; else {
7415                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7416               f += s; fen -= s>0;
7417             }
7418             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7419             if(r < 0) break;
7420             fen++;
7421         }
7422         DrawPosition(TRUE, NULL);
7423 }
7424
7425 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7426
7427 void
7428 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7429 {
7430     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7431     Markers *m = (Markers *) closure;
7432     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7433                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7434         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7435                          || kind == WhiteCapturesEnPassant
7436                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7437     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7438 }
7439
7440 static int hoverSavedValid;
7441
7442 void
7443 MarkTargetSquares (int clear)
7444 {
7445   int x, y, sum=0;
7446   if(clear) { // no reason to ever suppress clearing
7447     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7448     hoverSavedValid = 0;
7449     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7450   } else {
7451     int capt = 0;
7452     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7453        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7454     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7455     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7456       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7457       if(capt)
7458       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7459     }
7460   }
7461   DrawPosition(FALSE, NULL);
7462 }
7463
7464 int
7465 Explode (Board board, int fromX, int fromY, int toX, int toY)
7466 {
7467     if(gameInfo.variant == VariantAtomic &&
7468        (board[toY][toX] != EmptySquare ||                     // capture?
7469         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7470                          board[fromY][fromX] == BlackPawn   )
7471       )) {
7472         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7473         return TRUE;
7474     }
7475     return FALSE;
7476 }
7477
7478 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7479
7480 int
7481 CanPromote (ChessSquare piece, int y)
7482 {
7483         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7484         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7485         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7486         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7487            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7488           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7489            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7490         return (piece == BlackPawn && y <= zone ||
7491                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7492                 piece == BlackLance && y <= zone ||
7493                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7494 }
7495
7496 void
7497 HoverEvent (int xPix, int yPix, int x, int y)
7498 {
7499         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7500         int r, f;
7501         if(!first.highlight) return;
7502         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7503         if(x == oldX && y == oldY) return; // only do something if we enter new square
7504         oldFromX = fromX; oldFromY = fromY;
7505         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7506           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7507             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7508           hoverSavedValid = 1;
7509         } else if(oldX != x || oldY != y) {
7510           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7511           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7512           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7513             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7514           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7515             char buf[MSG_SIZ];
7516             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7517             SendToProgram(buf, &first);
7518           }
7519           oldX = x; oldY = y;
7520 //        SetHighlights(fromX, fromY, x, y);
7521         }
7522 }
7523
7524 void ReportClick(char *action, int x, int y)
7525 {
7526         char buf[MSG_SIZ]; // Inform engine of what user does
7527         int r, f;
7528         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7529           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7530             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7531         if(!first.highlight || gameMode == EditPosition) return;
7532         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7533         SendToProgram(buf, &first);
7534 }
7535
7536 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7537
7538 void
7539 LeftClick (ClickType clickType, int xPix, int yPix)
7540 {
7541     int x, y;
7542     Boolean saveAnimate;
7543     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7544     char promoChoice = NULLCHAR;
7545     ChessSquare piece;
7546     static TimeMark lastClickTime, prevClickTime;
7547
7548     if(flashing) return;
7549
7550     x = EventToSquare(xPix, BOARD_WIDTH);
7551     y = EventToSquare(yPix, BOARD_HEIGHT);
7552     if (!flipView && y >= 0) {
7553         y = BOARD_HEIGHT - 1 - y;
7554     }
7555     if (flipView && x >= 0) {
7556         x = BOARD_WIDTH - 1 - x;
7557     }
7558
7559     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7560         static int dummy;
7561         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7562         right = TRUE;
7563         return;
7564     }
7565
7566     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7567
7568     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7569
7570     if (clickType == Press) ErrorPopDown();
7571     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7572
7573     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7574         defaultPromoChoice = promoSweep;
7575         promoSweep = EmptySquare;   // terminate sweep
7576         promoDefaultAltered = TRUE;
7577         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7578     }
7579
7580     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7581         if(clickType == Release) return; // ignore upclick of click-click destination
7582         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7583         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7584         if(gameInfo.holdingsWidth &&
7585                 (WhiteOnMove(currentMove)
7586                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7587                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7588             // click in right holdings, for determining promotion piece
7589             ChessSquare p = boards[currentMove][y][x];
7590             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7591             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7592             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7593                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7594                 fromX = fromY = -1;
7595                 return;
7596             }
7597         }
7598         DrawPosition(FALSE, boards[currentMove]);
7599         return;
7600     }
7601
7602     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7603     if(clickType == Press
7604             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7605               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7606               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7607         return;
7608
7609     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7610         // could be static click on premove from-square: abort premove
7611         gotPremove = 0;
7612         ClearPremoveHighlights();
7613     }
7614
7615     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7616         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7617
7618     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7619         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7620                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7621         defaultPromoChoice = DefaultPromoChoice(side);
7622     }
7623
7624     autoQueen = appData.alwaysPromoteToQueen;
7625
7626     if (fromX == -1) {
7627       int originalY = y;
7628       gatingPiece = EmptySquare;
7629       if (clickType != Press) {
7630         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7631             DragPieceEnd(xPix, yPix); dragging = 0;
7632             DrawPosition(FALSE, NULL);
7633         }
7634         return;
7635       }
7636       doubleClick = FALSE;
7637       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7638         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7639       }
7640       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7641       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7642          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7643          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7644             /* First square */
7645             if (OKToStartUserMove(fromX, fromY)) {
7646                 second = 0;
7647                 ReportClick("lift", x, y);
7648                 MarkTargetSquares(0);
7649                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7650                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7651                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7652                     promoSweep = defaultPromoChoice;
7653                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7654                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7655                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7656                 }
7657                 if (appData.highlightDragging) {
7658                     SetHighlights(fromX, fromY, -1, -1);
7659                 } else {
7660                     ClearHighlights();
7661                 }
7662             } else fromX = fromY = -1;
7663             return;
7664         }
7665     }
7666
7667     /* fromX != -1 */
7668     if (clickType == Press && gameMode != EditPosition) {
7669         ChessSquare fromP;
7670         ChessSquare toP;
7671         int frc;
7672
7673         // ignore off-board to clicks
7674         if(y < 0 || x < 0) return;
7675
7676         /* Check if clicking again on the same color piece */
7677         fromP = boards[currentMove][fromY][fromX];
7678         toP = boards[currentMove][y][x];
7679         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7680         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7681             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7682            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7683              WhitePawn <= toP && toP <= WhiteKing &&
7684              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7685              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7686             (BlackPawn <= fromP && fromP <= BlackKing &&
7687              BlackPawn <= toP && toP <= BlackKing &&
7688              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7689              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7690             /* Clicked again on same color piece -- changed his mind */
7691             second = (x == fromX && y == fromY);
7692             killX = killY = kill2X = kill2Y = -1;
7693             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7694                 second = FALSE; // first double-click rather than scond click
7695                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7696             }
7697             promoDefaultAltered = FALSE;
7698            if(!second) MarkTargetSquares(1);
7699            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7700             if (appData.highlightDragging) {
7701                 SetHighlights(x, y, -1, -1);
7702             } else {
7703                 ClearHighlights();
7704             }
7705             if (OKToStartUserMove(x, y)) {
7706                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7707                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7708                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7709                  gatingPiece = boards[currentMove][fromY][fromX];
7710                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7711                 fromX = x;
7712                 fromY = y; dragging = 1;
7713                 if(!second) ReportClick("lift", x, y);
7714                 MarkTargetSquares(0);
7715                 DragPieceBegin(xPix, yPix, FALSE);
7716                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7717                     promoSweep = defaultPromoChoice;
7718                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7719                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7720                 }
7721             }
7722            }
7723            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7724            second = FALSE;
7725         }
7726         // ignore clicks on holdings
7727         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7728     }
7729
7730     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7731         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7732         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7733         return;
7734     }
7735
7736     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7737         DragPieceEnd(xPix, yPix); dragging = 0;
7738         if(clearFlag) {
7739             // a deferred attempt to click-click move an empty square on top of a piece
7740             boards[currentMove][y][x] = EmptySquare;
7741             ClearHighlights();
7742             DrawPosition(FALSE, boards[currentMove]);
7743             fromX = fromY = -1; clearFlag = 0;
7744             return;
7745         }
7746         if (appData.animateDragging) {
7747             /* Undo animation damage if any */
7748             DrawPosition(FALSE, NULL);
7749         }
7750         if (second) {
7751             /* Second up/down in same square; just abort move */
7752             second = 0;
7753             fromX = fromY = -1;
7754             gatingPiece = EmptySquare;
7755             ClearHighlights();
7756             gotPremove = 0;
7757             ClearPremoveHighlights();
7758             MarkTargetSquares(-1);
7759             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7760         } else {
7761             /* First upclick in same square; start click-click mode */
7762             SetHighlights(x, y, -1, -1);
7763         }
7764         return;
7765     }
7766
7767     clearFlag = 0;
7768
7769     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7770        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7771         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7772         DisplayMessage(_("only marked squares are legal"),"");
7773         DrawPosition(TRUE, NULL);
7774         return; // ignore to-click
7775     }
7776
7777     /* we now have a different from- and (possibly off-board) to-square */
7778     /* Completed move */
7779     if(!sweepSelecting) {
7780         toX = x;
7781         toY = y;
7782     }
7783
7784     piece = boards[currentMove][fromY][fromX];
7785
7786     saveAnimate = appData.animate;
7787     if (clickType == Press) {
7788         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7789         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7790             // must be Edit Position mode with empty-square selected
7791             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7792             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7793             return;
7794         }
7795         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7796             return;
7797         }
7798         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7799             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7800         } else
7801         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7802         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7803           if(appData.sweepSelect) {
7804             promoSweep = defaultPromoChoice;
7805             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7806             selectFlag = 0; lastX = xPix; lastY = yPix;
7807             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7808             saveFlash = appData.flashCount; appData.flashCount = 0;
7809             Sweep(0); // Pawn that is going to promote: preview promotion piece
7810             sweepSelecting = 1;
7811             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7812             MarkTargetSquares(1);
7813           }
7814           return; // promo popup appears on up-click
7815         }
7816         /* Finish clickclick move */
7817         if (appData.animate || appData.highlightLastMove) {
7818             SetHighlights(fromX, fromY, toX, toY);
7819         } else {
7820             ClearHighlights();
7821         }
7822         MarkTargetSquares(1);
7823     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7824         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7825         *promoRestrict = 0; appData.flashCount = saveFlash;
7826         if (appData.animate || appData.highlightLastMove) {
7827             SetHighlights(fromX, fromY, toX, toY);
7828         } else {
7829             ClearHighlights();
7830         }
7831         MarkTargetSquares(1);
7832     } else {
7833 #if 0
7834 // [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
7835         /* Finish drag move */
7836         if (appData.highlightLastMove) {
7837             SetHighlights(fromX, fromY, toX, toY);
7838         } else {
7839             ClearHighlights();
7840         }
7841 #endif
7842         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7843           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7844         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7845         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7846           dragging *= 2;            // flag button-less dragging if we are dragging
7847           MarkTargetSquares(1);
7848           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7849           else {
7850             kill2X = killX; kill2Y = killY;
7851             killX = x; killY = y;     // remember this square as intermediate
7852             ReportClick("put", x, y); // and inform engine
7853             ReportClick("lift", x, y);
7854             MarkTargetSquares(0);
7855             return;
7856           }
7857         }
7858         DragPieceEnd(xPix, yPix); dragging = 0;
7859         /* Don't animate move and drag both */
7860         appData.animate = FALSE;
7861         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7862     }
7863
7864     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7865     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7866         ChessSquare piece = boards[currentMove][fromY][fromX];
7867         if(gameMode == EditPosition && piece != EmptySquare &&
7868            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7869             int n;
7870
7871             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7872                 n = PieceToNumber(piece - (int)BlackPawn);
7873                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7874                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7875                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7876             } else
7877             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7878                 n = PieceToNumber(piece);
7879                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7880                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7881                 boards[currentMove][n][BOARD_WIDTH-2]++;
7882             }
7883             boards[currentMove][fromY][fromX] = EmptySquare;
7884         }
7885         ClearHighlights();
7886         fromX = fromY = -1;
7887         MarkTargetSquares(1);
7888         DrawPosition(TRUE, boards[currentMove]);
7889         return;
7890     }
7891
7892     // off-board moves should not be highlighted
7893     if(x < 0 || y < 0) ClearHighlights();
7894     else ReportClick("put", x, y);
7895
7896     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7897
7898     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7899
7900     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7901         SetHighlights(fromX, fromY, toX, toY);
7902         MarkTargetSquares(1);
7903         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7904             // [HGM] super: promotion to captured piece selected from holdings
7905             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7906             promotionChoice = TRUE;
7907             // kludge follows to temporarily execute move on display, without promoting yet
7908             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7909             boards[currentMove][toY][toX] = p;
7910             DrawPosition(FALSE, boards[currentMove]);
7911             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7912             boards[currentMove][toY][toX] = q;
7913             DisplayMessage("Click in holdings to choose piece", "");
7914             return;
7915         }
7916         PromotionPopUp(promoChoice);
7917     } else {
7918         int oldMove = currentMove;
7919         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7920         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7921         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7922         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7923         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7924            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7925             DrawPosition(TRUE, boards[currentMove]);
7926         fromX = fromY = -1;
7927         flashing = 0;
7928     }
7929     appData.animate = saveAnimate;
7930     if (appData.animate || appData.animateDragging) {
7931         /* Undo animation damage if needed */
7932 //      DrawPosition(FALSE, NULL);
7933     }
7934 }
7935
7936 int
7937 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7938 {   // front-end-free part taken out of PieceMenuPopup
7939     int whichMenu; int xSqr, ySqr;
7940
7941     if(seekGraphUp) { // [HGM] seekgraph
7942         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7943         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7944         return -2;
7945     }
7946
7947     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7948          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7949         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7950         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7951         if(action == Press)   {
7952             originalFlip = flipView;
7953             flipView = !flipView; // temporarily flip board to see game from partners perspective
7954             DrawPosition(TRUE, partnerBoard);
7955             DisplayMessage(partnerStatus, "");
7956             partnerUp = TRUE;
7957         } else if(action == Release) {
7958             flipView = originalFlip;
7959             DrawPosition(TRUE, boards[currentMove]);
7960             partnerUp = FALSE;
7961         }
7962         return -2;
7963     }
7964
7965     xSqr = EventToSquare(x, BOARD_WIDTH);
7966     ySqr = EventToSquare(y, BOARD_HEIGHT);
7967     if (action == Release) {
7968         if(pieceSweep != EmptySquare) {
7969             EditPositionMenuEvent(pieceSweep, toX, toY);
7970             pieceSweep = EmptySquare;
7971         } else UnLoadPV(); // [HGM] pv
7972     }
7973     if (action != Press) return -2; // return code to be ignored
7974     switch (gameMode) {
7975       case IcsExamining:
7976         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7977       case EditPosition:
7978         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7979         if (xSqr < 0 || ySqr < 0) return -1;
7980         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7981         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7982         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7983         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7984         NextPiece(0);
7985         return 2; // grab
7986       case IcsObserving:
7987         if(!appData.icsEngineAnalyze) return -1;
7988       case IcsPlayingWhite:
7989       case IcsPlayingBlack:
7990         if(!appData.zippyPlay) goto noZip;
7991       case AnalyzeMode:
7992       case AnalyzeFile:
7993       case MachinePlaysWhite:
7994       case MachinePlaysBlack:
7995       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7996         if (!appData.dropMenu) {
7997           LoadPV(x, y);
7998           return 2; // flag front-end to grab mouse events
7999         }
8000         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8001            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8002       case EditGame:
8003       noZip:
8004         if (xSqr < 0 || ySqr < 0) return -1;
8005         if (!appData.dropMenu || appData.testLegality &&
8006             gameInfo.variant != VariantBughouse &&
8007             gameInfo.variant != VariantCrazyhouse) return -1;
8008         whichMenu = 1; // drop menu
8009         break;
8010       default:
8011         return -1;
8012     }
8013
8014     if (((*fromX = xSqr) < 0) ||
8015         ((*fromY = ySqr) < 0)) {
8016         *fromX = *fromY = -1;
8017         return -1;
8018     }
8019     if (flipView)
8020       *fromX = BOARD_WIDTH - 1 - *fromX;
8021     else
8022       *fromY = BOARD_HEIGHT - 1 - *fromY;
8023
8024     return whichMenu;
8025 }
8026
8027 void
8028 Wheel (int dir, int x, int y)
8029 {
8030     if(gameMode == EditPosition) {
8031         int xSqr = EventToSquare(x, BOARD_WIDTH);
8032         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8033         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8034         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8035         do {
8036             boards[currentMove][ySqr][xSqr] += dir;
8037             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8038             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8039         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8040         DrawPosition(FALSE, boards[currentMove]);
8041     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8042 }
8043
8044 void
8045 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8046 {
8047 //    char * hint = lastHint;
8048     FrontEndProgramStats stats;
8049
8050     stats.which = cps == &first ? 0 : 1;
8051     stats.depth = cpstats->depth;
8052     stats.nodes = cpstats->nodes;
8053     stats.score = cpstats->score;
8054     stats.time = cpstats->time;
8055     stats.pv = cpstats->movelist;
8056     stats.hint = lastHint;
8057     stats.an_move_index = 0;
8058     stats.an_move_count = 0;
8059
8060     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8061         stats.hint = cpstats->move_name;
8062         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8063         stats.an_move_count = cpstats->nr_moves;
8064     }
8065
8066     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
8067
8068     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8069         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8070
8071     SetProgramStats( &stats );
8072 }
8073
8074 void
8075 ClearEngineOutputPane (int which)
8076 {
8077     static FrontEndProgramStats dummyStats;
8078     dummyStats.which = which;
8079     dummyStats.pv = "#";
8080     SetProgramStats( &dummyStats );
8081 }
8082
8083 #define MAXPLAYERS 500
8084
8085 char *
8086 TourneyStandings (int display)
8087 {
8088     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8089     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8090     char result, *p, *names[MAXPLAYERS];
8091
8092     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8093         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8094     names[0] = p = strdup(appData.participants);
8095     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8096
8097     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8098
8099     while(result = appData.results[nr]) {
8100         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8101         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8102         wScore = bScore = 0;
8103         switch(result) {
8104           case '+': wScore = 2; break;
8105           case '-': bScore = 2; break;
8106           case '=': wScore = bScore = 1; break;
8107           case ' ':
8108           case '*': return strdup("busy"); // tourney not finished
8109         }
8110         score[w] += wScore;
8111         score[b] += bScore;
8112         games[w]++;
8113         games[b]++;
8114         nr++;
8115     }
8116     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8117     for(w=0; w<nPlayers; w++) {
8118         bScore = -1;
8119         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8120         ranking[w] = b; points[w] = bScore; score[b] = -2;
8121     }
8122     p = malloc(nPlayers*34+1);
8123     for(w=0; w<nPlayers && w<display; w++)
8124         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8125     free(names[0]);
8126     return p;
8127 }
8128
8129 void
8130 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8131 {       // count all piece types
8132         int p, f, r;
8133         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8134         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8135         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8136                 p = board[r][f];
8137                 pCnt[p]++;
8138                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8139                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8140                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8141                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8142                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8143                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8144         }
8145 }
8146
8147 int
8148 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8149 {
8150         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8151         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8152
8153         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8154         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8155         if(myPawns == 2 && nMine == 3) // KPP
8156             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8157         if(myPawns == 1 && nMine == 2) // KP
8158             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8159         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8160             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8161         if(myPawns) return FALSE;
8162         if(pCnt[WhiteRook+side])
8163             return pCnt[BlackRook-side] ||
8164                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8165                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8166                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8167         if(pCnt[WhiteCannon+side]) {
8168             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8169             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8170         }
8171         if(pCnt[WhiteKnight+side])
8172             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8173         return FALSE;
8174 }
8175
8176 int
8177 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8178 {
8179         VariantClass v = gameInfo.variant;
8180
8181         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8182         if(v == VariantShatranj) return TRUE; // always winnable through baring
8183         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8184         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8185
8186         if(v == VariantXiangqi) {
8187                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8188
8189                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8190                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8191                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8192                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8193                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8194                 if(stale) // we have at least one last-rank P plus perhaps C
8195                     return majors // KPKX
8196                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8197                 else // KCA*E*
8198                     return pCnt[WhiteFerz+side] // KCAK
8199                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8200                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8201                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8202
8203         } else if(v == VariantKnightmate) {
8204                 if(nMine == 1) return FALSE;
8205                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8206         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8207                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8208
8209                 if(nMine == 1) return FALSE; // bare King
8210                 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
8211                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8212                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8213                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8214                 if(pCnt[WhiteKnight+side])
8215                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8216                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8217                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8218                 if(nBishops)
8219                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8220                 if(pCnt[WhiteAlfil+side])
8221                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8222                 if(pCnt[WhiteWazir+side])
8223                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8224         }
8225
8226         return TRUE;
8227 }
8228
8229 int
8230 CompareWithRights (Board b1, Board b2)
8231 {
8232     int rights = 0;
8233     if(!CompareBoards(b1, b2)) return FALSE;
8234     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8235     /* compare castling rights */
8236     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8237            rights++; /* King lost rights, while rook still had them */
8238     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8239         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8240            rights++; /* but at least one rook lost them */
8241     }
8242     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8243            rights++;
8244     if( b1[CASTLING][5] != NoRights ) {
8245         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8246            rights++;
8247     }
8248     return rights == 0;
8249 }
8250
8251 int
8252 Adjudicate (ChessProgramState *cps)
8253 {       // [HGM] some adjudications useful with buggy engines
8254         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8255         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8256         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8257         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8258         int k, drop, count = 0; static int bare = 1;
8259         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8260         Boolean canAdjudicate = !appData.icsActive;
8261
8262         // most tests only when we understand the game, i.e. legality-checking on
8263             if( appData.testLegality )
8264             {   /* [HGM] Some more adjudications for obstinate engines */
8265                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8266                 static int moveCount = 6;
8267                 ChessMove result;
8268                 char *reason = NULL;
8269
8270                 /* Count what is on board. */
8271                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8272
8273                 /* Some material-based adjudications that have to be made before stalemate test */
8274                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8275                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8276                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8277                      if(canAdjudicate && appData.checkMates) {
8278                          if(engineOpponent)
8279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8280                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8281                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8282                          return 1;
8283                      }
8284                 }
8285
8286                 /* Bare King in Shatranj (loses) or Losers (wins) */
8287                 if( nrW == 1 || nrB == 1) {
8288                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8289                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8290                      if(canAdjudicate && appData.checkMates) {
8291                          if(engineOpponent)
8292                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8293                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8294                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8295                          return 1;
8296                      }
8297                   } else
8298                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8299                   {    /* bare King */
8300                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8301                         if(canAdjudicate && appData.checkMates) {
8302                             /* but only adjudicate if adjudication enabled */
8303                             if(engineOpponent)
8304                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8305                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8306                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8307                             return 1;
8308                         }
8309                   }
8310                 } else bare = 1;
8311
8312
8313             // don't wait for engine to announce game end if we can judge ourselves
8314             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8315               case MT_CHECK:
8316                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8317                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8318                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8319                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8320                             checkCnt++;
8321                         if(checkCnt >= 2) {
8322                             reason = "Xboard adjudication: 3rd check";
8323                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8324                             break;
8325                         }
8326                     }
8327                 }
8328               case MT_NONE:
8329               default:
8330                 break;
8331               case MT_STEALMATE:
8332               case MT_STALEMATE:
8333               case MT_STAINMATE:
8334                 reason = "Xboard adjudication: Stalemate";
8335                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8336                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8337                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8338                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8339                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8340                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8341                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8342                                                                         EP_CHECKMATE : EP_WINS);
8343                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8344                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8345                 }
8346                 break;
8347               case MT_CHECKMATE:
8348                 reason = "Xboard adjudication: Checkmate";
8349                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8350                 if(gameInfo.variant == VariantShogi) {
8351                     if(forwardMostMove > backwardMostMove
8352                        && moveList[forwardMostMove-1][1] == '@'
8353                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8354                         reason = "XBoard adjudication: pawn-drop mate";
8355                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8356                     }
8357                 }
8358                 break;
8359             }
8360
8361                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8362                     case EP_STALEMATE:
8363                         result = GameIsDrawn; break;
8364                     case EP_CHECKMATE:
8365                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8366                     case EP_WINS:
8367                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8368                     default:
8369                         result = EndOfFile;
8370                 }
8371                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8372                     if(engineOpponent)
8373                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8374                     GameEnds( result, reason, GE_XBOARD );
8375                     return 1;
8376                 }
8377
8378                 /* Next absolutely insufficient mating material. */
8379                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8380                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8381                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8382
8383                      /* always flag draws, for judging claims */
8384                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8385
8386                      if(canAdjudicate && appData.materialDraws) {
8387                          /* but only adjudicate them if adjudication enabled */
8388                          if(engineOpponent) {
8389                            SendToProgram("force\n", engineOpponent); // suppress reply
8390                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8391                          }
8392                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8393                          return 1;
8394                      }
8395                 }
8396
8397                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8398                 if(gameInfo.variant == VariantXiangqi ?
8399                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8400                  : nrW + nrB == 4 &&
8401                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8402                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8403                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8404                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8405                    ) ) {
8406                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8407                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8408                           if(engineOpponent) {
8409                             SendToProgram("force\n", engineOpponent); // suppress reply
8410                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8411                           }
8412                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8413                           return 1;
8414                      }
8415                 } else moveCount = 6;
8416             }
8417
8418         // Repetition draws and 50-move rule can be applied independently of legality testing
8419
8420                 /* Check for rep-draws */
8421                 count = 0;
8422                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8423                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8424                 for(k = forwardMostMove-2;
8425                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8426                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8427                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8428                     k-=2)
8429                 {   int rights=0;
8430                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8431                         /* compare castling rights */
8432                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8433                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8434                                 rights++; /* King lost rights, while rook still had them */
8435                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8436                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8437                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8438                                    rights++; /* but at least one rook lost them */
8439                         }
8440                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8441                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8442                                 rights++;
8443                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8444                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8445                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8446                                    rights++;
8447                         }
8448                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8449                             && appData.drawRepeats > 1) {
8450                              /* adjudicate after user-specified nr of repeats */
8451                              int result = GameIsDrawn;
8452                              char *details = "XBoard adjudication: repetition draw";
8453                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8454                                 // [HGM] xiangqi: check for forbidden perpetuals
8455                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8456                                 for(m=forwardMostMove; m>k; m-=2) {
8457                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8458                                         ourPerpetual = 0; // the current mover did not always check
8459                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8460                                         hisPerpetual = 0; // the opponent did not always check
8461                                 }
8462                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8463                                                                         ourPerpetual, hisPerpetual);
8464                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8465                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8466                                     details = "Xboard adjudication: perpetual checking";
8467                                 } else
8468                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8469                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8470                                 } else
8471                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8472                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8473                                         result = BlackWins;
8474                                         details = "Xboard adjudication: repetition";
8475                                     }
8476                                 } else // it must be XQ
8477                                 // Now check for perpetual chases
8478                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8479                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8480                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8481                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8482                                         static char resdet[MSG_SIZ];
8483                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8484                                         details = resdet;
8485                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8486                                     } else
8487                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8488                                         break; // Abort repetition-checking loop.
8489                                 }
8490                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8491                              }
8492                              if(engineOpponent) {
8493                                SendToProgram("force\n", engineOpponent); // suppress reply
8494                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8495                              }
8496                              GameEnds( result, details, GE_XBOARD );
8497                              return 1;
8498                         }
8499                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8500                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8501                     }
8502                 }
8503
8504                 /* Now we test for 50-move draws. Determine ply count */
8505                 count = forwardMostMove;
8506                 /* look for last irreversble move */
8507                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8508                     count--;
8509                 /* if we hit starting position, add initial plies */
8510                 if( count == backwardMostMove )
8511                     count -= initialRulePlies;
8512                 count = forwardMostMove - count;
8513                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8514                         // adjust reversible move counter for checks in Xiangqi
8515                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8516                         if(i < backwardMostMove) i = backwardMostMove;
8517                         while(i <= forwardMostMove) {
8518                                 lastCheck = inCheck; // check evasion does not count
8519                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8520                                 if(inCheck || lastCheck) count--; // check does not count
8521                                 i++;
8522                         }
8523                 }
8524                 if( count >= 100)
8525                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8526                          /* this is used to judge if draw claims are legal */
8527                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8528                          if(engineOpponent) {
8529                            SendToProgram("force\n", engineOpponent); // suppress reply
8530                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8531                          }
8532                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8533                          return 1;
8534                 }
8535
8536                 /* if draw offer is pending, treat it as a draw claim
8537                  * when draw condition present, to allow engines a way to
8538                  * claim draws before making their move to avoid a race
8539                  * condition occurring after their move
8540                  */
8541                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8542                          char *p = NULL;
8543                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8544                              p = "Draw claim: 50-move rule";
8545                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8546                              p = "Draw claim: 3-fold repetition";
8547                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8548                              p = "Draw claim: insufficient mating material";
8549                          if( p != NULL && canAdjudicate) {
8550                              if(engineOpponent) {
8551                                SendToProgram("force\n", engineOpponent); // suppress reply
8552                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8553                              }
8554                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8555                              return 1;
8556                          }
8557                 }
8558
8559                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8560                     if(engineOpponent) {
8561                       SendToProgram("force\n", engineOpponent); // suppress reply
8562                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8563                     }
8564                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8565                     return 1;
8566                 }
8567         return 0;
8568 }
8569
8570 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8571 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8572 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8573
8574 static int
8575 BitbaseProbe ()
8576 {
8577     int pieces[10], squares[10], cnt=0, r, f, res;
8578     static int loaded;
8579     static PPROBE_EGBB probeBB;
8580     if(!appData.testLegality) return 10;
8581     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8582     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8583     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8584     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8585         ChessSquare piece = boards[forwardMostMove][r][f];
8586         int black = (piece >= BlackPawn);
8587         int type = piece - black*BlackPawn;
8588         if(piece == EmptySquare) continue;
8589         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8590         if(type == WhiteKing) type = WhiteQueen + 1;
8591         type = egbbCode[type];
8592         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8593         pieces[cnt] = type + black*6;
8594         if(++cnt > 5) return 11;
8595     }
8596     pieces[cnt] = squares[cnt] = 0;
8597     // probe EGBB
8598     if(loaded == 2) return 13; // loading failed before
8599     if(loaded == 0) {
8600         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8601         HMODULE lib;
8602         PLOAD_EGBB loadBB;
8603         loaded = 2; // prepare for failure
8604         if(!path) return 13; // no egbb installed
8605         strncpy(buf, path + 8, MSG_SIZ);
8606         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8607         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8608         lib = LoadLibrary(buf);
8609         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8610         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8611         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8612         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8613         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8614         loaded = 1; // success!
8615     }
8616     res = probeBB(forwardMostMove & 1, pieces, squares);
8617     return res > 0 ? 1 : res < 0 ? -1 : 0;
8618 }
8619
8620 char *
8621 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8622 {   // [HGM] book: this routine intercepts moves to simulate book replies
8623     char *bookHit = NULL;
8624
8625     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8626         char buf[MSG_SIZ];
8627         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8628         SendToProgram(buf, cps);
8629     }
8630     //first determine if the incoming move brings opponent into his book
8631     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8632         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8633     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8634     if(bookHit != NULL && !cps->bookSuspend) {
8635         // make sure opponent is not going to reply after receiving move to book position
8636         SendToProgram("force\n", cps);
8637         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8638     }
8639     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8640     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8641     // now arrange restart after book miss
8642     if(bookHit) {
8643         // after a book hit we never send 'go', and the code after the call to this routine
8644         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8645         char buf[MSG_SIZ], *move = bookHit;
8646         if(cps->useSAN) {
8647             int fromX, fromY, toX, toY;
8648             char promoChar;
8649             ChessMove moveType;
8650             move = buf + 30;
8651             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8652                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8653                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8654                                     PosFlags(forwardMostMove),
8655                                     fromY, fromX, toY, toX, promoChar, move);
8656             } else {
8657                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8658                 bookHit = NULL;
8659             }
8660         }
8661         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8662         SendToProgram(buf, cps);
8663         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8664     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8665         SendToProgram("go\n", cps);
8666         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8667     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8668         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8669             SendToProgram("go\n", cps);
8670         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8671     }
8672     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8673 }
8674
8675 int
8676 LoadError (char *errmess, ChessProgramState *cps)
8677 {   // unloads engine and switches back to -ncp mode if it was first
8678     if(cps->initDone) return FALSE;
8679     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8680     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8681     cps->pr = NoProc;
8682     if(cps == &first) {
8683         appData.noChessProgram = TRUE;
8684         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8685         gameMode = BeginningOfGame; ModeHighlight();
8686         SetNCPMode();
8687     }
8688     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8689     DisplayMessage("", ""); // erase waiting message
8690     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8691     return TRUE;
8692 }
8693
8694 char *savedMessage;
8695 ChessProgramState *savedState;
8696 void
8697 DeferredBookMove (void)
8698 {
8699         if(savedState->lastPing != savedState->lastPong)
8700                     ScheduleDelayedEvent(DeferredBookMove, 10);
8701         else
8702         HandleMachineMove(savedMessage, savedState);
8703 }
8704
8705 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8706 static ChessProgramState *stalledEngine;
8707 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8708
8709 void
8710 HandleMachineMove (char *message, ChessProgramState *cps)
8711 {
8712     static char firstLeg[20], legs;
8713     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8714     char realname[MSG_SIZ];
8715     int fromX, fromY, toX, toY;
8716     ChessMove moveType;
8717     char promoChar, roar;
8718     char *p, *pv=buf1;
8719     int oldError;
8720     char *bookHit;
8721
8722     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8723         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8724         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8725             DisplayError(_("Invalid pairing from pairing engine"), 0);
8726             return;
8727         }
8728         pairingReceived = 1;
8729         NextMatchGame();
8730         return; // Skim the pairing messages here.
8731     }
8732
8733     oldError = cps->userError; cps->userError = 0;
8734
8735 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8736     /*
8737      * Kludge to ignore BEL characters
8738      */
8739     while (*message == '\007') message++;
8740
8741     /*
8742      * [HGM] engine debug message: ignore lines starting with '#' character
8743      */
8744     if(cps->debug && *message == '#') return;
8745
8746     /*
8747      * Look for book output
8748      */
8749     if (cps == &first && bookRequested) {
8750         if (message[0] == '\t' || message[0] == ' ') {
8751             /* Part of the book output is here; append it */
8752             strcat(bookOutput, message);
8753             strcat(bookOutput, "  \n");
8754             return;
8755         } else if (bookOutput[0] != NULLCHAR) {
8756             /* All of book output has arrived; display it */
8757             char *p = bookOutput;
8758             while (*p != NULLCHAR) {
8759                 if (*p == '\t') *p = ' ';
8760                 p++;
8761             }
8762             DisplayInformation(bookOutput);
8763             bookRequested = FALSE;
8764             /* Fall through to parse the current output */
8765         }
8766     }
8767
8768     /*
8769      * Look for machine move.
8770      */
8771     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8772         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8773     {
8774         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8775             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8776             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8777             stalledEngine = cps;
8778             if(appData.ponderNextMove) { // bring opponent out of ponder
8779                 if(gameMode == TwoMachinesPlay) {
8780                     if(cps->other->pause)
8781                         PauseEngine(cps->other);
8782                     else
8783                         SendToProgram("easy\n", cps->other);
8784                 }
8785             }
8786             StopClocks();
8787             return;
8788         }
8789
8790       if(cps->usePing) {
8791
8792         /* This method is only useful on engines that support ping */
8793         if(abortEngineThink) {
8794             if (appData.debugMode) {
8795                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8796             }
8797             SendToProgram("undo\n", cps);
8798             return;
8799         }
8800
8801         if (cps->lastPing != cps->lastPong) {
8802             /* Extra move from before last new; ignore */
8803             if (appData.debugMode) {
8804                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8805             }
8806           return;
8807         }
8808
8809       } else {
8810
8811         int machineWhite = FALSE;
8812
8813         switch (gameMode) {
8814           case BeginningOfGame:
8815             /* Extra move from before last reset; ignore */
8816             if (appData.debugMode) {
8817                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8818             }
8819             return;
8820
8821           case EndOfGame:
8822           case IcsIdle:
8823           default:
8824             /* Extra move after we tried to stop.  The mode test is
8825                not a reliable way of detecting this problem, but it's
8826                the best we can do on engines that don't support ping.
8827             */
8828             if (appData.debugMode) {
8829                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8830                         cps->which, gameMode);
8831             }
8832             SendToProgram("undo\n", cps);
8833             return;
8834
8835           case MachinePlaysWhite:
8836           case IcsPlayingWhite:
8837             machineWhite = TRUE;
8838             break;
8839
8840           case MachinePlaysBlack:
8841           case IcsPlayingBlack:
8842             machineWhite = FALSE;
8843             break;
8844
8845           case TwoMachinesPlay:
8846             machineWhite = (cps->twoMachinesColor[0] == 'w');
8847             break;
8848         }
8849         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8850             if (appData.debugMode) {
8851                 fprintf(debugFP,
8852                         "Ignoring move out of turn by %s, gameMode %d"
8853                         ", forwardMost %d\n",
8854                         cps->which, gameMode, forwardMostMove);
8855             }
8856             return;
8857         }
8858       }
8859
8860         if(cps->alphaRank) AlphaRank(machineMove, 4);
8861
8862         // [HGM] lion: (some very limited) support for Alien protocol
8863         killX = killY = kill2X = kill2Y = -1;
8864         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8865             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8866             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8867             return;
8868         }
8869         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8870             char *q = strchr(p+1, ',');            // second comma?
8871             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8872             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8873             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8874         }
8875         if(firstLeg[0]) { // there was a previous leg;
8876             // only support case where same piece makes two step
8877             char buf[20], *p = machineMove+1, *q = buf+1, f;
8878             safeStrCpy(buf, machineMove, 20);
8879             while(isdigit(*q)) q++; // find start of to-square
8880             safeStrCpy(machineMove, firstLeg, 20);
8881             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8882             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8883             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8884             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8885             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8886             firstLeg[0] = NULLCHAR; legs = 0;
8887         }
8888
8889         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8890                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8891             /* Machine move could not be parsed; ignore it. */
8892           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8893                     machineMove, _(cps->which));
8894             DisplayMoveError(buf1);
8895             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8896                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8897             if (gameMode == TwoMachinesPlay) {
8898               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8899                        buf1, GE_XBOARD);
8900             }
8901             return;
8902         }
8903
8904         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8905         /* So we have to redo legality test with true e.p. status here,  */
8906         /* to make sure an illegal e.p. capture does not slip through,   */
8907         /* to cause a forfeit on a justified illegal-move complaint      */
8908         /* of the opponent.                                              */
8909         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8910            ChessMove moveType;
8911            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8912                              fromY, fromX, toY, toX, promoChar);
8913             if(moveType == IllegalMove) {
8914               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8915                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8916                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8917                            buf1, GE_XBOARD);
8918                 return;
8919            } else if(!appData.fischerCastling)
8920            /* [HGM] Kludge to handle engines that send FRC-style castling
8921               when they shouldn't (like TSCP-Gothic) */
8922            switch(moveType) {
8923              case WhiteASideCastleFR:
8924              case BlackASideCastleFR:
8925                toX+=2;
8926                currentMoveString[2]++;
8927                break;
8928              case WhiteHSideCastleFR:
8929              case BlackHSideCastleFR:
8930                toX--;
8931                currentMoveString[2]--;
8932                break;
8933              default: ; // nothing to do, but suppresses warning of pedantic compilers
8934            }
8935         }
8936         hintRequested = FALSE;
8937         lastHint[0] = NULLCHAR;
8938         bookRequested = FALSE;
8939         /* Program may be pondering now */
8940         cps->maybeThinking = TRUE;
8941         if (cps->sendTime == 2) cps->sendTime = 1;
8942         if (cps->offeredDraw) cps->offeredDraw--;
8943
8944         /* [AS] Save move info*/
8945         pvInfoList[ forwardMostMove ].score = programStats.score;
8946         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8947         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8948
8949         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8950
8951         /* Test suites abort the 'game' after one move */
8952         if(*appData.finger) {
8953            static FILE *f;
8954            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8955            if(!f) f = fopen(appData.finger, "w");
8956            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8957            else { DisplayFatalError("Bad output file", errno, 0); return; }
8958            free(fen);
8959            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8960         }
8961         if(appData.epd) {
8962            if(solvingTime >= 0) {
8963               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8964               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8965            } else {
8966               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8967               if(solvingTime == -2) second.matchWins++;
8968            }
8969            OutputKibitz(2, buf1);
8970            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8971         }
8972
8973         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8974         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8975             int count = 0;
8976
8977             while( count < adjudicateLossPlies ) {
8978                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8979
8980                 if( count & 1 ) {
8981                     score = -score; /* Flip score for winning side */
8982                 }
8983
8984                 if( score > appData.adjudicateLossThreshold ) {
8985                     break;
8986                 }
8987
8988                 count++;
8989             }
8990
8991             if( count >= adjudicateLossPlies ) {
8992                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8993
8994                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8995                     "Xboard adjudication",
8996                     GE_XBOARD );
8997
8998                 return;
8999             }
9000         }
9001
9002         if(Adjudicate(cps)) {
9003             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9004             return; // [HGM] adjudicate: for all automatic game ends
9005         }
9006
9007 #if ZIPPY
9008         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9009             first.initDone) {
9010           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9011                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9012                 SendToICS("draw ");
9013                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9014           }
9015           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9016           ics_user_moved = 1;
9017           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9018                 char buf[3*MSG_SIZ];
9019
9020                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9021                         programStats.score / 100.,
9022                         programStats.depth,
9023                         programStats.time / 100.,
9024                         (unsigned int)programStats.nodes,
9025                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9026                         programStats.movelist);
9027                 SendToICS(buf);
9028           }
9029         }
9030 #endif
9031
9032         /* [AS] Clear stats for next move */
9033         ClearProgramStats();
9034         thinkOutput[0] = NULLCHAR;
9035         hiddenThinkOutputState = 0;
9036
9037         bookHit = NULL;
9038         if (gameMode == TwoMachinesPlay) {
9039             /* [HGM] relaying draw offers moved to after reception of move */
9040             /* and interpreting offer as claim if it brings draw condition */
9041             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9042                 SendToProgram("draw\n", cps->other);
9043             }
9044             if (cps->other->sendTime) {
9045                 SendTimeRemaining(cps->other,
9046                                   cps->other->twoMachinesColor[0] == 'w');
9047             }
9048             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9049             if (firstMove && !bookHit) {
9050                 firstMove = FALSE;
9051                 if (cps->other->useColors) {
9052                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9053                 }
9054                 SendToProgram("go\n", cps->other);
9055             }
9056             cps->other->maybeThinking = TRUE;
9057         }
9058
9059         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9060
9061         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9062
9063         if (!pausing && appData.ringBellAfterMoves) {
9064             if(!roar) RingBell();
9065         }
9066
9067         /*
9068          * Reenable menu items that were disabled while
9069          * machine was thinking
9070          */
9071         if (gameMode != TwoMachinesPlay)
9072             SetUserThinkingEnables();
9073
9074         // [HGM] book: after book hit opponent has received move and is now in force mode
9075         // force the book reply into it, and then fake that it outputted this move by jumping
9076         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9077         if(bookHit) {
9078                 static char bookMove[MSG_SIZ]; // a bit generous?
9079
9080                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9081                 strcat(bookMove, bookHit);
9082                 message = bookMove;
9083                 cps = cps->other;
9084                 programStats.nodes = programStats.depth = programStats.time =
9085                 programStats.score = programStats.got_only_move = 0;
9086                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9087
9088                 if(cps->lastPing != cps->lastPong) {
9089                     savedMessage = message; // args for deferred call
9090                     savedState = cps;
9091                     ScheduleDelayedEvent(DeferredBookMove, 10);
9092                     return;
9093                 }
9094                 goto FakeBookMove;
9095         }
9096
9097         return;
9098     }
9099
9100     /* Set special modes for chess engines.  Later something general
9101      *  could be added here; for now there is just one kludge feature,
9102      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9103      *  when "xboard" is given as an interactive command.
9104      */
9105     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9106         cps->useSigint = FALSE;
9107         cps->useSigterm = FALSE;
9108     }
9109     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9110       ParseFeatures(message+8, cps);
9111       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9112     }
9113
9114     if (!strncmp(message, "setup ", 6) && 
9115         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9116           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9117                                         ) { // [HGM] allow first engine to define opening position
9118       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9119       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9120       *buf = NULLCHAR;
9121       if(sscanf(message, "setup (%s", buf) == 1) {
9122         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9123         ASSIGN(appData.pieceToCharTable, buf);
9124       }
9125       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9126       if(dummy >= 3) {
9127         while(message[s] && message[s++] != ' ');
9128         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9129            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9130             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9131             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9132           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9133           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9134           startedFromSetupPosition = FALSE;
9135         }
9136       }
9137       if(startedFromSetupPosition) return;
9138       ParseFEN(boards[0], &dummy, message+s, FALSE);
9139       DrawPosition(TRUE, boards[0]);
9140       CopyBoard(initialPosition, boards[0]);
9141       startedFromSetupPosition = TRUE;
9142       return;
9143     }
9144     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9145       ChessSquare piece = WhitePawn;
9146       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9147       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9148       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9149       piece += CharToPiece(ID & 255) - WhitePawn;
9150       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9151       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9152       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9153       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9154       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9155       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9156                                                && gameInfo.variant != VariantGreat
9157                                                && gameInfo.variant != VariantFairy    ) return;
9158       if(piece < EmptySquare) {
9159         pieceDefs = TRUE;
9160         ASSIGN(pieceDesc[piece], buf1);
9161         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9162       }
9163       return;
9164     }
9165     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9166       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9167       Sweep(0);
9168       return;
9169     }
9170     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9171      * want this, I was asked to put it in, and obliged.
9172      */
9173     if (!strncmp(message, "setboard ", 9)) {
9174         Board initial_position;
9175
9176         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9177
9178         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9179             DisplayError(_("Bad FEN received from engine"), 0);
9180             return ;
9181         } else {
9182            Reset(TRUE, FALSE);
9183            CopyBoard(boards[0], initial_position);
9184            initialRulePlies = FENrulePlies;
9185            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9186            else gameMode = MachinePlaysBlack;
9187            DrawPosition(FALSE, boards[currentMove]);
9188         }
9189         return;
9190     }
9191
9192     /*
9193      * Look for communication commands
9194      */
9195     if (!strncmp(message, "telluser ", 9)) {
9196         if(message[9] == '\\' && message[10] == '\\')
9197             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9198         PlayTellSound();
9199         DisplayNote(message + 9);
9200         return;
9201     }
9202     if (!strncmp(message, "tellusererror ", 14)) {
9203         cps->userError = 1;
9204         if(message[14] == '\\' && message[15] == '\\')
9205             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9206         PlayTellSound();
9207         DisplayError(message + 14, 0);
9208         return;
9209     }
9210     if (!strncmp(message, "tellopponent ", 13)) {
9211       if (appData.icsActive) {
9212         if (loggedOn) {
9213           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9214           SendToICS(buf1);
9215         }
9216       } else {
9217         DisplayNote(message + 13);
9218       }
9219       return;
9220     }
9221     if (!strncmp(message, "tellothers ", 11)) {
9222       if (appData.icsActive) {
9223         if (loggedOn) {
9224           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9225           SendToICS(buf1);
9226         }
9227       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9228       return;
9229     }
9230     if (!strncmp(message, "tellall ", 8)) {
9231       if (appData.icsActive) {
9232         if (loggedOn) {
9233           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9234           SendToICS(buf1);
9235         }
9236       } else {
9237         DisplayNote(message + 8);
9238       }
9239       return;
9240     }
9241     if (strncmp(message, "warning", 7) == 0) {
9242         /* Undocumented feature, use tellusererror in new code */
9243         DisplayError(message, 0);
9244         return;
9245     }
9246     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9247         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9248         strcat(realname, " query");
9249         AskQuestion(realname, buf2, buf1, cps->pr);
9250         return;
9251     }
9252     /* Commands from the engine directly to ICS.  We don't allow these to be
9253      *  sent until we are logged on. Crafty kibitzes have been known to
9254      *  interfere with the login process.
9255      */
9256     if (loggedOn) {
9257         if (!strncmp(message, "tellics ", 8)) {
9258             SendToICS(message + 8);
9259             SendToICS("\n");
9260             return;
9261         }
9262         if (!strncmp(message, "tellicsnoalias ", 15)) {
9263             SendToICS(ics_prefix);
9264             SendToICS(message + 15);
9265             SendToICS("\n");
9266             return;
9267         }
9268         /* The following are for backward compatibility only */
9269         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9270             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9271             SendToICS(ics_prefix);
9272             SendToICS(message);
9273             SendToICS("\n");
9274             return;
9275         }
9276     }
9277     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9278         if(initPing == cps->lastPong) {
9279             if(gameInfo.variant == VariantUnknown) {
9280                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9281                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9282                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9283             }
9284             initPing = -1;
9285         }
9286         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9287             abortEngineThink = FALSE;
9288             DisplayMessage("", "");
9289             ThawUI();
9290         }
9291         return;
9292     }
9293     if(!strncmp(message, "highlight ", 10)) {
9294         if(appData.testLegality && !*engineVariant && appData.markers) return;
9295         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9296         return;
9297     }
9298     if(!strncmp(message, "click ", 6)) {
9299         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9300         if(appData.testLegality || !appData.oneClick) return;
9301         sscanf(message+6, "%c%d%c", &f, &y, &c);
9302         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9303         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9304         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9305         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9306         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9307         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9308             LeftClick(Release, lastLeftX, lastLeftY);
9309         controlKey  = (c == ',');
9310         LeftClick(Press, x, y);
9311         LeftClick(Release, x, y);
9312         first.highlight = f;
9313         return;
9314     }
9315     /*
9316      * If the move is illegal, cancel it and redraw the board.
9317      * Also deal with other error cases.  Matching is rather loose
9318      * here to accommodate engines written before the spec.
9319      */
9320     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9321         strncmp(message, "Error", 5) == 0) {
9322         if (StrStr(message, "name") ||
9323             StrStr(message, "rating") || StrStr(message, "?") ||
9324             StrStr(message, "result") || StrStr(message, "board") ||
9325             StrStr(message, "bk") || StrStr(message, "computer") ||
9326             StrStr(message, "variant") || StrStr(message, "hint") ||
9327             StrStr(message, "random") || StrStr(message, "depth") ||
9328             StrStr(message, "accepted")) {
9329             return;
9330         }
9331         if (StrStr(message, "protover")) {
9332           /* Program is responding to input, so it's apparently done
9333              initializing, and this error message indicates it is
9334              protocol version 1.  So we don't need to wait any longer
9335              for it to initialize and send feature commands. */
9336           FeatureDone(cps, 1);
9337           cps->protocolVersion = 1;
9338           return;
9339         }
9340         cps->maybeThinking = FALSE;
9341
9342         if (StrStr(message, "draw")) {
9343             /* Program doesn't have "draw" command */
9344             cps->sendDrawOffers = 0;
9345             return;
9346         }
9347         if (cps->sendTime != 1 &&
9348             (StrStr(message, "time") || StrStr(message, "otim"))) {
9349           /* Program apparently doesn't have "time" or "otim" command */
9350           cps->sendTime = 0;
9351           return;
9352         }
9353         if (StrStr(message, "analyze")) {
9354             cps->analysisSupport = FALSE;
9355             cps->analyzing = FALSE;
9356 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9357             EditGameEvent(); // [HGM] try to preserve loaded game
9358             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9359             DisplayError(buf2, 0);
9360             return;
9361         }
9362         if (StrStr(message, "(no matching move)st")) {
9363           /* Special kludge for GNU Chess 4 only */
9364           cps->stKludge = TRUE;
9365           SendTimeControl(cps, movesPerSession, timeControl,
9366                           timeIncrement, appData.searchDepth,
9367                           searchTime);
9368           return;
9369         }
9370         if (StrStr(message, "(no matching move)sd")) {
9371           /* Special kludge for GNU Chess 4 only */
9372           cps->sdKludge = TRUE;
9373           SendTimeControl(cps, movesPerSession, timeControl,
9374                           timeIncrement, appData.searchDepth,
9375                           searchTime);
9376           return;
9377         }
9378         if (!StrStr(message, "llegal")) {
9379             return;
9380         }
9381         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9382             gameMode == IcsIdle) return;
9383         if (forwardMostMove <= backwardMostMove) return;
9384         if (pausing) PauseEvent();
9385       if(appData.forceIllegal) {
9386             // [HGM] illegal: machine refused move; force position after move into it
9387           SendToProgram("force\n", cps);
9388           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9389                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9390                 // when black is to move, while there might be nothing on a2 or black
9391                 // might already have the move. So send the board as if white has the move.
9392                 // But first we must change the stm of the engine, as it refused the last move
9393                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9394                 if(WhiteOnMove(forwardMostMove)) {
9395                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9396                     SendBoard(cps, forwardMostMove); // kludgeless board
9397                 } else {
9398                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9399                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9400                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9401                 }
9402           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9403             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9404                  gameMode == TwoMachinesPlay)
9405               SendToProgram("go\n", cps);
9406             return;
9407       } else
9408         if (gameMode == PlayFromGameFile) {
9409             /* Stop reading this game file */
9410             gameMode = EditGame;
9411             ModeHighlight();
9412         }
9413         /* [HGM] illegal-move claim should forfeit game when Xboard */
9414         /* only passes fully legal moves                            */
9415         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9416             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9417                                 "False illegal-move claim", GE_XBOARD );
9418             return; // do not take back move we tested as valid
9419         }
9420         currentMove = forwardMostMove-1;
9421         DisplayMove(currentMove-1); /* before DisplayMoveError */
9422         SwitchClocks(forwardMostMove-1); // [HGM] race
9423         DisplayBothClocks();
9424         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9425                 parseList[currentMove], _(cps->which));
9426         DisplayMoveError(buf1);
9427         DrawPosition(FALSE, boards[currentMove]);
9428
9429         SetUserThinkingEnables();
9430         return;
9431     }
9432     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9433         /* Program has a broken "time" command that
9434            outputs a string not ending in newline.
9435            Don't use it. */
9436         cps->sendTime = 0;
9437     }
9438     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9439         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9440             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9441     }
9442
9443     /*
9444      * If chess program startup fails, exit with an error message.
9445      * Attempts to recover here are futile. [HGM] Well, we try anyway
9446      */
9447     if ((StrStr(message, "unknown host") != NULL)
9448         || (StrStr(message, "No remote directory") != NULL)
9449         || (StrStr(message, "not found") != NULL)
9450         || (StrStr(message, "No such file") != NULL)
9451         || (StrStr(message, "can't alloc") != NULL)
9452         || (StrStr(message, "Permission denied") != NULL)) {
9453
9454         cps->maybeThinking = FALSE;
9455         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9456                 _(cps->which), cps->program, cps->host, message);
9457         RemoveInputSource(cps->isr);
9458         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9459             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9460             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9461         }
9462         return;
9463     }
9464
9465     /*
9466      * Look for hint output
9467      */
9468     if (sscanf(message, "Hint: %s", buf1) == 1) {
9469         if (cps == &first && hintRequested) {
9470             hintRequested = FALSE;
9471             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9472                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9473                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9474                                     PosFlags(forwardMostMove),
9475                                     fromY, fromX, toY, toX, promoChar, buf1);
9476                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9477                 DisplayInformation(buf2);
9478             } else {
9479                 /* Hint move could not be parsed!? */
9480               snprintf(buf2, sizeof(buf2),
9481                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9482                         buf1, _(cps->which));
9483                 DisplayError(buf2, 0);
9484             }
9485         } else {
9486           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9487         }
9488         return;
9489     }
9490
9491     /*
9492      * Ignore other messages if game is not in progress
9493      */
9494     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9495         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9496
9497     /*
9498      * look for win, lose, draw, or draw offer
9499      */
9500     if (strncmp(message, "1-0", 3) == 0) {
9501         char *p, *q, *r = "";
9502         p = strchr(message, '{');
9503         if (p) {
9504             q = strchr(p, '}');
9505             if (q) {
9506                 *q = NULLCHAR;
9507                 r = p + 1;
9508             }
9509         }
9510         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9511         return;
9512     } else if (strncmp(message, "0-1", 3) == 0) {
9513         char *p, *q, *r = "";
9514         p = strchr(message, '{');
9515         if (p) {
9516             q = strchr(p, '}');
9517             if (q) {
9518                 *q = NULLCHAR;
9519                 r = p + 1;
9520             }
9521         }
9522         /* Kludge for Arasan 4.1 bug */
9523         if (strcmp(r, "Black resigns") == 0) {
9524             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9525             return;
9526         }
9527         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9528         return;
9529     } else if (strncmp(message, "1/2", 3) == 0) {
9530         char *p, *q, *r = "";
9531         p = strchr(message, '{');
9532         if (p) {
9533             q = strchr(p, '}');
9534             if (q) {
9535                 *q = NULLCHAR;
9536                 r = p + 1;
9537             }
9538         }
9539
9540         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9541         return;
9542
9543     } else if (strncmp(message, "White resign", 12) == 0) {
9544         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9545         return;
9546     } else if (strncmp(message, "Black resign", 12) == 0) {
9547         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9548         return;
9549     } else if (strncmp(message, "White matches", 13) == 0 ||
9550                strncmp(message, "Black matches", 13) == 0   ) {
9551         /* [HGM] ignore GNUShogi noises */
9552         return;
9553     } else if (strncmp(message, "White", 5) == 0 &&
9554                message[5] != '(' &&
9555                StrStr(message, "Black") == NULL) {
9556         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9557         return;
9558     } else if (strncmp(message, "Black", 5) == 0 &&
9559                message[5] != '(') {
9560         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9561         return;
9562     } else if (strcmp(message, "resign") == 0 ||
9563                strcmp(message, "computer resigns") == 0) {
9564         switch (gameMode) {
9565           case MachinePlaysBlack:
9566           case IcsPlayingBlack:
9567             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9568             break;
9569           case MachinePlaysWhite:
9570           case IcsPlayingWhite:
9571             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9572             break;
9573           case TwoMachinesPlay:
9574             if (cps->twoMachinesColor[0] == 'w')
9575               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9576             else
9577               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9578             break;
9579           default:
9580             /* can't happen */
9581             break;
9582         }
9583         return;
9584     } else if (strncmp(message, "opponent mates", 14) == 0) {
9585         switch (gameMode) {
9586           case MachinePlaysBlack:
9587           case IcsPlayingBlack:
9588             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9589             break;
9590           case MachinePlaysWhite:
9591           case IcsPlayingWhite:
9592             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9593             break;
9594           case TwoMachinesPlay:
9595             if (cps->twoMachinesColor[0] == 'w')
9596               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9597             else
9598               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9599             break;
9600           default:
9601             /* can't happen */
9602             break;
9603         }
9604         return;
9605     } else if (strncmp(message, "computer mates", 14) == 0) {
9606         switch (gameMode) {
9607           case MachinePlaysBlack:
9608           case IcsPlayingBlack:
9609             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9610             break;
9611           case MachinePlaysWhite:
9612           case IcsPlayingWhite:
9613             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9614             break;
9615           case TwoMachinesPlay:
9616             if (cps->twoMachinesColor[0] == 'w')
9617               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9618             else
9619               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9620             break;
9621           default:
9622             /* can't happen */
9623             break;
9624         }
9625         return;
9626     } else if (strncmp(message, "checkmate", 9) == 0) {
9627         if (WhiteOnMove(forwardMostMove)) {
9628             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9629         } else {
9630             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9631         }
9632         return;
9633     } else if (strstr(message, "Draw") != NULL ||
9634                strstr(message, "game is a draw") != NULL) {
9635         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9636         return;
9637     } else if (strstr(message, "offer") != NULL &&
9638                strstr(message, "draw") != NULL) {
9639 #if ZIPPY
9640         if (appData.zippyPlay && first.initDone) {
9641             /* Relay offer to ICS */
9642             SendToICS(ics_prefix);
9643             SendToICS("draw\n");
9644         }
9645 #endif
9646         cps->offeredDraw = 2; /* valid until this engine moves twice */
9647         if (gameMode == TwoMachinesPlay) {
9648             if (cps->other->offeredDraw) {
9649                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9650             /* [HGM] in two-machine mode we delay relaying draw offer      */
9651             /* until after we also have move, to see if it is really claim */
9652             }
9653         } else if (gameMode == MachinePlaysWhite ||
9654                    gameMode == MachinePlaysBlack) {
9655           if (userOfferedDraw) {
9656             DisplayInformation(_("Machine accepts your draw offer"));
9657             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9658           } else {
9659             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9660           }
9661         }
9662     }
9663
9664
9665     /*
9666      * Look for thinking output
9667      */
9668     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9669           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9670                                 ) {
9671         int plylev, mvleft, mvtot, curscore, time;
9672         char mvname[MOVE_LEN];
9673         u64 nodes; // [DM]
9674         char plyext;
9675         int ignore = FALSE;
9676         int prefixHint = FALSE;
9677         mvname[0] = NULLCHAR;
9678
9679         switch (gameMode) {
9680           case MachinePlaysBlack:
9681           case IcsPlayingBlack:
9682             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9683             break;
9684           case MachinePlaysWhite:
9685           case IcsPlayingWhite:
9686             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9687             break;
9688           case AnalyzeMode:
9689           case AnalyzeFile:
9690             break;
9691           case IcsObserving: /* [DM] icsEngineAnalyze */
9692             if (!appData.icsEngineAnalyze) ignore = TRUE;
9693             break;
9694           case TwoMachinesPlay:
9695             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9696                 ignore = TRUE;
9697             }
9698             break;
9699           default:
9700             ignore = TRUE;
9701             break;
9702         }
9703
9704         if (!ignore) {
9705             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9706             int solved = 0;
9707             buf1[0] = NULLCHAR;
9708             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9709                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9710                 char score_buf[MSG_SIZ];
9711
9712                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9713                     nodes += u64Const(0x100000000);
9714
9715                 if (plyext != ' ' && plyext != '\t') {
9716                     time *= 100;
9717                 }
9718
9719                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9720                 if( cps->scoreIsAbsolute &&
9721                     ( gameMode == MachinePlaysBlack ||
9722                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9723                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9724                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9725                      !WhiteOnMove(currentMove)
9726                     ) )
9727                 {
9728                     curscore = -curscore;
9729                 }
9730
9731                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9732
9733                 if(*bestMove) { // rememer time best EPD move was first found
9734                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9735                     ChessMove mt; char *p = bestMove;
9736                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9737                     solved = 0;
9738                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9739                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9740                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9741                             solved = 1;
9742                             break;
9743                         }
9744                         while(*p && *p != ' ') p++;
9745                         while(*p == ' ') p++;
9746                     }
9747                     if(!solved) solvingTime = -1;
9748                 }
9749                 if(*avoidMove && !solved) {
9750                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9751                     ChessMove mt; char *p = avoidMove, solved = 1;
9752                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9753                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9754                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9755                             solved = 0; solvingTime = -2;
9756                             break;
9757                         }
9758                         while(*p && *p != ' ') p++;
9759                         while(*p == ' ') p++;
9760                     }
9761                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9762                 }
9763
9764                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9765                         char buf[MSG_SIZ];
9766                         FILE *f;
9767                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9768                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9769                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9770                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9771                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9772                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9773                                 fclose(f);
9774                         }
9775                         else
9776                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9777                           DisplayError(_("failed writing PV"), 0);
9778                 }
9779
9780                 tempStats.depth = plylev;
9781                 tempStats.nodes = nodes;
9782                 tempStats.time = time;
9783                 tempStats.score = curscore;
9784                 tempStats.got_only_move = 0;
9785
9786                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9787                         int ticklen;
9788
9789                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9790                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9791                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9792                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9793                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9794                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9795                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9796                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9797                 }
9798
9799                 /* Buffer overflow protection */
9800                 if (pv[0] != NULLCHAR) {
9801                     if (strlen(pv) >= sizeof(tempStats.movelist)
9802                         && appData.debugMode) {
9803                         fprintf(debugFP,
9804                                 "PV is too long; using the first %u bytes.\n",
9805                                 (unsigned) sizeof(tempStats.movelist) - 1);
9806                     }
9807
9808                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9809                 } else {
9810                     sprintf(tempStats.movelist, " no PV\n");
9811                 }
9812
9813                 if (tempStats.seen_stat) {
9814                     tempStats.ok_to_send = 1;
9815                 }
9816
9817                 if (strchr(tempStats.movelist, '(') != NULL) {
9818                     tempStats.line_is_book = 1;
9819                     tempStats.nr_moves = 0;
9820                     tempStats.moves_left = 0;
9821                 } else {
9822                     tempStats.line_is_book = 0;
9823                 }
9824
9825                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9826                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9827
9828                 SendProgramStatsToFrontend( cps, &tempStats );
9829
9830                 /*
9831                     [AS] Protect the thinkOutput buffer from overflow... this
9832                     is only useful if buf1 hasn't overflowed first!
9833                 */
9834                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9835                 if(curscore >= MATE_SCORE) 
9836                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9837                 else if(curscore <= -MATE_SCORE) 
9838                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9839                 else
9840                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9841                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9842                          plylev,
9843                          (gameMode == TwoMachinesPlay ?
9844                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9845                          score_buf,
9846                          prefixHint ? lastHint : "",
9847                          prefixHint ? " " : "" );
9848
9849                 if( buf1[0] != NULLCHAR ) {
9850                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9851
9852                     if( strlen(pv) > max_len ) {
9853                         if( appData.debugMode) {
9854                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9855                         }
9856                         pv[max_len+1] = '\0';
9857                     }
9858
9859                     strcat( thinkOutput, pv);
9860                 }
9861
9862                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9863                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9864                     DisplayMove(currentMove - 1);
9865                 }
9866                 return;
9867
9868             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9869                 /* crafty (9.25+) says "(only move) <move>"
9870                  * if there is only 1 legal move
9871                  */
9872                 sscanf(p, "(only move) %s", buf1);
9873                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9874                 sprintf(programStats.movelist, "%s (only move)", buf1);
9875                 programStats.depth = 1;
9876                 programStats.nr_moves = 1;
9877                 programStats.moves_left = 1;
9878                 programStats.nodes = 1;
9879                 programStats.time = 1;
9880                 programStats.got_only_move = 1;
9881
9882                 /* Not really, but we also use this member to
9883                    mean "line isn't going to change" (Crafty
9884                    isn't searching, so stats won't change) */
9885                 programStats.line_is_book = 1;
9886
9887                 SendProgramStatsToFrontend( cps, &programStats );
9888
9889                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9890                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9891                     DisplayMove(currentMove - 1);
9892                 }
9893                 return;
9894             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9895                               &time, &nodes, &plylev, &mvleft,
9896                               &mvtot, mvname) >= 5) {
9897                 /* The stat01: line is from Crafty (9.29+) in response
9898                    to the "." command */
9899                 programStats.seen_stat = 1;
9900                 cps->maybeThinking = TRUE;
9901
9902                 if (programStats.got_only_move || !appData.periodicUpdates)
9903                   return;
9904
9905                 programStats.depth = plylev;
9906                 programStats.time = time;
9907                 programStats.nodes = nodes;
9908                 programStats.moves_left = mvleft;
9909                 programStats.nr_moves = mvtot;
9910                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9911                 programStats.ok_to_send = 1;
9912                 programStats.movelist[0] = '\0';
9913
9914                 SendProgramStatsToFrontend( cps, &programStats );
9915
9916                 return;
9917
9918             } else if (strncmp(message,"++",2) == 0) {
9919                 /* Crafty 9.29+ outputs this */
9920                 programStats.got_fail = 2;
9921                 return;
9922
9923             } else if (strncmp(message,"--",2) == 0) {
9924                 /* Crafty 9.29+ outputs this */
9925                 programStats.got_fail = 1;
9926                 return;
9927
9928             } else if (thinkOutput[0] != NULLCHAR &&
9929                        strncmp(message, "    ", 4) == 0) {
9930                 unsigned message_len;
9931
9932                 p = message;
9933                 while (*p && *p == ' ') p++;
9934
9935                 message_len = strlen( p );
9936
9937                 /* [AS] Avoid buffer overflow */
9938                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9939                     strcat(thinkOutput, " ");
9940                     strcat(thinkOutput, p);
9941                 }
9942
9943                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9944                     strcat(programStats.movelist, " ");
9945                     strcat(programStats.movelist, p);
9946                 }
9947
9948                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9949                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9950                     DisplayMove(currentMove - 1);
9951                 }
9952                 return;
9953             }
9954         }
9955         else {
9956             buf1[0] = NULLCHAR;
9957
9958             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9959                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9960             {
9961                 ChessProgramStats cpstats;
9962
9963                 if (plyext != ' ' && plyext != '\t') {
9964                     time *= 100;
9965                 }
9966
9967                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9968                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9969                     curscore = -curscore;
9970                 }
9971
9972                 cpstats.depth = plylev;
9973                 cpstats.nodes = nodes;
9974                 cpstats.time = time;
9975                 cpstats.score = curscore;
9976                 cpstats.got_only_move = 0;
9977                 cpstats.movelist[0] = '\0';
9978
9979                 if (buf1[0] != NULLCHAR) {
9980                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9981                 }
9982
9983                 cpstats.ok_to_send = 0;
9984                 cpstats.line_is_book = 0;
9985                 cpstats.nr_moves = 0;
9986                 cpstats.moves_left = 0;
9987
9988                 SendProgramStatsToFrontend( cps, &cpstats );
9989             }
9990         }
9991     }
9992 }
9993
9994
9995 /* Parse a game score from the character string "game", and
9996    record it as the history of the current game.  The game
9997    score is NOT assumed to start from the standard position.
9998    The display is not updated in any way.
9999    */
10000 void
10001 ParseGameHistory (char *game)
10002 {
10003     ChessMove moveType;
10004     int fromX, fromY, toX, toY, boardIndex;
10005     char promoChar;
10006     char *p, *q;
10007     char buf[MSG_SIZ];
10008
10009     if (appData.debugMode)
10010       fprintf(debugFP, "Parsing game history: %s\n", game);
10011
10012     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10013     gameInfo.site = StrSave(appData.icsHost);
10014     gameInfo.date = PGNDate();
10015     gameInfo.round = StrSave("-");
10016
10017     /* Parse out names of players */
10018     while (*game == ' ') game++;
10019     p = buf;
10020     while (*game != ' ') *p++ = *game++;
10021     *p = NULLCHAR;
10022     gameInfo.white = StrSave(buf);
10023     while (*game == ' ') game++;
10024     p = buf;
10025     while (*game != ' ' && *game != '\n') *p++ = *game++;
10026     *p = NULLCHAR;
10027     gameInfo.black = StrSave(buf);
10028
10029     /* Parse moves */
10030     boardIndex = blackPlaysFirst ? 1 : 0;
10031     yynewstr(game);
10032     for (;;) {
10033         yyboardindex = boardIndex;
10034         moveType = (ChessMove) Myylex();
10035         switch (moveType) {
10036           case IllegalMove:             /* maybe suicide chess, etc. */
10037   if (appData.debugMode) {
10038     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10039     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10040     setbuf(debugFP, NULL);
10041   }
10042           case WhitePromotion:
10043           case BlackPromotion:
10044           case WhiteNonPromotion:
10045           case BlackNonPromotion:
10046           case NormalMove:
10047           case FirstLeg:
10048           case WhiteCapturesEnPassant:
10049           case BlackCapturesEnPassant:
10050           case WhiteKingSideCastle:
10051           case WhiteQueenSideCastle:
10052           case BlackKingSideCastle:
10053           case BlackQueenSideCastle:
10054           case WhiteKingSideCastleWild:
10055           case WhiteQueenSideCastleWild:
10056           case BlackKingSideCastleWild:
10057           case BlackQueenSideCastleWild:
10058           /* PUSH Fabien */
10059           case WhiteHSideCastleFR:
10060           case WhiteASideCastleFR:
10061           case BlackHSideCastleFR:
10062           case BlackASideCastleFR:
10063           /* POP Fabien */
10064             fromX = currentMoveString[0] - AAA;
10065             fromY = currentMoveString[1] - ONE;
10066             toX = currentMoveString[2] - AAA;
10067             toY = currentMoveString[3] - ONE;
10068             promoChar = currentMoveString[4];
10069             break;
10070           case WhiteDrop:
10071           case BlackDrop:
10072             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10073             fromX = moveType == WhiteDrop ?
10074               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10075             (int) CharToPiece(ToLower(currentMoveString[0]));
10076             fromY = DROP_RANK;
10077             toX = currentMoveString[2] - AAA;
10078             toY = currentMoveString[3] - ONE;
10079             promoChar = NULLCHAR;
10080             break;
10081           case AmbiguousMove:
10082             /* bug? */
10083             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10084   if (appData.debugMode) {
10085     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10086     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10087     setbuf(debugFP, NULL);
10088   }
10089             DisplayError(buf, 0);
10090             return;
10091           case ImpossibleMove:
10092             /* bug? */
10093             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10094   if (appData.debugMode) {
10095     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10096     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10097     setbuf(debugFP, NULL);
10098   }
10099             DisplayError(buf, 0);
10100             return;
10101           case EndOfFile:
10102             if (boardIndex < backwardMostMove) {
10103                 /* Oops, gap.  How did that happen? */
10104                 DisplayError(_("Gap in move list"), 0);
10105                 return;
10106             }
10107             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10108             if (boardIndex > forwardMostMove) {
10109                 forwardMostMove = boardIndex;
10110             }
10111             return;
10112           case ElapsedTime:
10113             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10114                 strcat(parseList[boardIndex-1], " ");
10115                 strcat(parseList[boardIndex-1], yy_text);
10116             }
10117             continue;
10118           case Comment:
10119           case PGNTag:
10120           case NAG:
10121           default:
10122             /* ignore */
10123             continue;
10124           case WhiteWins:
10125           case BlackWins:
10126           case GameIsDrawn:
10127           case GameUnfinished:
10128             if (gameMode == IcsExamining) {
10129                 if (boardIndex < backwardMostMove) {
10130                     /* Oops, gap.  How did that happen? */
10131                     return;
10132                 }
10133                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10134                 return;
10135             }
10136             gameInfo.result = moveType;
10137             p = strchr(yy_text, '{');
10138             if (p == NULL) p = strchr(yy_text, '(');
10139             if (p == NULL) {
10140                 p = yy_text;
10141                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10142             } else {
10143                 q = strchr(p, *p == '{' ? '}' : ')');
10144                 if (q != NULL) *q = NULLCHAR;
10145                 p++;
10146             }
10147             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10148             gameInfo.resultDetails = StrSave(p);
10149             continue;
10150         }
10151         if (boardIndex >= forwardMostMove &&
10152             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10153             backwardMostMove = blackPlaysFirst ? 1 : 0;
10154             return;
10155         }
10156         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10157                                  fromY, fromX, toY, toX, promoChar,
10158                                  parseList[boardIndex]);
10159         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10160         /* currentMoveString is set as a side-effect of yylex */
10161         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10162         strcat(moveList[boardIndex], "\n");
10163         boardIndex++;
10164         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10165         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10166           case MT_NONE:
10167           case MT_STALEMATE:
10168           default:
10169             break;
10170           case MT_CHECK:
10171             if(!IS_SHOGI(gameInfo.variant))
10172                 strcat(parseList[boardIndex - 1], "+");
10173             break;
10174           case MT_CHECKMATE:
10175           case MT_STAINMATE:
10176             strcat(parseList[boardIndex - 1], "#");
10177             break;
10178         }
10179     }
10180 }
10181
10182
10183 /* Apply a move to the given board  */
10184 void
10185 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10186 {
10187   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10188   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10189
10190     /* [HGM] compute & store e.p. status and castling rights for new position */
10191     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10192
10193       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10194       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10195       board[EP_STATUS] = EP_NONE;
10196       board[EP_FILE] = board[EP_RANK] = 100;
10197
10198   if (fromY == DROP_RANK) {
10199         /* must be first */
10200         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10201             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10202             return;
10203         }
10204         piece = board[toY][toX] = (ChessSquare) fromX;
10205   } else {
10206 //      ChessSquare victim;
10207       int i;
10208
10209       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10210 //           victim = board[killY][killX],
10211            killed = board[killY][killX],
10212            board[killY][killX] = EmptySquare,
10213            board[EP_STATUS] = EP_CAPTURE;
10214            if( kill2X >= 0 && kill2Y >= 0)
10215              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10216       }
10217
10218       if( board[toY][toX] != EmptySquare ) {
10219            board[EP_STATUS] = EP_CAPTURE;
10220            if( (fromX != toX || fromY != toY) && // not igui!
10221                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10222                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10223                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10224            }
10225       }
10226
10227       pawn = board[fromY][fromX];
10228       if( pawn == WhiteLance || pawn == BlackLance ) {
10229            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10230                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10231                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10232            }
10233       }
10234       if( pawn == WhitePawn ) {
10235            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10236                board[EP_STATUS] = EP_PAWN_MOVE;
10237            if( toY-fromY>=2) {
10238                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10239                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10240                         gameInfo.variant != VariantBerolina || toX < fromX)
10241                       board[EP_STATUS] = toX | berolina;
10242                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10243                         gameInfo.variant != VariantBerolina || toX > fromX)
10244                       board[EP_STATUS] = toX;
10245            }
10246       } else
10247       if( pawn == BlackPawn ) {
10248            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10249                board[EP_STATUS] = EP_PAWN_MOVE;
10250            if( toY-fromY<= -2) {
10251                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10252                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10253                         gameInfo.variant != VariantBerolina || toX < fromX)
10254                       board[EP_STATUS] = toX | berolina;
10255                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10256                         gameInfo.variant != VariantBerolina || toX > fromX)
10257                       board[EP_STATUS] = toX;
10258            }
10259        }
10260
10261        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10262        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10263        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10264        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10265
10266        for(i=0; i<nrCastlingRights; i++) {
10267            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10268               board[CASTLING][i] == toX   && castlingRank[i] == toY
10269              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10270        }
10271
10272        if(gameInfo.variant == VariantSChess) { // update virginity
10273            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10274            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10275            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10276            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10277        }
10278
10279      if (fromX == toX && fromY == toY && killX < 0) return;
10280
10281      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10282      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10283      if(gameInfo.variant == VariantKnightmate)
10284          king += (int) WhiteUnicorn - (int) WhiteKing;
10285
10286     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10287        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10288         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10289         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10290         board[EP_STATUS] = EP_NONE; // capture was fake!
10291     } else
10292     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10293         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10294         board[toY][toX] = piece;
10295         board[EP_STATUS] = EP_NONE; // capture was fake!
10296     } else
10297     /* Code added by Tord: */
10298     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10299     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10300         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10301       board[EP_STATUS] = EP_NONE; // capture was fake!
10302       board[fromY][fromX] = EmptySquare;
10303       board[toY][toX] = EmptySquare;
10304       if((toX > fromX) != (piece == WhiteRook)) {
10305         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10306       } else {
10307         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10308       }
10309     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10310                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10311       board[EP_STATUS] = EP_NONE;
10312       board[fromY][fromX] = EmptySquare;
10313       board[toY][toX] = EmptySquare;
10314       if((toX > fromX) != (piece == BlackRook)) {
10315         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10316       } else {
10317         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10318       }
10319     /* End of code added by Tord */
10320
10321     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10322         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10323         board[toY][toX] = piece;
10324     } else if (board[fromY][fromX] == king
10325         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10326         && toY == fromY && toX > fromX+1) {
10327         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10328                                                                                              ; // castle with nearest piece
10329         board[fromY][toX-1] = board[fromY][rookX];
10330         board[fromY][rookX] = EmptySquare;
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = king;
10333     } else if (board[fromY][fromX] == king
10334         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10335                && toY == fromY && toX < fromX-1) {
10336         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10337                                                                                   ; // castle with nearest piece
10338         board[fromY][toX+1] = board[fromY][rookX];
10339         board[fromY][rookX] = EmptySquare;
10340         board[fromY][fromX] = EmptySquare;
10341         board[toY][toX] = king;
10342     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10343                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10344                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10345                ) {
10346         /* white pawn promotion */
10347         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10348         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10349             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10350         board[fromY][fromX] = EmptySquare;
10351     } else if ((fromY >= BOARD_HEIGHT>>1)
10352                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10353                && (toX != fromX)
10354                && gameInfo.variant != VariantXiangqi
10355                && gameInfo.variant != VariantBerolina
10356                && (pawn == WhitePawn)
10357                && (board[toY][toX] == EmptySquare)) {
10358         board[fromY][fromX] = EmptySquare;
10359         board[toY][toX] = piece;
10360         if(toY == epRank - 128 + 1)
10361             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10362         else
10363             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10364     } else if ((fromY == BOARD_HEIGHT-4)
10365                && (toX == fromX)
10366                && gameInfo.variant == VariantBerolina
10367                && (board[fromY][fromX] == WhitePawn)
10368                && (board[toY][toX] == EmptySquare)) {
10369         board[fromY][fromX] = EmptySquare;
10370         board[toY][toX] = WhitePawn;
10371         if(oldEP & EP_BEROLIN_A) {
10372                 captured = board[fromY][fromX-1];
10373                 board[fromY][fromX-1] = EmptySquare;
10374         }else{  captured = board[fromY][fromX+1];
10375                 board[fromY][fromX+1] = EmptySquare;
10376         }
10377     } else if (board[fromY][fromX] == king
10378         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10379                && toY == fromY && toX > fromX+1) {
10380         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10381                                                                                              ;
10382         board[fromY][toX-1] = board[fromY][rookX];
10383         board[fromY][rookX] = EmptySquare;
10384         board[fromY][fromX] = EmptySquare;
10385         board[toY][toX] = king;
10386     } else if (board[fromY][fromX] == king
10387         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10388                && toY == fromY && toX < fromX-1) {
10389         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10390                                                                                 ;
10391         board[fromY][toX+1] = board[fromY][rookX];
10392         board[fromY][rookX] = EmptySquare;
10393         board[fromY][fromX] = EmptySquare;
10394         board[toY][toX] = king;
10395     } else if (fromY == 7 && fromX == 3
10396                && board[fromY][fromX] == BlackKing
10397                && toY == 7 && toX == 5) {
10398         board[fromY][fromX] = EmptySquare;
10399         board[toY][toX] = BlackKing;
10400         board[fromY][7] = EmptySquare;
10401         board[toY][4] = BlackRook;
10402     } else if (fromY == 7 && fromX == 3
10403                && board[fromY][fromX] == BlackKing
10404                && toY == 7 && toX == 1) {
10405         board[fromY][fromX] = EmptySquare;
10406         board[toY][toX] = BlackKing;
10407         board[fromY][0] = EmptySquare;
10408         board[toY][2] = BlackRook;
10409     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10410                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10411                && toY < promoRank && promoChar
10412                ) {
10413         /* black pawn promotion */
10414         board[toY][toX] = CharToPiece(ToLower(promoChar));
10415         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10416             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10417         board[fromY][fromX] = EmptySquare;
10418     } else if ((fromY < BOARD_HEIGHT>>1)
10419                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10420                && (toX != fromX)
10421                && gameInfo.variant != VariantXiangqi
10422                && gameInfo.variant != VariantBerolina
10423                && (pawn == BlackPawn)
10424                && (board[toY][toX] == EmptySquare)) {
10425         board[fromY][fromX] = EmptySquare;
10426         board[toY][toX] = piece;
10427         if(toY == epRank - 128 - 1)
10428             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10429         else
10430             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10431     } else if ((fromY == 3)
10432                && (toX == fromX)
10433                && gameInfo.variant == VariantBerolina
10434                && (board[fromY][fromX] == BlackPawn)
10435                && (board[toY][toX] == EmptySquare)) {
10436         board[fromY][fromX] = EmptySquare;
10437         board[toY][toX] = BlackPawn;
10438         if(oldEP & EP_BEROLIN_A) {
10439                 captured = board[fromY][fromX-1];
10440                 board[fromY][fromX-1] = EmptySquare;
10441         }else{  captured = board[fromY][fromX+1];
10442                 board[fromY][fromX+1] = EmptySquare;
10443         }
10444     } else {
10445         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10446         board[fromY][fromX] = EmptySquare;
10447         board[toY][toX] = piece;
10448     }
10449   }
10450
10451     if (gameInfo.holdingsWidth != 0) {
10452
10453       /* !!A lot more code needs to be written to support holdings  */
10454       /* [HGM] OK, so I have written it. Holdings are stored in the */
10455       /* penultimate board files, so they are automaticlly stored   */
10456       /* in the game history.                                       */
10457       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10458                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10459         /* Delete from holdings, by decreasing count */
10460         /* and erasing image if necessary            */
10461         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10462         if(p < (int) BlackPawn) { /* white drop */
10463              p -= (int)WhitePawn;
10464                  p = PieceToNumber((ChessSquare)p);
10465              if(p >= gameInfo.holdingsSize) p = 0;
10466              if(--board[p][BOARD_WIDTH-2] <= 0)
10467                   board[p][BOARD_WIDTH-1] = EmptySquare;
10468              if((int)board[p][BOARD_WIDTH-2] < 0)
10469                         board[p][BOARD_WIDTH-2] = 0;
10470         } else {                  /* black drop */
10471              p -= (int)BlackPawn;
10472                  p = PieceToNumber((ChessSquare)p);
10473              if(p >= gameInfo.holdingsSize) p = 0;
10474              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10475                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10476              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10477                         board[BOARD_HEIGHT-1-p][1] = 0;
10478         }
10479       }
10480       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10481           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10482         /* [HGM] holdings: Add to holdings, if holdings exist */
10483         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10484                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10485                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10486         }
10487         p = (int) captured;
10488         if (p >= (int) BlackPawn) {
10489           p -= (int)BlackPawn;
10490           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10491                   /* Restore shogi-promoted piece to its original  first */
10492                   captured = (ChessSquare) (DEMOTED(captured));
10493                   p = DEMOTED(p);
10494           }
10495           p = PieceToNumber((ChessSquare)p);
10496           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10497           board[p][BOARD_WIDTH-2]++;
10498           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10499         } else {
10500           p -= (int)WhitePawn;
10501           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10502                   captured = (ChessSquare) (DEMOTED(captured));
10503                   p = DEMOTED(p);
10504           }
10505           p = PieceToNumber((ChessSquare)p);
10506           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10507           board[BOARD_HEIGHT-1-p][1]++;
10508           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10509         }
10510       }
10511     } else if (gameInfo.variant == VariantAtomic) {
10512       if (captured != EmptySquare) {
10513         int y, x;
10514         for (y = toY-1; y <= toY+1; y++) {
10515           for (x = toX-1; x <= toX+1; x++) {
10516             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10517                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10518               board[y][x] = EmptySquare;
10519             }
10520           }
10521         }
10522         board[toY][toX] = EmptySquare;
10523       }
10524     }
10525
10526     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10527         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10528     } else
10529     if(promoChar == '+') {
10530         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10531         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10532         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10533           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10534     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10535         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10536         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10537            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10538         board[toY][toX] = newPiece;
10539     }
10540     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10541                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10542         // [HGM] superchess: take promotion piece out of holdings
10543         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10544         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10545             if(!--board[k][BOARD_WIDTH-2])
10546                 board[k][BOARD_WIDTH-1] = EmptySquare;
10547         } else {
10548             if(!--board[BOARD_HEIGHT-1-k][1])
10549                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10550         }
10551     }
10552 }
10553
10554 /* Updates forwardMostMove */
10555 void
10556 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10557 {
10558     int x = toX, y = toY;
10559     char *s = parseList[forwardMostMove];
10560     ChessSquare p = boards[forwardMostMove][toY][toX];
10561 //    forwardMostMove++; // [HGM] bare: moved downstream
10562
10563     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10564     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10565     (void) CoordsToAlgebraic(boards[forwardMostMove],
10566                              PosFlags(forwardMostMove),
10567                              fromY, fromX, y, x, (killX < 0)*promoChar,
10568                              s);
10569     if(kill2X >= 0 && kill2Y >= 0)
10570         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10571     if(killX >= 0 && killY >= 0)
10572         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10573                                            toX + AAA, toY + ONE - '0', promoChar);
10574
10575     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10576         int timeLeft; static int lastLoadFlag=0; int king, piece;
10577         piece = boards[forwardMostMove][fromY][fromX];
10578         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10579         if(gameInfo.variant == VariantKnightmate)
10580             king += (int) WhiteUnicorn - (int) WhiteKing;
10581         if(forwardMostMove == 0) {
10582             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10583                 fprintf(serverMoves, "%s;", UserName());
10584             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10585                 fprintf(serverMoves, "%s;", second.tidy);
10586             fprintf(serverMoves, "%s;", first.tidy);
10587             if(gameMode == MachinePlaysWhite)
10588                 fprintf(serverMoves, "%s;", UserName());
10589             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10590                 fprintf(serverMoves, "%s;", second.tidy);
10591         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10592         lastLoadFlag = loadFlag;
10593         // print base move
10594         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10595         // print castling suffix
10596         if( toY == fromY && piece == king ) {
10597             if(toX-fromX > 1)
10598                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10599             if(fromX-toX >1)
10600                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10601         }
10602         // e.p. suffix
10603         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10604              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10605              boards[forwardMostMove][toY][toX] == EmptySquare
10606              && fromX != toX && fromY != toY)
10607                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10608         // promotion suffix
10609         if(promoChar != NULLCHAR) {
10610             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10611                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10612                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10613             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10614         }
10615         if(!loadFlag) {
10616                 char buf[MOVE_LEN*2], *p; int len;
10617             fprintf(serverMoves, "/%d/%d",
10618                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10619             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10620             else                      timeLeft = blackTimeRemaining/1000;
10621             fprintf(serverMoves, "/%d", timeLeft);
10622                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10623                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10624                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10625                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10626             fprintf(serverMoves, "/%s", buf);
10627         }
10628         fflush(serverMoves);
10629     }
10630
10631     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10632         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10633       return;
10634     }
10635     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10636     if (commentList[forwardMostMove+1] != NULL) {
10637         free(commentList[forwardMostMove+1]);
10638         commentList[forwardMostMove+1] = NULL;
10639     }
10640     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10642     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10643     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10644     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10645     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10646     adjustedClock = FALSE;
10647     gameInfo.result = GameUnfinished;
10648     if (gameInfo.resultDetails != NULL) {
10649         free(gameInfo.resultDetails);
10650         gameInfo.resultDetails = NULL;
10651     }
10652     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10653                               moveList[forwardMostMove - 1]);
10654     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10655       case MT_NONE:
10656       case MT_STALEMATE:
10657       default:
10658         break;
10659       case MT_CHECK:
10660         if(!IS_SHOGI(gameInfo.variant))
10661             strcat(parseList[forwardMostMove - 1], "+");
10662         break;
10663       case MT_CHECKMATE:
10664       case MT_STAINMATE:
10665         strcat(parseList[forwardMostMove - 1], "#");
10666         break;
10667     }
10668 }
10669
10670 /* Updates currentMove if not pausing */
10671 void
10672 ShowMove (int fromX, int fromY, int toX, int toY)
10673 {
10674     int instant = (gameMode == PlayFromGameFile) ?
10675         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10676     if(appData.noGUI) return;
10677     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10678         if (!instant) {
10679             if (forwardMostMove == currentMove + 1) {
10680                 AnimateMove(boards[forwardMostMove - 1],
10681                             fromX, fromY, toX, toY);
10682             }
10683         }
10684         currentMove = forwardMostMove;
10685     }
10686
10687     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10688
10689     if (instant) return;
10690
10691     DisplayMove(currentMove - 1);
10692     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10693             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10694                 SetHighlights(fromX, fromY, toX, toY);
10695             }
10696     }
10697     DrawPosition(FALSE, boards[currentMove]);
10698     DisplayBothClocks();
10699     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10700 }
10701
10702 void
10703 SendEgtPath (ChessProgramState *cps)
10704 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10705         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10706
10707         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10708
10709         while(*p) {
10710             char c, *q = name+1, *r, *s;
10711
10712             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10713             while(*p && *p != ',') *q++ = *p++;
10714             *q++ = ':'; *q = 0;
10715             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10716                 strcmp(name, ",nalimov:") == 0 ) {
10717                 // take nalimov path from the menu-changeable option first, if it is defined
10718               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10719                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10720             } else
10721             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10722                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10723                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10724                 s = r = StrStr(s, ":") + 1; // beginning of path info
10725                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10726                 c = *r; *r = 0;             // temporarily null-terminate path info
10727                     *--q = 0;               // strip of trailig ':' from name
10728                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10729                 *r = c;
10730                 SendToProgram(buf,cps);     // send egtbpath command for this format
10731             }
10732             if(*p == ',') p++; // read away comma to position for next format name
10733         }
10734 }
10735
10736 static int
10737 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10738 {
10739       int width = 8, height = 8, holdings = 0;             // most common sizes
10740       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10741       // correct the deviations default for each variant
10742       if( v == VariantXiangqi ) width = 9,  height = 10;
10743       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10744       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10745       if( v == VariantCapablanca || v == VariantCapaRandom ||
10746           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10747                                 width = 10;
10748       if( v == VariantCourier ) width = 12;
10749       if( v == VariantSuper )                            holdings = 8;
10750       if( v == VariantGreat )   width = 10,              holdings = 8;
10751       if( v == VariantSChess )                           holdings = 7;
10752       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10753       if( v == VariantChuChess) width = 10, height = 10;
10754       if( v == VariantChu )     width = 12, height = 12;
10755       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10756              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10757              holdingsSize >= 0 && holdingsSize != holdings;
10758 }
10759
10760 char variantError[MSG_SIZ];
10761
10762 char *
10763 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10764 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10765       char *p, *variant = VariantName(v);
10766       static char b[MSG_SIZ];
10767       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10768            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10769                                                holdingsSize, variant); // cook up sized variant name
10770            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10771            if(StrStr(list, b) == NULL) {
10772                // specific sized variant not known, check if general sizing allowed
10773                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10774                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10775                             boardWidth, boardHeight, holdingsSize, engine);
10776                    return NULL;
10777                }
10778                /* [HGM] here we really should compare with the maximum supported board size */
10779            }
10780       } else snprintf(b, MSG_SIZ,"%s", variant);
10781       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10782       p = StrStr(list, b);
10783       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10784       if(p == NULL) {
10785           // occurs not at all in list, or only as sub-string
10786           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10787           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10788               int l = strlen(variantError);
10789               char *q;
10790               while(p != list && p[-1] != ',') p--;
10791               q = strchr(p, ',');
10792               if(q) *q = NULLCHAR;
10793               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10794               if(q) *q= ',';
10795           }
10796           return NULL;
10797       }
10798       return b;
10799 }
10800
10801 void
10802 InitChessProgram (ChessProgramState *cps, int setup)
10803 /* setup needed to setup FRC opening position */
10804 {
10805     char buf[MSG_SIZ], *b;
10806     if (appData.noChessProgram) return;
10807     hintRequested = FALSE;
10808     bookRequested = FALSE;
10809
10810     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10811     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10812     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10813     if(cps->memSize) { /* [HGM] memory */
10814       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10815         SendToProgram(buf, cps);
10816     }
10817     SendEgtPath(cps); /* [HGM] EGT */
10818     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10819       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10820         SendToProgram(buf, cps);
10821     }
10822
10823     setboardSpoiledMachineBlack = FALSE;
10824     SendToProgram(cps->initString, cps);
10825     if (gameInfo.variant != VariantNormal &&
10826         gameInfo.variant != VariantLoadable
10827         /* [HGM] also send variant if board size non-standard */
10828         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10829
10830       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10831                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10832
10833       if (b == NULL) {
10834         VariantClass v;
10835         char c, *q = cps->variants, *p = strchr(q, ',');
10836         if(p) *p = NULLCHAR;
10837         v = StringToVariant(q);
10838         DisplayError(variantError, 0);
10839         if(v != VariantUnknown && cps == &first) {
10840             int w, h, s;
10841             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10842                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10843             ASSIGN(appData.variant, q);
10844             Reset(TRUE, FALSE);
10845         }
10846         if(p) *p = ',';
10847         return;
10848       }
10849
10850       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10851       SendToProgram(buf, cps);
10852     }
10853     currentlyInitializedVariant = gameInfo.variant;
10854
10855     /* [HGM] send opening position in FRC to first engine */
10856     if(setup) {
10857           SendToProgram("force\n", cps);
10858           SendBoard(cps, 0);
10859           /* engine is now in force mode! Set flag to wake it up after first move. */
10860           setboardSpoiledMachineBlack = 1;
10861     }
10862
10863     if (cps->sendICS) {
10864       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10865       SendToProgram(buf, cps);
10866     }
10867     cps->maybeThinking = FALSE;
10868     cps->offeredDraw = 0;
10869     if (!appData.icsActive) {
10870         SendTimeControl(cps, movesPerSession, timeControl,
10871                         timeIncrement, appData.searchDepth,
10872                         searchTime);
10873     }
10874     if (appData.showThinking
10875         // [HGM] thinking: four options require thinking output to be sent
10876         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10877                                 ) {
10878         SendToProgram("post\n", cps);
10879     }
10880     SendToProgram("hard\n", cps);
10881     if (!appData.ponderNextMove) {
10882         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10883            it without being sure what state we are in first.  "hard"
10884            is not a toggle, so that one is OK.
10885          */
10886         SendToProgram("easy\n", cps);
10887     }
10888     if (cps->usePing) {
10889       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10890       SendToProgram(buf, cps);
10891     }
10892     cps->initDone = TRUE;
10893     ClearEngineOutputPane(cps == &second);
10894 }
10895
10896
10897 void
10898 ResendOptions (ChessProgramState *cps)
10899 { // send the stored value of the options
10900   int i;
10901   char buf[MSG_SIZ];
10902   Option *opt = cps->option;
10903   for(i=0; i<cps->nrOptions; i++, opt++) {
10904       switch(opt->type) {
10905         case Spin:
10906         case Slider:
10907         case CheckBox:
10908             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10909           break;
10910         case ComboBox:
10911           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10912           break;
10913         default:
10914             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10915           break;
10916         case Button:
10917         case SaveButton:
10918           continue;
10919       }
10920       SendToProgram(buf, cps);
10921   }
10922 }
10923
10924 void
10925 StartChessProgram (ChessProgramState *cps)
10926 {
10927     char buf[MSG_SIZ];
10928     int err;
10929
10930     if (appData.noChessProgram) return;
10931     cps->initDone = FALSE;
10932
10933     if (strcmp(cps->host, "localhost") == 0) {
10934         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10935     } else if (*appData.remoteShell == NULLCHAR) {
10936         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10937     } else {
10938         if (*appData.remoteUser == NULLCHAR) {
10939           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10940                     cps->program);
10941         } else {
10942           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10943                     cps->host, appData.remoteUser, cps->program);
10944         }
10945         err = StartChildProcess(buf, "", &cps->pr);
10946     }
10947
10948     if (err != 0) {
10949       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10950         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10951         if(cps != &first) return;
10952         appData.noChessProgram = TRUE;
10953         ThawUI();
10954         SetNCPMode();
10955 //      DisplayFatalError(buf, err, 1);
10956 //      cps->pr = NoProc;
10957 //      cps->isr = NULL;
10958         return;
10959     }
10960
10961     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10962     if (cps->protocolVersion > 1) {
10963       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10964       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10965         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10966         cps->comboCnt = 0;  //                and values of combo boxes
10967       }
10968       SendToProgram(buf, cps);
10969       if(cps->reload) ResendOptions(cps);
10970     } else {
10971       SendToProgram("xboard\n", cps);
10972     }
10973 }
10974
10975 void
10976 TwoMachinesEventIfReady P((void))
10977 {
10978   static int curMess = 0;
10979   if (first.lastPing != first.lastPong) {
10980     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10981     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10982     return;
10983   }
10984   if (second.lastPing != second.lastPong) {
10985     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10986     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10987     return;
10988   }
10989   DisplayMessage("", ""); curMess = 0;
10990   TwoMachinesEvent();
10991 }
10992
10993 char *
10994 MakeName (char *template)
10995 {
10996     time_t clock;
10997     struct tm *tm;
10998     static char buf[MSG_SIZ];
10999     char *p = buf;
11000     int i;
11001
11002     clock = time((time_t *)NULL);
11003     tm = localtime(&clock);
11004
11005     while(*p++ = *template++) if(p[-1] == '%') {
11006         switch(*template++) {
11007           case 0:   *p = 0; return buf;
11008           case 'Y': i = tm->tm_year+1900; break;
11009           case 'y': i = tm->tm_year-100; break;
11010           case 'M': i = tm->tm_mon+1; break;
11011           case 'd': i = tm->tm_mday; break;
11012           case 'h': i = tm->tm_hour; break;
11013           case 'm': i = tm->tm_min; break;
11014           case 's': i = tm->tm_sec; break;
11015           default:  i = 0;
11016         }
11017         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11018     }
11019     return buf;
11020 }
11021
11022 int
11023 CountPlayers (char *p)
11024 {
11025     int n = 0;
11026     while(p = strchr(p, '\n')) p++, n++; // count participants
11027     return n;
11028 }
11029
11030 FILE *
11031 WriteTourneyFile (char *results, FILE *f)
11032 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11033     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11034     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11035         // create a file with tournament description
11036         fprintf(f, "-participants {%s}\n", appData.participants);
11037         fprintf(f, "-seedBase %d\n", appData.seedBase);
11038         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11039         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11040         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11041         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11042         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11043         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11044         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11045         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11046         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11047         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11048         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11049         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11050         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11051         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11052         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11053         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11054         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11055         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11056         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11057         fprintf(f, "-smpCores %d\n", appData.smpCores);
11058         if(searchTime > 0)
11059                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11060         else {
11061                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11062                 fprintf(f, "-tc %s\n", appData.timeControl);
11063                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11064         }
11065         fprintf(f, "-results \"%s\"\n", results);
11066     }
11067     return f;
11068 }
11069
11070 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11071
11072 void
11073 Substitute (char *participants, int expunge)
11074 {
11075     int i, changed, changes=0, nPlayers=0;
11076     char *p, *q, *r, buf[MSG_SIZ];
11077     if(participants == NULL) return;
11078     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11079     r = p = participants; q = appData.participants;
11080     while(*p && *p == *q) {
11081         if(*p == '\n') r = p+1, nPlayers++;
11082         p++; q++;
11083     }
11084     if(*p) { // difference
11085         while(*p && *p++ != '\n')
11086                                  ;
11087         while(*q && *q++ != '\n')
11088                                  ;
11089       changed = nPlayers;
11090         changes = 1 + (strcmp(p, q) != 0);
11091     }
11092     if(changes == 1) { // a single engine mnemonic was changed
11093         q = r; while(*q) nPlayers += (*q++ == '\n');
11094         p = buf; while(*r && (*p = *r++) != '\n') p++;
11095         *p = NULLCHAR;
11096         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11097         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11098         if(mnemonic[i]) { // The substitute is valid
11099             FILE *f;
11100             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11101                 flock(fileno(f), LOCK_EX);
11102                 ParseArgsFromFile(f);
11103                 fseek(f, 0, SEEK_SET);
11104                 FREE(appData.participants); appData.participants = participants;
11105                 if(expunge) { // erase results of replaced engine
11106                     int len = strlen(appData.results), w, b, dummy;
11107                     for(i=0; i<len; i++) {
11108                         Pairing(i, nPlayers, &w, &b, &dummy);
11109                         if((w == changed || b == changed) && appData.results[i] == '*') {
11110                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11111                             fclose(f);
11112                             return;
11113                         }
11114                     }
11115                     for(i=0; i<len; i++) {
11116                         Pairing(i, nPlayers, &w, &b, &dummy);
11117                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11118                     }
11119                 }
11120                 WriteTourneyFile(appData.results, f);
11121                 fclose(f); // release lock
11122                 return;
11123             }
11124         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11125     }
11126     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11127     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11128     free(participants);
11129     return;
11130 }
11131
11132 int
11133 CheckPlayers (char *participants)
11134 {
11135         int i;
11136         char buf[MSG_SIZ], *p;
11137         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11138         while(p = strchr(participants, '\n')) {
11139             *p = NULLCHAR;
11140             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11141             if(!mnemonic[i]) {
11142                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11143                 *p = '\n';
11144                 DisplayError(buf, 0);
11145                 return 1;
11146             }
11147             *p = '\n';
11148             participants = p + 1;
11149         }
11150         return 0;
11151 }
11152
11153 int
11154 CreateTourney (char *name)
11155 {
11156         FILE *f;
11157         if(matchMode && strcmp(name, appData.tourneyFile)) {
11158              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11159         }
11160         if(name[0] == NULLCHAR) {
11161             if(appData.participants[0])
11162                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11163             return 0;
11164         }
11165         f = fopen(name, "r");
11166         if(f) { // file exists
11167             ASSIGN(appData.tourneyFile, name);
11168             ParseArgsFromFile(f); // parse it
11169         } else {
11170             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11171             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11172                 DisplayError(_("Not enough participants"), 0);
11173                 return 0;
11174             }
11175             if(CheckPlayers(appData.participants)) return 0;
11176             ASSIGN(appData.tourneyFile, name);
11177             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11178             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11179         }
11180         fclose(f);
11181         appData.noChessProgram = FALSE;
11182         appData.clockMode = TRUE;
11183         SetGNUMode();
11184         return 1;
11185 }
11186
11187 int
11188 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11189 {
11190     char buf[MSG_SIZ], *p, *q;
11191     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11192     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11193     skip = !all && group[0]; // if group requested, we start in skip mode
11194     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11195         p = names; q = buf; header = 0;
11196         while(*p && *p != '\n') *q++ = *p++;
11197         *q = 0;
11198         if(*p == '\n') p++;
11199         if(buf[0] == '#') {
11200             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11201             depth++; // we must be entering a new group
11202             if(all) continue; // suppress printing group headers when complete list requested
11203             header = 1;
11204             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11205         }
11206         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11207         if(engineList[i]) free(engineList[i]);
11208         engineList[i] = strdup(buf);
11209         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11210         if(engineMnemonic[i]) free(engineMnemonic[i]);
11211         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11212             strcat(buf, " (");
11213             sscanf(q + 8, "%s", buf + strlen(buf));
11214             strcat(buf, ")");
11215         }
11216         engineMnemonic[i] = strdup(buf);
11217         i++;
11218     }
11219     engineList[i] = engineMnemonic[i] = NULL;
11220     return i;
11221 }
11222
11223 // following implemented as macro to avoid type limitations
11224 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11225
11226 void
11227 SwapEngines (int n)
11228 {   // swap settings for first engine and other engine (so far only some selected options)
11229     int h;
11230     char *p;
11231     if(n == 0) return;
11232     SWAP(directory, p)
11233     SWAP(chessProgram, p)
11234     SWAP(isUCI, h)
11235     SWAP(hasOwnBookUCI, h)
11236     SWAP(protocolVersion, h)
11237     SWAP(reuse, h)
11238     SWAP(scoreIsAbsolute, h)
11239     SWAP(timeOdds, h)
11240     SWAP(logo, p)
11241     SWAP(pgnName, p)
11242     SWAP(pvSAN, h)
11243     SWAP(engOptions, p)
11244     SWAP(engInitString, p)
11245     SWAP(computerString, p)
11246     SWAP(features, p)
11247     SWAP(fenOverride, p)
11248     SWAP(NPS, h)
11249     SWAP(accumulateTC, h)
11250     SWAP(drawDepth, h)
11251     SWAP(host, p)
11252     SWAP(pseudo, h)
11253 }
11254
11255 int
11256 GetEngineLine (char *s, int n)
11257 {
11258     int i;
11259     char buf[MSG_SIZ];
11260     extern char *icsNames;
11261     if(!s || !*s) return 0;
11262     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11263     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11264     if(!mnemonic[i]) return 0;
11265     if(n == 11) return 1; // just testing if there was a match
11266     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11267     if(n == 1) SwapEngines(n);
11268     ParseArgsFromString(buf);
11269     if(n == 1) SwapEngines(n);
11270     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11271         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11272         ParseArgsFromString(buf);
11273     }
11274     return 1;
11275 }
11276
11277 int
11278 SetPlayer (int player, char *p)
11279 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11280     int i;
11281     char buf[MSG_SIZ], *engineName;
11282     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11283     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11284     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11285     if(mnemonic[i]) {
11286         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11287         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11288         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11289         ParseArgsFromString(buf);
11290     } else { // no engine with this nickname is installed!
11291         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11292         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11293         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11294         ModeHighlight();
11295         DisplayError(buf, 0);
11296         return 0;
11297     }
11298     free(engineName);
11299     return i;
11300 }
11301
11302 char *recentEngines;
11303
11304 void
11305 RecentEngineEvent (int nr)
11306 {
11307     int n;
11308 //    SwapEngines(1); // bump first to second
11309 //    ReplaceEngine(&second, 1); // and load it there
11310     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11311     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11312     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11313         ReplaceEngine(&first, 0);
11314         FloatToFront(&appData.recentEngineList, command[n]);
11315     }
11316 }
11317
11318 int
11319 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11320 {   // determine players from game number
11321     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11322
11323     if(appData.tourneyType == 0) {
11324         roundsPerCycle = (nPlayers - 1) | 1;
11325         pairingsPerRound = nPlayers / 2;
11326     } else if(appData.tourneyType > 0) {
11327         roundsPerCycle = nPlayers - appData.tourneyType;
11328         pairingsPerRound = appData.tourneyType;
11329     }
11330     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11331     gamesPerCycle = gamesPerRound * roundsPerCycle;
11332     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11333     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11334     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11335     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11336     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11337     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11338
11339     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11340     if(appData.roundSync) *syncInterval = gamesPerRound;
11341
11342     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11343
11344     if(appData.tourneyType == 0) {
11345         if(curPairing == (nPlayers-1)/2 ) {
11346             *whitePlayer = curRound;
11347             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11348         } else {
11349             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11350             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11351             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11352             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11353         }
11354     } else if(appData.tourneyType > 1) {
11355         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11356         *whitePlayer = curRound + appData.tourneyType;
11357     } else if(appData.tourneyType > 0) {
11358         *whitePlayer = curPairing;
11359         *blackPlayer = curRound + appData.tourneyType;
11360     }
11361
11362     // take care of white/black alternation per round.
11363     // For cycles and games this is already taken care of by default, derived from matchGame!
11364     return curRound & 1;
11365 }
11366
11367 int
11368 NextTourneyGame (int nr, int *swapColors)
11369 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11370     char *p, *q;
11371     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11372     FILE *tf;
11373     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11374     tf = fopen(appData.tourneyFile, "r");
11375     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11376     ParseArgsFromFile(tf); fclose(tf);
11377     InitTimeControls(); // TC might be altered from tourney file
11378
11379     nPlayers = CountPlayers(appData.participants); // count participants
11380     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11381     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11382
11383     if(syncInterval) {
11384         p = q = appData.results;
11385         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11386         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11387             DisplayMessage(_("Waiting for other game(s)"),"");
11388             waitingForGame = TRUE;
11389             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11390             return 0;
11391         }
11392         waitingForGame = FALSE;
11393     }
11394
11395     if(appData.tourneyType < 0) {
11396         if(nr>=0 && !pairingReceived) {
11397             char buf[1<<16];
11398             if(pairing.pr == NoProc) {
11399                 if(!appData.pairingEngine[0]) {
11400                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11401                     return 0;
11402                 }
11403                 StartChessProgram(&pairing); // starts the pairing engine
11404             }
11405             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11406             SendToProgram(buf, &pairing);
11407             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11408             SendToProgram(buf, &pairing);
11409             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11410         }
11411         pairingReceived = 0;                              // ... so we continue here
11412         *swapColors = 0;
11413         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11414         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11415         matchGame = 1; roundNr = nr / syncInterval + 1;
11416     }
11417
11418     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11419
11420     // redefine engines, engine dir, etc.
11421     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11422     if(first.pr == NoProc) {
11423       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11424       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11425     }
11426     if(second.pr == NoProc) {
11427       SwapEngines(1);
11428       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11429       SwapEngines(1);         // and make that valid for second engine by swapping
11430       InitEngine(&second, 1);
11431     }
11432     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11433     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11434     return OK;
11435 }
11436
11437 void
11438 NextMatchGame ()
11439 {   // performs game initialization that does not invoke engines, and then tries to start the game
11440     int res, firstWhite, swapColors = 0;
11441     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11442     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
11443         char buf[MSG_SIZ];
11444         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11445         if(strcmp(buf, currentDebugFile)) { // name has changed
11446             FILE *f = fopen(buf, "w");
11447             if(f) { // if opening the new file failed, just keep using the old one
11448                 ASSIGN(currentDebugFile, buf);
11449                 fclose(debugFP);
11450                 debugFP = f;
11451             }
11452             if(appData.serverFileName) {
11453                 if(serverFP) fclose(serverFP);
11454                 serverFP = fopen(appData.serverFileName, "w");
11455                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11456                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11457             }
11458         }
11459     }
11460     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11461     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11462     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11463     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11464     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11465     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11466     Reset(FALSE, first.pr != NoProc);
11467     res = LoadGameOrPosition(matchGame); // setup game
11468     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11469     if(!res) return; // abort when bad game/pos file
11470     if(appData.epd) {// in EPD mode we make sure first engine is to move
11471         firstWhite = !(forwardMostMove & 1);
11472         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11473         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11474     }
11475     TwoMachinesEvent();
11476 }
11477
11478 void
11479 UserAdjudicationEvent (int result)
11480 {
11481     ChessMove gameResult = GameIsDrawn;
11482
11483     if( result > 0 ) {
11484         gameResult = WhiteWins;
11485     }
11486     else if( result < 0 ) {
11487         gameResult = BlackWins;
11488     }
11489
11490     if( gameMode == TwoMachinesPlay ) {
11491         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11492     }
11493 }
11494
11495
11496 // [HGM] save: calculate checksum of game to make games easily identifiable
11497 int
11498 StringCheckSum (char *s)
11499 {
11500         int i = 0;
11501         if(s==NULL) return 0;
11502         while(*s) i = i*259 + *s++;
11503         return i;
11504 }
11505
11506 int
11507 GameCheckSum ()
11508 {
11509         int i, sum=0;
11510         for(i=backwardMostMove; i<forwardMostMove; i++) {
11511                 sum += pvInfoList[i].depth;
11512                 sum += StringCheckSum(parseList[i]);
11513                 sum += StringCheckSum(commentList[i]);
11514                 sum *= 261;
11515         }
11516         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11517         return sum + StringCheckSum(commentList[i]);
11518 } // end of save patch
11519
11520 void
11521 GameEnds (ChessMove result, char *resultDetails, int whosays)
11522 {
11523     GameMode nextGameMode;
11524     int isIcsGame;
11525     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11526
11527     if(endingGame) return; /* [HGM] crash: forbid recursion */
11528     endingGame = 1;
11529     if(twoBoards) { // [HGM] dual: switch back to one board
11530         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11531         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11532     }
11533     if (appData.debugMode) {
11534       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11535               result, resultDetails ? resultDetails : "(null)", whosays);
11536     }
11537
11538     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11539
11540     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11541
11542     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11543         /* If we are playing on ICS, the server decides when the
11544            game is over, but the engine can offer to draw, claim
11545            a draw, or resign.
11546          */
11547 #if ZIPPY
11548         if (appData.zippyPlay && first.initDone) {
11549             if (result == GameIsDrawn) {
11550                 /* In case draw still needs to be claimed */
11551                 SendToICS(ics_prefix);
11552                 SendToICS("draw\n");
11553             } else if (StrCaseStr(resultDetails, "resign")) {
11554                 SendToICS(ics_prefix);
11555                 SendToICS("resign\n");
11556             }
11557         }
11558 #endif
11559         endingGame = 0; /* [HGM] crash */
11560         return;
11561     }
11562
11563     /* If we're loading the game from a file, stop */
11564     if (whosays == GE_FILE) {
11565       (void) StopLoadGameTimer();
11566       gameFileFP = NULL;
11567     }
11568
11569     /* Cancel draw offers */
11570     first.offeredDraw = second.offeredDraw = 0;
11571
11572     /* If this is an ICS game, only ICS can really say it's done;
11573        if not, anyone can. */
11574     isIcsGame = (gameMode == IcsPlayingWhite ||
11575                  gameMode == IcsPlayingBlack ||
11576                  gameMode == IcsObserving    ||
11577                  gameMode == IcsExamining);
11578
11579     if (!isIcsGame || whosays == GE_ICS) {
11580         /* OK -- not an ICS game, or ICS said it was done */
11581         StopClocks();
11582         if (!isIcsGame && !appData.noChessProgram)
11583           SetUserThinkingEnables();
11584
11585         /* [HGM] if a machine claims the game end we verify this claim */
11586         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11587             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11588                 char claimer;
11589                 ChessMove trueResult = (ChessMove) -1;
11590
11591                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11592                                             first.twoMachinesColor[0] :
11593                                             second.twoMachinesColor[0] ;
11594
11595                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11596                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11597                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11598                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11599                 } else
11600                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11601                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11602                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11603                 } else
11604                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11605                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11606                 }
11607
11608                 // now verify win claims, but not in drop games, as we don't understand those yet
11609                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11610                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11611                     (result == WhiteWins && claimer == 'w' ||
11612                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11613                       if (appData.debugMode) {
11614                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11615                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11616                       }
11617                       if(result != trueResult) {
11618                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11619                               result = claimer == 'w' ? BlackWins : WhiteWins;
11620                               resultDetails = buf;
11621                       }
11622                 } else
11623                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11624                     && (forwardMostMove <= backwardMostMove ||
11625                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11626                         (claimer=='b')==(forwardMostMove&1))
11627                                                                                   ) {
11628                       /* [HGM] verify: draws that were not flagged are false claims */
11629                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11630                       result = claimer == 'w' ? BlackWins : WhiteWins;
11631                       resultDetails = buf;
11632                 }
11633                 /* (Claiming a loss is accepted no questions asked!) */
11634             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11635                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11636                 result = GameUnfinished;
11637                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11638             }
11639             /* [HGM] bare: don't allow bare King to win */
11640             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11641                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11642                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11643                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11644                && result != GameIsDrawn)
11645             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11646                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11647                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11648                         if(p >= 0 && p <= (int)WhiteKing) k++;
11649                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11650                 }
11651                 if (appData.debugMode) {
11652                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11653                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11654                 }
11655                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11656                         result = GameIsDrawn;
11657                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11658                         resultDetails = buf;
11659                 }
11660             }
11661         }
11662
11663
11664         if(serverMoves != NULL && !loadFlag) { char c = '=';
11665             if(result==WhiteWins) c = '+';
11666             if(result==BlackWins) c = '-';
11667             if(resultDetails != NULL)
11668                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11669         }
11670         if (resultDetails != NULL) {
11671             gameInfo.result = result;
11672             gameInfo.resultDetails = StrSave(resultDetails);
11673
11674             /* display last move only if game was not loaded from file */
11675             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11676                 DisplayMove(currentMove - 1);
11677
11678             if (forwardMostMove != 0) {
11679                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11680                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11681                                                                 ) {
11682                     if (*appData.saveGameFile != NULLCHAR) {
11683                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11684                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11685                         else
11686                         SaveGameToFile(appData.saveGameFile, TRUE);
11687                     } else if (appData.autoSaveGames) {
11688                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11689                     }
11690                     if (*appData.savePositionFile != NULLCHAR) {
11691                         SavePositionToFile(appData.savePositionFile);
11692                     }
11693                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11694                 }
11695             }
11696
11697             /* Tell program how game ended in case it is learning */
11698             /* [HGM] Moved this to after saving the PGN, just in case */
11699             /* engine died and we got here through time loss. In that */
11700             /* case we will get a fatal error writing the pipe, which */
11701             /* would otherwise lose us the PGN.                       */
11702             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11703             /* output during GameEnds should never be fatal anymore   */
11704             if (gameMode == MachinePlaysWhite ||
11705                 gameMode == MachinePlaysBlack ||
11706                 gameMode == TwoMachinesPlay ||
11707                 gameMode == IcsPlayingWhite ||
11708                 gameMode == IcsPlayingBlack ||
11709                 gameMode == BeginningOfGame) {
11710                 char buf[MSG_SIZ];
11711                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11712                         resultDetails);
11713                 if (first.pr != NoProc) {
11714                     SendToProgram(buf, &first);
11715                 }
11716                 if (second.pr != NoProc &&
11717                     gameMode == TwoMachinesPlay) {
11718                     SendToProgram(buf, &second);
11719                 }
11720             }
11721         }
11722
11723         if (appData.icsActive) {
11724             if (appData.quietPlay &&
11725                 (gameMode == IcsPlayingWhite ||
11726                  gameMode == IcsPlayingBlack)) {
11727                 SendToICS(ics_prefix);
11728                 SendToICS("set shout 1\n");
11729             }
11730             nextGameMode = IcsIdle;
11731             ics_user_moved = FALSE;
11732             /* clean up premove.  It's ugly when the game has ended and the
11733              * premove highlights are still on the board.
11734              */
11735             if (gotPremove) {
11736               gotPremove = FALSE;
11737               ClearPremoveHighlights();
11738               DrawPosition(FALSE, boards[currentMove]);
11739             }
11740             if (whosays == GE_ICS) {
11741                 switch (result) {
11742                 case WhiteWins:
11743                     if (gameMode == IcsPlayingWhite)
11744                         PlayIcsWinSound();
11745                     else if(gameMode == IcsPlayingBlack)
11746                         PlayIcsLossSound();
11747                     break;
11748                 case BlackWins:
11749                     if (gameMode == IcsPlayingBlack)
11750                         PlayIcsWinSound();
11751                     else if(gameMode == IcsPlayingWhite)
11752                         PlayIcsLossSound();
11753                     break;
11754                 case GameIsDrawn:
11755                     PlayIcsDrawSound();
11756                     break;
11757                 default:
11758                     PlayIcsUnfinishedSound();
11759                 }
11760             }
11761             if(appData.quitNext) { ExitEvent(0); return; }
11762         } else if (gameMode == EditGame ||
11763                    gameMode == PlayFromGameFile ||
11764                    gameMode == AnalyzeMode ||
11765                    gameMode == AnalyzeFile) {
11766             nextGameMode = gameMode;
11767         } else {
11768             nextGameMode = EndOfGame;
11769         }
11770         pausing = FALSE;
11771         ModeHighlight();
11772     } else {
11773         nextGameMode = gameMode;
11774     }
11775
11776     if (appData.noChessProgram) {
11777         gameMode = nextGameMode;
11778         ModeHighlight();
11779         endingGame = 0; /* [HGM] crash */
11780         return;
11781     }
11782
11783     if (first.reuse) {
11784         /* Put first chess program into idle state */
11785         if (first.pr != NoProc &&
11786             (gameMode == MachinePlaysWhite ||
11787              gameMode == MachinePlaysBlack ||
11788              gameMode == TwoMachinesPlay ||
11789              gameMode == IcsPlayingWhite ||
11790              gameMode == IcsPlayingBlack ||
11791              gameMode == BeginningOfGame)) {
11792             SendToProgram("force\n", &first);
11793             if (first.usePing) {
11794               char buf[MSG_SIZ];
11795               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11796               SendToProgram(buf, &first);
11797             }
11798         }
11799     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11800         /* Kill off first chess program */
11801         if (first.isr != NULL)
11802           RemoveInputSource(first.isr);
11803         first.isr = NULL;
11804
11805         if (first.pr != NoProc) {
11806             ExitAnalyzeMode();
11807             DoSleep( appData.delayBeforeQuit );
11808             SendToProgram("quit\n", &first);
11809             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11810             first.reload = TRUE;
11811         }
11812         first.pr = NoProc;
11813     }
11814     if (second.reuse) {
11815         /* Put second chess program into idle state */
11816         if (second.pr != NoProc &&
11817             gameMode == TwoMachinesPlay) {
11818             SendToProgram("force\n", &second);
11819             if (second.usePing) {
11820               char buf[MSG_SIZ];
11821               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11822               SendToProgram(buf, &second);
11823             }
11824         }
11825     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11826         /* Kill off second chess program */
11827         if (second.isr != NULL)
11828           RemoveInputSource(second.isr);
11829         second.isr = NULL;
11830
11831         if (second.pr != NoProc) {
11832             DoSleep( appData.delayBeforeQuit );
11833             SendToProgram("quit\n", &second);
11834             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11835             second.reload = TRUE;
11836         }
11837         second.pr = NoProc;
11838     }
11839
11840     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11841         char resChar = '=';
11842         switch (result) {
11843         case WhiteWins:
11844           resChar = '+';
11845           if (first.twoMachinesColor[0] == 'w') {
11846             first.matchWins++;
11847           } else {
11848             second.matchWins++;
11849           }
11850           break;
11851         case BlackWins:
11852           resChar = '-';
11853           if (first.twoMachinesColor[0] == 'b') {
11854             first.matchWins++;
11855           } else {
11856             second.matchWins++;
11857           }
11858           break;
11859         case GameUnfinished:
11860           resChar = ' ';
11861         default:
11862           break;
11863         }
11864
11865         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11866         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11867             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11868             ReserveGame(nextGame, resChar); // sets nextGame
11869             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11870             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11871         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11872
11873         if (nextGame <= appData.matchGames && !abortMatch) {
11874             gameMode = nextGameMode;
11875             matchGame = nextGame; // this will be overruled in tourney mode!
11876             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11877             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11878             endingGame = 0; /* [HGM] crash */
11879             return;
11880         } else {
11881             gameMode = nextGameMode;
11882             if(appData.epd) {
11883                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11884                 OutputKibitz(2, buf);
11885                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11886                 OutputKibitz(2, buf);
11887                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11888                 if(second.matchWins) OutputKibitz(2, buf);
11889                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11890                 OutputKibitz(2, buf);
11891             }
11892             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11893                      first.tidy, second.tidy,
11894                      first.matchWins, second.matchWins,
11895                      appData.matchGames - (first.matchWins + second.matchWins));
11896             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11897             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11898             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11899             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11900                 first.twoMachinesColor = "black\n";
11901                 second.twoMachinesColor = "white\n";
11902             } else {
11903                 first.twoMachinesColor = "white\n";
11904                 second.twoMachinesColor = "black\n";
11905             }
11906         }
11907     }
11908     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11909         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11910       ExitAnalyzeMode();
11911     gameMode = nextGameMode;
11912     ModeHighlight();
11913     endingGame = 0;  /* [HGM] crash */
11914     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11915         if(matchMode == TRUE) { // match through command line: exit with or without popup
11916             if(ranking) {
11917                 ToNrEvent(forwardMostMove);
11918                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11919                 else ExitEvent(0);
11920             } else DisplayFatalError(buf, 0, 0);
11921         } else { // match through menu; just stop, with or without popup
11922             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11923             ModeHighlight();
11924             if(ranking){
11925                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11926             } else DisplayNote(buf);
11927       }
11928       if(ranking) free(ranking);
11929     }
11930 }
11931
11932 /* Assumes program was just initialized (initString sent).
11933    Leaves program in force mode. */
11934 void
11935 FeedMovesToProgram (ChessProgramState *cps, int upto)
11936 {
11937     int i;
11938
11939     if (appData.debugMode)
11940       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11941               startedFromSetupPosition ? "position and " : "",
11942               backwardMostMove, upto, cps->which);
11943     if(currentlyInitializedVariant != gameInfo.variant) {
11944       char buf[MSG_SIZ];
11945         // [HGM] variantswitch: make engine aware of new variant
11946         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11947                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11948                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11949         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11950         SendToProgram(buf, cps);
11951         currentlyInitializedVariant = gameInfo.variant;
11952     }
11953     SendToProgram("force\n", cps);
11954     if (startedFromSetupPosition) {
11955         SendBoard(cps, backwardMostMove);
11956     if (appData.debugMode) {
11957         fprintf(debugFP, "feedMoves\n");
11958     }
11959     }
11960     for (i = backwardMostMove; i < upto; i++) {
11961         SendMoveToProgram(i, cps);
11962     }
11963 }
11964
11965
11966 int
11967 ResurrectChessProgram ()
11968 {
11969      /* The chess program may have exited.
11970         If so, restart it and feed it all the moves made so far. */
11971     static int doInit = 0;
11972
11973     if (appData.noChessProgram) return 1;
11974
11975     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11976         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11977         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11978         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11979     } else {
11980         if (first.pr != NoProc) return 1;
11981         StartChessProgram(&first);
11982     }
11983     InitChessProgram(&first, FALSE);
11984     FeedMovesToProgram(&first, currentMove);
11985
11986     if (!first.sendTime) {
11987         /* can't tell gnuchess what its clock should read,
11988            so we bow to its notion. */
11989         ResetClocks();
11990         timeRemaining[0][currentMove] = whiteTimeRemaining;
11991         timeRemaining[1][currentMove] = blackTimeRemaining;
11992     }
11993
11994     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11995                 appData.icsEngineAnalyze) && first.analysisSupport) {
11996       SendToProgram("analyze\n", &first);
11997       first.analyzing = TRUE;
11998     }
11999     return 1;
12000 }
12001
12002 /*
12003  * Button procedures
12004  */
12005 void
12006 Reset (int redraw, int init)
12007 {
12008     int i;
12009
12010     if (appData.debugMode) {
12011         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12012                 redraw, init, gameMode);
12013     }
12014     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12015     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12016     CleanupTail(); // [HGM] vari: delete any stored variations
12017     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12018     pausing = pauseExamInvalid = FALSE;
12019     startedFromSetupPosition = blackPlaysFirst = FALSE;
12020     firstMove = TRUE;
12021     whiteFlag = blackFlag = FALSE;
12022     userOfferedDraw = FALSE;
12023     hintRequested = bookRequested = FALSE;
12024     first.maybeThinking = FALSE;
12025     second.maybeThinking = FALSE;
12026     first.bookSuspend = FALSE; // [HGM] book
12027     second.bookSuspend = FALSE;
12028     thinkOutput[0] = NULLCHAR;
12029     lastHint[0] = NULLCHAR;
12030     ClearGameInfo(&gameInfo);
12031     gameInfo.variant = StringToVariant(appData.variant);
12032     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12033         gameInfo.variant = VariantUnknown;
12034         strncpy(engineVariant, appData.variant, MSG_SIZ);
12035     }
12036     ics_user_moved = ics_clock_paused = FALSE;
12037     ics_getting_history = H_FALSE;
12038     ics_gamenum = -1;
12039     white_holding[0] = black_holding[0] = NULLCHAR;
12040     ClearProgramStats();
12041     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12042
12043     ResetFrontEnd();
12044     ClearHighlights();
12045     flipView = appData.flipView;
12046     ClearPremoveHighlights();
12047     gotPremove = FALSE;
12048     alarmSounded = FALSE;
12049     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12050
12051     GameEnds(EndOfFile, NULL, GE_PLAYER);
12052     if(appData.serverMovesName != NULL) {
12053         /* [HGM] prepare to make moves file for broadcasting */
12054         clock_t t = clock();
12055         if(serverMoves != NULL) fclose(serverMoves);
12056         serverMoves = fopen(appData.serverMovesName, "r");
12057         if(serverMoves != NULL) {
12058             fclose(serverMoves);
12059             /* delay 15 sec before overwriting, so all clients can see end */
12060             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12061         }
12062         serverMoves = fopen(appData.serverMovesName, "w");
12063     }
12064
12065     ExitAnalyzeMode();
12066     gameMode = BeginningOfGame;
12067     ModeHighlight();
12068     if(appData.icsActive) gameInfo.variant = VariantNormal;
12069     currentMove = forwardMostMove = backwardMostMove = 0;
12070     MarkTargetSquares(1);
12071     InitPosition(redraw);
12072     for (i = 0; i < MAX_MOVES; i++) {
12073         if (commentList[i] != NULL) {
12074             free(commentList[i]);
12075             commentList[i] = NULL;
12076         }
12077     }
12078     ResetClocks();
12079     timeRemaining[0][0] = whiteTimeRemaining;
12080     timeRemaining[1][0] = blackTimeRemaining;
12081
12082     if (first.pr == NoProc) {
12083         StartChessProgram(&first);
12084     }
12085     if (init) {
12086             InitChessProgram(&first, startedFromSetupPosition);
12087     }
12088     DisplayTitle("");
12089     DisplayMessage("", "");
12090     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12091     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12092     ClearMap();        // [HGM] exclude: invalidate map
12093 }
12094
12095 void
12096 AutoPlayGameLoop ()
12097 {
12098     for (;;) {
12099         if (!AutoPlayOneMove())
12100           return;
12101         if (matchMode || appData.timeDelay == 0)
12102           continue;
12103         if (appData.timeDelay < 0)
12104           return;
12105         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12106         break;
12107     }
12108 }
12109
12110 void
12111 AnalyzeNextGame()
12112 {
12113     ReloadGame(1); // next game
12114 }
12115
12116 int
12117 AutoPlayOneMove ()
12118 {
12119     int fromX, fromY, toX, toY;
12120
12121     if (appData.debugMode) {
12122       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12123     }
12124
12125     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12126       return FALSE;
12127
12128     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12129       pvInfoList[currentMove].depth = programStats.depth;
12130       pvInfoList[currentMove].score = programStats.score;
12131       pvInfoList[currentMove].time  = 0;
12132       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12133       else { // append analysis of final position as comment
12134         char buf[MSG_SIZ];
12135         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12136         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12137       }
12138       programStats.depth = 0;
12139     }
12140
12141     if (currentMove >= forwardMostMove) {
12142       if(gameMode == AnalyzeFile) {
12143           if(appData.loadGameIndex == -1) {
12144             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12145           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12146           } else {
12147           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12148         }
12149       }
12150 //      gameMode = EndOfGame;
12151 //      ModeHighlight();
12152
12153       /* [AS] Clear current move marker at the end of a game */
12154       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12155
12156       return FALSE;
12157     }
12158
12159     toX = moveList[currentMove][2] - AAA;
12160     toY = moveList[currentMove][3] - ONE;
12161
12162     if (moveList[currentMove][1] == '@') {
12163         if (appData.highlightLastMove) {
12164             SetHighlights(-1, -1, toX, toY);
12165         }
12166     } else {
12167         fromX = moveList[currentMove][0] - AAA;
12168         fromY = moveList[currentMove][1] - ONE;
12169
12170         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12171
12172         if(moveList[currentMove][4] == ';') { // multi-leg
12173             killX = moveList[currentMove][5] - AAA;
12174             killY = moveList[currentMove][6] - ONE;
12175         }
12176         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12177         killX = killY = -1;
12178
12179         if (appData.highlightLastMove) {
12180             SetHighlights(fromX, fromY, toX, toY);
12181         }
12182     }
12183     DisplayMove(currentMove);
12184     SendMoveToProgram(currentMove++, &first);
12185     DisplayBothClocks();
12186     DrawPosition(FALSE, boards[currentMove]);
12187     // [HGM] PV info: always display, routine tests if empty
12188     DisplayComment(currentMove - 1, commentList[currentMove]);
12189     return TRUE;
12190 }
12191
12192
12193 int
12194 LoadGameOneMove (ChessMove readAhead)
12195 {
12196     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12197     char promoChar = NULLCHAR;
12198     ChessMove moveType;
12199     char move[MSG_SIZ];
12200     char *p, *q;
12201
12202     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12203         gameMode != AnalyzeMode && gameMode != Training) {
12204         gameFileFP = NULL;
12205         return FALSE;
12206     }
12207
12208     yyboardindex = forwardMostMove;
12209     if (readAhead != EndOfFile) {
12210       moveType = readAhead;
12211     } else {
12212       if (gameFileFP == NULL)
12213           return FALSE;
12214       moveType = (ChessMove) Myylex();
12215     }
12216
12217     done = FALSE;
12218     switch (moveType) {
12219       case Comment:
12220         if (appData.debugMode)
12221           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12222         p = yy_text;
12223
12224         /* append the comment but don't display it */
12225         AppendComment(currentMove, p, FALSE);
12226         return TRUE;
12227
12228       case WhiteCapturesEnPassant:
12229       case BlackCapturesEnPassant:
12230       case WhitePromotion:
12231       case BlackPromotion:
12232       case WhiteNonPromotion:
12233       case BlackNonPromotion:
12234       case NormalMove:
12235       case FirstLeg:
12236       case WhiteKingSideCastle:
12237       case WhiteQueenSideCastle:
12238       case BlackKingSideCastle:
12239       case BlackQueenSideCastle:
12240       case WhiteKingSideCastleWild:
12241       case WhiteQueenSideCastleWild:
12242       case BlackKingSideCastleWild:
12243       case BlackQueenSideCastleWild:
12244       /* PUSH Fabien */
12245       case WhiteHSideCastleFR:
12246       case WhiteASideCastleFR:
12247       case BlackHSideCastleFR:
12248       case BlackASideCastleFR:
12249       /* POP Fabien */
12250         if (appData.debugMode)
12251           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12252         fromX = currentMoveString[0] - AAA;
12253         fromY = currentMoveString[1] - ONE;
12254         toX = currentMoveString[2] - AAA;
12255         toY = currentMoveString[3] - ONE;
12256         promoChar = currentMoveString[4];
12257         if(promoChar == ';') promoChar = currentMoveString[7];
12258         break;
12259
12260       case WhiteDrop:
12261       case BlackDrop:
12262         if (appData.debugMode)
12263           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12264         fromX = moveType == WhiteDrop ?
12265           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12266         (int) CharToPiece(ToLower(currentMoveString[0]));
12267         fromY = DROP_RANK;
12268         toX = currentMoveString[2] - AAA;
12269         toY = currentMoveString[3] - ONE;
12270         break;
12271
12272       case WhiteWins:
12273       case BlackWins:
12274       case GameIsDrawn:
12275       case GameUnfinished:
12276         if (appData.debugMode)
12277           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12278         p = strchr(yy_text, '{');
12279         if (p == NULL) p = strchr(yy_text, '(');
12280         if (p == NULL) {
12281             p = yy_text;
12282             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12283         } else {
12284             q = strchr(p, *p == '{' ? '}' : ')');
12285             if (q != NULL) *q = NULLCHAR;
12286             p++;
12287         }
12288         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12289         GameEnds(moveType, p, GE_FILE);
12290         done = TRUE;
12291         if (cmailMsgLoaded) {
12292             ClearHighlights();
12293             flipView = WhiteOnMove(currentMove);
12294             if (moveType == GameUnfinished) flipView = !flipView;
12295             if (appData.debugMode)
12296               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12297         }
12298         break;
12299
12300       case EndOfFile:
12301         if (appData.debugMode)
12302           fprintf(debugFP, "Parser hit end of file\n");
12303         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12304           case MT_NONE:
12305           case MT_CHECK:
12306             break;
12307           case MT_CHECKMATE:
12308           case MT_STAINMATE:
12309             if (WhiteOnMove(currentMove)) {
12310                 GameEnds(BlackWins, "Black mates", GE_FILE);
12311             } else {
12312                 GameEnds(WhiteWins, "White mates", GE_FILE);
12313             }
12314             break;
12315           case MT_STALEMATE:
12316             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12317             break;
12318         }
12319         done = TRUE;
12320         break;
12321
12322       case MoveNumberOne:
12323         if (lastLoadGameStart == GNUChessGame) {
12324             /* GNUChessGames have numbers, but they aren't move numbers */
12325             if (appData.debugMode)
12326               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12327                       yy_text, (int) moveType);
12328             return LoadGameOneMove(EndOfFile); /* tail recursion */
12329         }
12330         /* else fall thru */
12331
12332       case XBoardGame:
12333       case GNUChessGame:
12334       case PGNTag:
12335         /* Reached start of next game in file */
12336         if (appData.debugMode)
12337           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12338         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12339           case MT_NONE:
12340           case MT_CHECK:
12341             break;
12342           case MT_CHECKMATE:
12343           case MT_STAINMATE:
12344             if (WhiteOnMove(currentMove)) {
12345                 GameEnds(BlackWins, "Black mates", GE_FILE);
12346             } else {
12347                 GameEnds(WhiteWins, "White mates", GE_FILE);
12348             }
12349             break;
12350           case MT_STALEMATE:
12351             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12352             break;
12353         }
12354         done = TRUE;
12355         break;
12356
12357       case PositionDiagram:     /* should not happen; ignore */
12358       case ElapsedTime:         /* ignore */
12359       case NAG:                 /* ignore */
12360         if (appData.debugMode)
12361           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12362                   yy_text, (int) moveType);
12363         return LoadGameOneMove(EndOfFile); /* tail recursion */
12364
12365       case IllegalMove:
12366         if (appData.testLegality) {
12367             if (appData.debugMode)
12368               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12369             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12370                     (forwardMostMove / 2) + 1,
12371                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12372             DisplayError(move, 0);
12373             done = TRUE;
12374         } else {
12375             if (appData.debugMode)
12376               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12377                       yy_text, currentMoveString);
12378             if(currentMoveString[1] == '@') {
12379                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12380                 fromY = DROP_RANK;
12381             } else {
12382                 fromX = currentMoveString[0] - AAA;
12383                 fromY = currentMoveString[1] - ONE;
12384             }
12385             toX = currentMoveString[2] - AAA;
12386             toY = currentMoveString[3] - ONE;
12387             promoChar = currentMoveString[4];
12388         }
12389         break;
12390
12391       case AmbiguousMove:
12392         if (appData.debugMode)
12393           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12394         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12395                 (forwardMostMove / 2) + 1,
12396                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12397         DisplayError(move, 0);
12398         done = TRUE;
12399         break;
12400
12401       default:
12402       case ImpossibleMove:
12403         if (appData.debugMode)
12404           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12405         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12406                 (forwardMostMove / 2) + 1,
12407                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12408         DisplayError(move, 0);
12409         done = TRUE;
12410         break;
12411     }
12412
12413     if (done) {
12414         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12415             DrawPosition(FALSE, boards[currentMove]);
12416             DisplayBothClocks();
12417             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12418               DisplayComment(currentMove - 1, commentList[currentMove]);
12419         }
12420         (void) StopLoadGameTimer();
12421         gameFileFP = NULL;
12422         cmailOldMove = forwardMostMove;
12423         return FALSE;
12424     } else {
12425         /* currentMoveString is set as a side-effect of yylex */
12426
12427         thinkOutput[0] = NULLCHAR;
12428         MakeMove(fromX, fromY, toX, toY, promoChar);
12429         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12430         currentMove = forwardMostMove;
12431         return TRUE;
12432     }
12433 }
12434
12435 /* Load the nth game from the given file */
12436 int
12437 LoadGameFromFile (char *filename, int n, char *title, int useList)
12438 {
12439     FILE *f;
12440     char buf[MSG_SIZ];
12441
12442     if (strcmp(filename, "-") == 0) {
12443         f = stdin;
12444         title = "stdin";
12445     } else {
12446         f = fopen(filename, "rb");
12447         if (f == NULL) {
12448           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12449             DisplayError(buf, errno);
12450             return FALSE;
12451         }
12452     }
12453     if (fseek(f, 0, 0) == -1) {
12454         /* f is not seekable; probably a pipe */
12455         useList = FALSE;
12456     }
12457     if (useList && n == 0) {
12458         int error = GameListBuild(f);
12459         if (error) {
12460             DisplayError(_("Cannot build game list"), error);
12461         } else if (!ListEmpty(&gameList) &&
12462                    ((ListGame *) gameList.tailPred)->number > 1) {
12463             GameListPopUp(f, title);
12464             return TRUE;
12465         }
12466         GameListDestroy();
12467         n = 1;
12468     }
12469     if (n == 0) n = 1;
12470     return LoadGame(f, n, title, FALSE);
12471 }
12472
12473
12474 void
12475 MakeRegisteredMove ()
12476 {
12477     int fromX, fromY, toX, toY;
12478     char promoChar;
12479     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12480         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12481           case CMAIL_MOVE:
12482           case CMAIL_DRAW:
12483             if (appData.debugMode)
12484               fprintf(debugFP, "Restoring %s for game %d\n",
12485                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12486
12487             thinkOutput[0] = NULLCHAR;
12488             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12489             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12490             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12491             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12492             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12493             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12494             MakeMove(fromX, fromY, toX, toY, promoChar);
12495             ShowMove(fromX, fromY, toX, toY);
12496
12497             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12498               case MT_NONE:
12499               case MT_CHECK:
12500                 break;
12501
12502               case MT_CHECKMATE:
12503               case MT_STAINMATE:
12504                 if (WhiteOnMove(currentMove)) {
12505                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12506                 } else {
12507                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12508                 }
12509                 break;
12510
12511               case MT_STALEMATE:
12512                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12513                 break;
12514             }
12515
12516             break;
12517
12518           case CMAIL_RESIGN:
12519             if (WhiteOnMove(currentMove)) {
12520                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12521             } else {
12522                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12523             }
12524             break;
12525
12526           case CMAIL_ACCEPT:
12527             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12528             break;
12529
12530           default:
12531             break;
12532         }
12533     }
12534
12535     return;
12536 }
12537
12538 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12539 int
12540 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12541 {
12542     int retVal;
12543
12544     if (gameNumber > nCmailGames) {
12545         DisplayError(_("No more games in this message"), 0);
12546         return FALSE;
12547     }
12548     if (f == lastLoadGameFP) {
12549         int offset = gameNumber - lastLoadGameNumber;
12550         if (offset == 0) {
12551             cmailMsg[0] = NULLCHAR;
12552             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12553                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12554                 nCmailMovesRegistered--;
12555             }
12556             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12557             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12558                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12559             }
12560         } else {
12561             if (! RegisterMove()) return FALSE;
12562         }
12563     }
12564
12565     retVal = LoadGame(f, gameNumber, title, useList);
12566
12567     /* Make move registered during previous look at this game, if any */
12568     MakeRegisteredMove();
12569
12570     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12571         commentList[currentMove]
12572           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12573         DisplayComment(currentMove - 1, commentList[currentMove]);
12574     }
12575
12576     return retVal;
12577 }
12578
12579 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12580 int
12581 ReloadGame (int offset)
12582 {
12583     int gameNumber = lastLoadGameNumber + offset;
12584     if (lastLoadGameFP == NULL) {
12585         DisplayError(_("No game has been loaded yet"), 0);
12586         return FALSE;
12587     }
12588     if (gameNumber <= 0) {
12589         DisplayError(_("Can't back up any further"), 0);
12590         return FALSE;
12591     }
12592     if (cmailMsgLoaded) {
12593         return CmailLoadGame(lastLoadGameFP, gameNumber,
12594                              lastLoadGameTitle, lastLoadGameUseList);
12595     } else {
12596         return LoadGame(lastLoadGameFP, gameNumber,
12597                         lastLoadGameTitle, lastLoadGameUseList);
12598     }
12599 }
12600
12601 int keys[EmptySquare+1];
12602
12603 int
12604 PositionMatches (Board b1, Board b2)
12605 {
12606     int r, f, sum=0;
12607     switch(appData.searchMode) {
12608         case 1: return CompareWithRights(b1, b2);
12609         case 2:
12610             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12611                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12612             }
12613             return TRUE;
12614         case 3:
12615             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12616               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12617                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12618             }
12619             return sum==0;
12620         case 4:
12621             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12622                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12623             }
12624             return sum==0;
12625     }
12626     return TRUE;
12627 }
12628
12629 #define Q_PROMO  4
12630 #define Q_EP     3
12631 #define Q_BCASTL 2
12632 #define Q_WCASTL 1
12633
12634 int pieceList[256], quickBoard[256];
12635 ChessSquare pieceType[256] = { EmptySquare };
12636 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12637 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12638 int soughtTotal, turn;
12639 Boolean epOK, flipSearch;
12640
12641 typedef struct {
12642     unsigned char piece, to;
12643 } Move;
12644
12645 #define DSIZE (250000)
12646
12647 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12648 Move *moveDatabase = initialSpace;
12649 unsigned int movePtr, dataSize = DSIZE;
12650
12651 int
12652 MakePieceList (Board board, int *counts)
12653 {
12654     int r, f, n=Q_PROMO, total=0;
12655     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12656     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12657         int sq = f + (r<<4);
12658         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12659             quickBoard[sq] = ++n;
12660             pieceList[n] = sq;
12661             pieceType[n] = board[r][f];
12662             counts[board[r][f]]++;
12663             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12664             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12665             total++;
12666         }
12667     }
12668     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12669     return total;
12670 }
12671
12672 void
12673 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12674 {
12675     int sq = fromX + (fromY<<4);
12676     int piece = quickBoard[sq], rook;
12677     quickBoard[sq] = 0;
12678     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12679     if(piece == pieceList[1] && fromY == toY) {
12680       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12681         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12682         moveDatabase[movePtr++].piece = Q_WCASTL;
12683         quickBoard[sq] = piece;
12684         piece = quickBoard[from]; quickBoard[from] = 0;
12685         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12686       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12687         quickBoard[sq] = 0; // remove Rook
12688         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12689         moveDatabase[movePtr++].piece = Q_WCASTL;
12690         quickBoard[sq] = pieceList[1]; // put King
12691         piece = rook;
12692         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12693       }
12694     } else
12695     if(piece == pieceList[2] && fromY == toY) {
12696       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12697         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12698         moveDatabase[movePtr++].piece = Q_BCASTL;
12699         quickBoard[sq] = piece;
12700         piece = quickBoard[from]; quickBoard[from] = 0;
12701         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12702       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12703         quickBoard[sq] = 0; // remove Rook
12704         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12705         moveDatabase[movePtr++].piece = Q_BCASTL;
12706         quickBoard[sq] = pieceList[2]; // put King
12707         piece = rook;
12708         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12709       }
12710     } else
12711     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12712         quickBoard[(fromY<<4)+toX] = 0;
12713         moveDatabase[movePtr].piece = Q_EP;
12714         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12715         moveDatabase[movePtr].to = sq;
12716     } else
12717     if(promoPiece != pieceType[piece]) {
12718         moveDatabase[movePtr++].piece = Q_PROMO;
12719         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12720     }
12721     moveDatabase[movePtr].piece = piece;
12722     quickBoard[sq] = piece;
12723     movePtr++;
12724 }
12725
12726 int
12727 PackGame (Board board)
12728 {
12729     Move *newSpace = NULL;
12730     moveDatabase[movePtr].piece = 0; // terminate previous game
12731     if(movePtr > dataSize) {
12732         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12733         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12734         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12735         if(newSpace) {
12736             int i;
12737             Move *p = moveDatabase, *q = newSpace;
12738             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12739             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12740             moveDatabase = newSpace;
12741         } else { // calloc failed, we must be out of memory. Too bad...
12742             dataSize = 0; // prevent calloc events for all subsequent games
12743             return 0;     // and signal this one isn't cached
12744         }
12745     }
12746     movePtr++;
12747     MakePieceList(board, counts);
12748     return movePtr;
12749 }
12750
12751 int
12752 QuickCompare (Board board, int *minCounts, int *maxCounts)
12753 {   // compare according to search mode
12754     int r, f;
12755     switch(appData.searchMode)
12756     {
12757       case 1: // exact position match
12758         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12759         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12760             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12761         }
12762         break;
12763       case 2: // can have extra material on empty squares
12764         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12765             if(board[r][f] == EmptySquare) continue;
12766             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12767         }
12768         break;
12769       case 3: // material with exact Pawn structure
12770         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12771             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12772             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12773         } // fall through to material comparison
12774       case 4: // exact material
12775         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12776         break;
12777       case 6: // material range with given imbalance
12778         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12779         // fall through to range comparison
12780       case 5: // material range
12781         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12782     }
12783     return TRUE;
12784 }
12785
12786 int
12787 QuickScan (Board board, Move *move)
12788 {   // reconstruct game,and compare all positions in it
12789     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12790     do {
12791         int piece = move->piece;
12792         int to = move->to, from = pieceList[piece];
12793         if(found < 0) { // if already found just scan to game end for final piece count
12794           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12795            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12796            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12797                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12798             ) {
12799             static int lastCounts[EmptySquare+1];
12800             int i;
12801             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12802             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12803           } else stretch = 0;
12804           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12805           if(found >= 0 && !appData.minPieces) return found;
12806         }
12807         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12808           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12809           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12810             piece = (++move)->piece;
12811             from = pieceList[piece];
12812             counts[pieceType[piece]]--;
12813             pieceType[piece] = (ChessSquare) move->to;
12814             counts[move->to]++;
12815           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12816             counts[pieceType[quickBoard[to]]]--;
12817             quickBoard[to] = 0; total--;
12818             move++;
12819             continue;
12820           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12821             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12822             from  = pieceList[piece]; // so this must be King
12823             quickBoard[from] = 0;
12824             pieceList[piece] = to;
12825             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12826             quickBoard[from] = 0; // rook
12827             quickBoard[to] = piece;
12828             to = move->to; piece = move->piece;
12829             goto aftercastle;
12830           }
12831         }
12832         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12833         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12834         quickBoard[from] = 0;
12835       aftercastle:
12836         quickBoard[to] = piece;
12837         pieceList[piece] = to;
12838         cnt++; turn ^= 3;
12839         move++;
12840     } while(1);
12841 }
12842
12843 void
12844 InitSearch ()
12845 {
12846     int r, f;
12847     flipSearch = FALSE;
12848     CopyBoard(soughtBoard, boards[currentMove]);
12849     soughtTotal = MakePieceList(soughtBoard, maxSought);
12850     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12851     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12852     CopyBoard(reverseBoard, boards[currentMove]);
12853     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12854         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12855         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12856         reverseBoard[r][f] = piece;
12857     }
12858     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12859     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12860     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12861                  || (boards[currentMove][CASTLING][2] == NoRights ||
12862                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12863                  && (boards[currentMove][CASTLING][5] == NoRights ||
12864                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12865       ) {
12866         flipSearch = TRUE;
12867         CopyBoard(flipBoard, soughtBoard);
12868         CopyBoard(rotateBoard, reverseBoard);
12869         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12870             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12871             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12872         }
12873     }
12874     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12875     if(appData.searchMode >= 5) {
12876         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12877         MakePieceList(soughtBoard, minSought);
12878         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12879     }
12880     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12881         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12882 }
12883
12884 GameInfo dummyInfo;
12885 static int creatingBook;
12886
12887 int
12888 GameContainsPosition (FILE *f, ListGame *lg)
12889 {
12890     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12891     int fromX, fromY, toX, toY;
12892     char promoChar;
12893     static int initDone=FALSE;
12894
12895     // weed out games based on numerical tag comparison
12896     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12897     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12898     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12899     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12900     if(!initDone) {
12901         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12902         initDone = TRUE;
12903     }
12904     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12905     else CopyBoard(boards[scratch], initialPosition); // default start position
12906     if(lg->moves) {
12907         turn = btm + 1;
12908         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12909         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12910     }
12911     if(btm) plyNr++;
12912     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12913     fseek(f, lg->offset, 0);
12914     yynewfile(f);
12915     while(1) {
12916         yyboardindex = scratch;
12917         quickFlag = plyNr+1;
12918         next = Myylex();
12919         quickFlag = 0;
12920         switch(next) {
12921             case PGNTag:
12922                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12923             default:
12924                 continue;
12925
12926             case XBoardGame:
12927             case GNUChessGame:
12928                 if(plyNr) return -1; // after we have seen moves, this is for new game
12929               continue;
12930
12931             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12932             case ImpossibleMove:
12933             case WhiteWins: // game ends here with these four
12934             case BlackWins:
12935             case GameIsDrawn:
12936             case GameUnfinished:
12937                 return -1;
12938
12939             case IllegalMove:
12940                 if(appData.testLegality) return -1;
12941             case WhiteCapturesEnPassant:
12942             case BlackCapturesEnPassant:
12943             case WhitePromotion:
12944             case BlackPromotion:
12945             case WhiteNonPromotion:
12946             case BlackNonPromotion:
12947             case NormalMove:
12948             case FirstLeg:
12949             case WhiteKingSideCastle:
12950             case WhiteQueenSideCastle:
12951             case BlackKingSideCastle:
12952             case BlackQueenSideCastle:
12953             case WhiteKingSideCastleWild:
12954             case WhiteQueenSideCastleWild:
12955             case BlackKingSideCastleWild:
12956             case BlackQueenSideCastleWild:
12957             case WhiteHSideCastleFR:
12958             case WhiteASideCastleFR:
12959             case BlackHSideCastleFR:
12960             case BlackASideCastleFR:
12961                 fromX = currentMoveString[0] - AAA;
12962                 fromY = currentMoveString[1] - ONE;
12963                 toX = currentMoveString[2] - AAA;
12964                 toY = currentMoveString[3] - ONE;
12965                 promoChar = currentMoveString[4];
12966                 break;
12967             case WhiteDrop:
12968             case BlackDrop:
12969                 fromX = next == WhiteDrop ?
12970                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12971                   (int) CharToPiece(ToLower(currentMoveString[0]));
12972                 fromY = DROP_RANK;
12973                 toX = currentMoveString[2] - AAA;
12974                 toY = currentMoveString[3] - ONE;
12975                 promoChar = 0;
12976                 break;
12977         }
12978         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12979         plyNr++;
12980         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12981         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12982         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12983         if(appData.findMirror) {
12984             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12985             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12986         }
12987     }
12988 }
12989
12990 /* Load the nth game from open file f */
12991 int
12992 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12993 {
12994     ChessMove cm;
12995     char buf[MSG_SIZ];
12996     int gn = gameNumber;
12997     ListGame *lg = NULL;
12998     int numPGNTags = 0, i;
12999     int err, pos = -1;
13000     GameMode oldGameMode;
13001     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13002     char oldName[MSG_SIZ];
13003
13004     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13005
13006     if (appData.debugMode)
13007         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13008
13009     if (gameMode == Training )
13010         SetTrainingModeOff();
13011
13012     oldGameMode = gameMode;
13013     if (gameMode != BeginningOfGame) {
13014       Reset(FALSE, TRUE);
13015     }
13016     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13017
13018     gameFileFP = f;
13019     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13020         fclose(lastLoadGameFP);
13021     }
13022
13023     if (useList) {
13024         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13025
13026         if (lg) {
13027             fseek(f, lg->offset, 0);
13028             GameListHighlight(gameNumber);
13029             pos = lg->position;
13030             gn = 1;
13031         }
13032         else {
13033             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13034               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13035             else
13036             DisplayError(_("Game number out of range"), 0);
13037             return FALSE;
13038         }
13039     } else {
13040         GameListDestroy();
13041         if (fseek(f, 0, 0) == -1) {
13042             if (f == lastLoadGameFP ?
13043                 gameNumber == lastLoadGameNumber + 1 :
13044                 gameNumber == 1) {
13045                 gn = 1;
13046             } else {
13047                 DisplayError(_("Can't seek on game file"), 0);
13048                 return FALSE;
13049             }
13050         }
13051     }
13052     lastLoadGameFP = f;
13053     lastLoadGameNumber = gameNumber;
13054     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13055     lastLoadGameUseList = useList;
13056
13057     yynewfile(f);
13058
13059     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13060       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13061                 lg->gameInfo.black);
13062             DisplayTitle(buf);
13063     } else if (*title != NULLCHAR) {
13064         if (gameNumber > 1) {
13065           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13066             DisplayTitle(buf);
13067         } else {
13068             DisplayTitle(title);
13069         }
13070     }
13071
13072     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13073         gameMode = PlayFromGameFile;
13074         ModeHighlight();
13075     }
13076
13077     currentMove = forwardMostMove = backwardMostMove = 0;
13078     CopyBoard(boards[0], initialPosition);
13079     StopClocks();
13080
13081     /*
13082      * Skip the first gn-1 games in the file.
13083      * Also skip over anything that precedes an identifiable
13084      * start of game marker, to avoid being confused by
13085      * garbage at the start of the file.  Currently
13086      * recognized start of game markers are the move number "1",
13087      * the pattern "gnuchess .* game", the pattern
13088      * "^[#;%] [^ ]* game file", and a PGN tag block.
13089      * A game that starts with one of the latter two patterns
13090      * will also have a move number 1, possibly
13091      * following a position diagram.
13092      * 5-4-02: Let's try being more lenient and allowing a game to
13093      * start with an unnumbered move.  Does that break anything?
13094      */
13095     cm = lastLoadGameStart = EndOfFile;
13096     while (gn > 0) {
13097         yyboardindex = forwardMostMove;
13098         cm = (ChessMove) Myylex();
13099         switch (cm) {
13100           case EndOfFile:
13101             if (cmailMsgLoaded) {
13102                 nCmailGames = CMAIL_MAX_GAMES - gn;
13103             } else {
13104                 Reset(TRUE, TRUE);
13105                 DisplayError(_("Game not found in file"), 0);
13106             }
13107             return FALSE;
13108
13109           case GNUChessGame:
13110           case XBoardGame:
13111             gn--;
13112             lastLoadGameStart = cm;
13113             break;
13114
13115           case MoveNumberOne:
13116             switch (lastLoadGameStart) {
13117               case GNUChessGame:
13118               case XBoardGame:
13119               case PGNTag:
13120                 break;
13121               case MoveNumberOne:
13122               case EndOfFile:
13123                 gn--;           /* count this game */
13124                 lastLoadGameStart = cm;
13125                 break;
13126               default:
13127                 /* impossible */
13128                 break;
13129             }
13130             break;
13131
13132           case PGNTag:
13133             switch (lastLoadGameStart) {
13134               case GNUChessGame:
13135               case PGNTag:
13136               case MoveNumberOne:
13137               case EndOfFile:
13138                 gn--;           /* count this game */
13139                 lastLoadGameStart = cm;
13140                 break;
13141               case XBoardGame:
13142                 lastLoadGameStart = cm; /* game counted already */
13143                 break;
13144               default:
13145                 /* impossible */
13146                 break;
13147             }
13148             if (gn > 0) {
13149                 do {
13150                     yyboardindex = forwardMostMove;
13151                     cm = (ChessMove) Myylex();
13152                 } while (cm == PGNTag || cm == Comment);
13153             }
13154             break;
13155
13156           case WhiteWins:
13157           case BlackWins:
13158           case GameIsDrawn:
13159             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13160                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13161                     != CMAIL_OLD_RESULT) {
13162                     nCmailResults ++ ;
13163                     cmailResult[  CMAIL_MAX_GAMES
13164                                 - gn - 1] = CMAIL_OLD_RESULT;
13165                 }
13166             }
13167             break;
13168
13169           case NormalMove:
13170           case FirstLeg:
13171             /* Only a NormalMove can be at the start of a game
13172              * without a position diagram. */
13173             if (lastLoadGameStart == EndOfFile ) {
13174               gn--;
13175               lastLoadGameStart = MoveNumberOne;
13176             }
13177             break;
13178
13179           default:
13180             break;
13181         }
13182     }
13183
13184     if (appData.debugMode)
13185       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13186
13187     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13188
13189     if (cm == XBoardGame) {
13190         /* Skip any header junk before position diagram and/or move 1 */
13191         for (;;) {
13192             yyboardindex = forwardMostMove;
13193             cm = (ChessMove) Myylex();
13194
13195             if (cm == EndOfFile ||
13196                 cm == GNUChessGame || cm == XBoardGame) {
13197                 /* Empty game; pretend end-of-file and handle later */
13198                 cm = EndOfFile;
13199                 break;
13200             }
13201
13202             if (cm == MoveNumberOne || cm == PositionDiagram ||
13203                 cm == PGNTag || cm == Comment)
13204               break;
13205         }
13206     } else if (cm == GNUChessGame) {
13207         if (gameInfo.event != NULL) {
13208             free(gameInfo.event);
13209         }
13210         gameInfo.event = StrSave(yy_text);
13211     }
13212
13213     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13214     while (cm == PGNTag) {
13215         if (appData.debugMode)
13216           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13217         err = ParsePGNTag(yy_text, &gameInfo);
13218         if (!err) numPGNTags++;
13219
13220         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13221         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13222             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13223             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13224             InitPosition(TRUE);
13225             oldVariant = gameInfo.variant;
13226             if (appData.debugMode)
13227               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13228         }
13229
13230
13231         if (gameInfo.fen != NULL) {
13232           Board initial_position;
13233           startedFromSetupPosition = TRUE;
13234           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13235             Reset(TRUE, TRUE);
13236             DisplayError(_("Bad FEN position in file"), 0);
13237             return FALSE;
13238           }
13239           CopyBoard(boards[0], initial_position);
13240           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13241             CopyBoard(initialPosition, initial_position);
13242           if (blackPlaysFirst) {
13243             currentMove = forwardMostMove = backwardMostMove = 1;
13244             CopyBoard(boards[1], initial_position);
13245             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13246             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13247             timeRemaining[0][1] = whiteTimeRemaining;
13248             timeRemaining[1][1] = blackTimeRemaining;
13249             if (commentList[0] != NULL) {
13250               commentList[1] = commentList[0];
13251               commentList[0] = NULL;
13252             }
13253           } else {
13254             currentMove = forwardMostMove = backwardMostMove = 0;
13255           }
13256           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13257           {   int i;
13258               initialRulePlies = FENrulePlies;
13259               for( i=0; i< nrCastlingRights; i++ )
13260                   initialRights[i] = initial_position[CASTLING][i];
13261           }
13262           yyboardindex = forwardMostMove;
13263           free(gameInfo.fen);
13264           gameInfo.fen = NULL;
13265         }
13266
13267         yyboardindex = forwardMostMove;
13268         cm = (ChessMove) Myylex();
13269
13270         /* Handle comments interspersed among the tags */
13271         while (cm == Comment) {
13272             char *p;
13273             if (appData.debugMode)
13274               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13275             p = yy_text;
13276             AppendComment(currentMove, p, FALSE);
13277             yyboardindex = forwardMostMove;
13278             cm = (ChessMove) Myylex();
13279         }
13280     }
13281
13282     /* don't rely on existence of Event tag since if game was
13283      * pasted from clipboard the Event tag may not exist
13284      */
13285     if (numPGNTags > 0){
13286         char *tags;
13287         if (gameInfo.variant == VariantNormal) {
13288           VariantClass v = StringToVariant(gameInfo.event);
13289           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13290           if(v < VariantShogi) gameInfo.variant = v;
13291         }
13292         if (!matchMode) {
13293           if( appData.autoDisplayTags ) {
13294             tags = PGNTags(&gameInfo);
13295             TagsPopUp(tags, CmailMsg());
13296             free(tags);
13297           }
13298         }
13299     } else {
13300         /* Make something up, but don't display it now */
13301         SetGameInfo();
13302         TagsPopDown();
13303     }
13304
13305     if (cm == PositionDiagram) {
13306         int i, j;
13307         char *p;
13308         Board initial_position;
13309
13310         if (appData.debugMode)
13311           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13312
13313         if (!startedFromSetupPosition) {
13314             p = yy_text;
13315             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13316               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13317                 switch (*p) {
13318                   case '{':
13319                   case '[':
13320                   case '-':
13321                   case ' ':
13322                   case '\t':
13323                   case '\n':
13324                   case '\r':
13325                     break;
13326                   default:
13327                     initial_position[i][j++] = CharToPiece(*p);
13328                     break;
13329                 }
13330             while (*p == ' ' || *p == '\t' ||
13331                    *p == '\n' || *p == '\r') p++;
13332
13333             if (strncmp(p, "black", strlen("black"))==0)
13334               blackPlaysFirst = TRUE;
13335             else
13336               blackPlaysFirst = FALSE;
13337             startedFromSetupPosition = TRUE;
13338
13339             CopyBoard(boards[0], initial_position);
13340             if (blackPlaysFirst) {
13341                 currentMove = forwardMostMove = backwardMostMove = 1;
13342                 CopyBoard(boards[1], initial_position);
13343                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13344                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13345                 timeRemaining[0][1] = whiteTimeRemaining;
13346                 timeRemaining[1][1] = blackTimeRemaining;
13347                 if (commentList[0] != NULL) {
13348                     commentList[1] = commentList[0];
13349                     commentList[0] = NULL;
13350                 }
13351             } else {
13352                 currentMove = forwardMostMove = backwardMostMove = 0;
13353             }
13354         }
13355         yyboardindex = forwardMostMove;
13356         cm = (ChessMove) Myylex();
13357     }
13358
13359   if(!creatingBook) {
13360     if (first.pr == NoProc) {
13361         StartChessProgram(&first);
13362     }
13363     InitChessProgram(&first, FALSE);
13364     if(gameInfo.variant == VariantUnknown && *oldName) {
13365         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13366         gameInfo.variant = v;
13367     }
13368     SendToProgram("force\n", &first);
13369     if (startedFromSetupPosition) {
13370         SendBoard(&first, forwardMostMove);
13371     if (appData.debugMode) {
13372         fprintf(debugFP, "Load Game\n");
13373     }
13374         DisplayBothClocks();
13375     }
13376   }
13377
13378     /* [HGM] server: flag to write setup moves in broadcast file as one */
13379     loadFlag = appData.suppressLoadMoves;
13380
13381     while (cm == Comment) {
13382         char *p;
13383         if (appData.debugMode)
13384           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13385         p = yy_text;
13386         AppendComment(currentMove, p, FALSE);
13387         yyboardindex = forwardMostMove;
13388         cm = (ChessMove) Myylex();
13389     }
13390
13391     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13392         cm == WhiteWins || cm == BlackWins ||
13393         cm == GameIsDrawn || cm == GameUnfinished) {
13394         DisplayMessage("", _("No moves in game"));
13395         if (cmailMsgLoaded) {
13396             if (appData.debugMode)
13397               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13398             ClearHighlights();
13399             flipView = FALSE;
13400         }
13401         DrawPosition(FALSE, boards[currentMove]);
13402         DisplayBothClocks();
13403         gameMode = EditGame;
13404         ModeHighlight();
13405         gameFileFP = NULL;
13406         cmailOldMove = 0;
13407         return TRUE;
13408     }
13409
13410     // [HGM] PV info: routine tests if comment empty
13411     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13412         DisplayComment(currentMove - 1, commentList[currentMove]);
13413     }
13414     if (!matchMode && appData.timeDelay != 0)
13415       DrawPosition(FALSE, boards[currentMove]);
13416
13417     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13418       programStats.ok_to_send = 1;
13419     }
13420
13421     /* if the first token after the PGN tags is a move
13422      * and not move number 1, retrieve it from the parser
13423      */
13424     if (cm != MoveNumberOne)
13425         LoadGameOneMove(cm);
13426
13427     /* load the remaining moves from the file */
13428     while (LoadGameOneMove(EndOfFile)) {
13429       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13430       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13431     }
13432
13433     /* rewind to the start of the game */
13434     currentMove = backwardMostMove;
13435
13436     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13437
13438     if (oldGameMode == AnalyzeFile) {
13439       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13440       AnalyzeFileEvent();
13441     } else
13442     if (oldGameMode == AnalyzeMode) {
13443       AnalyzeFileEvent();
13444     }
13445
13446     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13447         long int w, b; // [HGM] adjourn: restore saved clock times
13448         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13449         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13450             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13451             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13452         }
13453     }
13454
13455     if(creatingBook) return TRUE;
13456     if (!matchMode && pos > 0) {
13457         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13458     } else
13459     if (matchMode || appData.timeDelay == 0) {
13460       ToEndEvent();
13461     } else if (appData.timeDelay > 0) {
13462       AutoPlayGameLoop();
13463     }
13464
13465     if (appData.debugMode)
13466         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13467
13468     loadFlag = 0; /* [HGM] true game starts */
13469     return TRUE;
13470 }
13471
13472 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13473 int
13474 ReloadPosition (int offset)
13475 {
13476     int positionNumber = lastLoadPositionNumber + offset;
13477     if (lastLoadPositionFP == NULL) {
13478         DisplayError(_("No position has been loaded yet"), 0);
13479         return FALSE;
13480     }
13481     if (positionNumber <= 0) {
13482         DisplayError(_("Can't back up any further"), 0);
13483         return FALSE;
13484     }
13485     return LoadPosition(lastLoadPositionFP, positionNumber,
13486                         lastLoadPositionTitle);
13487 }
13488
13489 /* Load the nth position from the given file */
13490 int
13491 LoadPositionFromFile (char *filename, int n, char *title)
13492 {
13493     FILE *f;
13494     char buf[MSG_SIZ];
13495
13496     if (strcmp(filename, "-") == 0) {
13497         return LoadPosition(stdin, n, "stdin");
13498     } else {
13499         f = fopen(filename, "rb");
13500         if (f == NULL) {
13501             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13502             DisplayError(buf, errno);
13503             return FALSE;
13504         } else {
13505             return LoadPosition(f, n, title);
13506         }
13507     }
13508 }
13509
13510 /* Load the nth position from the given open file, and close it */
13511 int
13512 LoadPosition (FILE *f, int positionNumber, char *title)
13513 {
13514     char *p, line[MSG_SIZ];
13515     Board initial_position;
13516     int i, j, fenMode, pn;
13517
13518     if (gameMode == Training )
13519         SetTrainingModeOff();
13520
13521     if (gameMode != BeginningOfGame) {
13522         Reset(FALSE, TRUE);
13523     }
13524     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13525         fclose(lastLoadPositionFP);
13526     }
13527     if (positionNumber == 0) positionNumber = 1;
13528     lastLoadPositionFP = f;
13529     lastLoadPositionNumber = positionNumber;
13530     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13531     if (first.pr == NoProc && !appData.noChessProgram) {
13532       StartChessProgram(&first);
13533       InitChessProgram(&first, FALSE);
13534     }
13535     pn = positionNumber;
13536     if (positionNumber < 0) {
13537         /* Negative position number means to seek to that byte offset */
13538         if (fseek(f, -positionNumber, 0) == -1) {
13539             DisplayError(_("Can't seek on position file"), 0);
13540             return FALSE;
13541         };
13542         pn = 1;
13543     } else {
13544         if (fseek(f, 0, 0) == -1) {
13545             if (f == lastLoadPositionFP ?
13546                 positionNumber == lastLoadPositionNumber + 1 :
13547                 positionNumber == 1) {
13548                 pn = 1;
13549             } else {
13550                 DisplayError(_("Can't seek on position file"), 0);
13551                 return FALSE;
13552             }
13553         }
13554     }
13555     /* See if this file is FEN or old-style xboard */
13556     if (fgets(line, MSG_SIZ, f) == NULL) {
13557         DisplayError(_("Position not found in file"), 0);
13558         return FALSE;
13559     }
13560     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13561     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13562
13563     if (pn >= 2) {
13564         if (fenMode || line[0] == '#') pn--;
13565         while (pn > 0) {
13566             /* skip positions before number pn */
13567             if (fgets(line, MSG_SIZ, f) == NULL) {
13568                 Reset(TRUE, TRUE);
13569                 DisplayError(_("Position not found in file"), 0);
13570                 return FALSE;
13571             }
13572             if (fenMode || line[0] == '#') pn--;
13573         }
13574     }
13575
13576     if (fenMode) {
13577         char *p;
13578         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13579             DisplayError(_("Bad FEN position in file"), 0);
13580             return FALSE;
13581         }
13582         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13583             sscanf(p+4, "%[^;]", bestMove);
13584         } else *bestMove = NULLCHAR;
13585         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13586             sscanf(p+4, "%[^;]", avoidMove);
13587         } else *avoidMove = NULLCHAR;
13588     } else {
13589         (void) fgets(line, MSG_SIZ, f);
13590         (void) fgets(line, MSG_SIZ, f);
13591
13592         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13593             (void) fgets(line, MSG_SIZ, f);
13594             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13595                 if (*p == ' ')
13596                   continue;
13597                 initial_position[i][j++] = CharToPiece(*p);
13598             }
13599         }
13600
13601         blackPlaysFirst = FALSE;
13602         if (!feof(f)) {
13603             (void) fgets(line, MSG_SIZ, f);
13604             if (strncmp(line, "black", strlen("black"))==0)
13605               blackPlaysFirst = TRUE;
13606         }
13607     }
13608     startedFromSetupPosition = TRUE;
13609
13610     CopyBoard(boards[0], initial_position);
13611     if (blackPlaysFirst) {
13612         currentMove = forwardMostMove = backwardMostMove = 1;
13613         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13614         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13615         CopyBoard(boards[1], initial_position);
13616         DisplayMessage("", _("Black to play"));
13617     } else {
13618         currentMove = forwardMostMove = backwardMostMove = 0;
13619         DisplayMessage("", _("White to play"));
13620     }
13621     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13622     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13623         SendToProgram("force\n", &first);
13624         SendBoard(&first, forwardMostMove);
13625     }
13626     if (appData.debugMode) {
13627 int i, j;
13628   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13629   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13630         fprintf(debugFP, "Load Position\n");
13631     }
13632
13633     if (positionNumber > 1) {
13634       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13635         DisplayTitle(line);
13636     } else {
13637         DisplayTitle(title);
13638     }
13639     gameMode = EditGame;
13640     ModeHighlight();
13641     ResetClocks();
13642     timeRemaining[0][1] = whiteTimeRemaining;
13643     timeRemaining[1][1] = blackTimeRemaining;
13644     DrawPosition(FALSE, boards[currentMove]);
13645
13646     return TRUE;
13647 }
13648
13649
13650 void
13651 CopyPlayerNameIntoFileName (char **dest, char *src)
13652 {
13653     while (*src != NULLCHAR && *src != ',') {
13654         if (*src == ' ') {
13655             *(*dest)++ = '_';
13656             src++;
13657         } else {
13658             *(*dest)++ = *src++;
13659         }
13660     }
13661 }
13662
13663 char *
13664 DefaultFileName (char *ext)
13665 {
13666     static char def[MSG_SIZ];
13667     char *p;
13668
13669     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13670         p = def;
13671         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13672         *p++ = '-';
13673         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13674         *p++ = '.';
13675         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13676     } else {
13677         def[0] = NULLCHAR;
13678     }
13679     return def;
13680 }
13681
13682 /* Save the current game to the given file */
13683 int
13684 SaveGameToFile (char *filename, int append)
13685 {
13686     FILE *f;
13687     char buf[MSG_SIZ];
13688     int result, i, t,tot=0;
13689
13690     if (strcmp(filename, "-") == 0) {
13691         return SaveGame(stdout, 0, NULL);
13692     } else {
13693         for(i=0; i<10; i++) { // upto 10 tries
13694              f = fopen(filename, append ? "a" : "w");
13695              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13696              if(f || errno != 13) break;
13697              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13698              tot += t;
13699         }
13700         if (f == NULL) {
13701             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13702             DisplayError(buf, errno);
13703             return FALSE;
13704         } else {
13705             safeStrCpy(buf, lastMsg, MSG_SIZ);
13706             DisplayMessage(_("Waiting for access to save file"), "");
13707             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13708             DisplayMessage(_("Saving game"), "");
13709             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13710             result = SaveGame(f, 0, NULL);
13711             DisplayMessage(buf, "");
13712             return result;
13713         }
13714     }
13715 }
13716
13717 char *
13718 SavePart (char *str)
13719 {
13720     static char buf[MSG_SIZ];
13721     char *p;
13722
13723     p = strchr(str, ' ');
13724     if (p == NULL) return str;
13725     strncpy(buf, str, p - str);
13726     buf[p - str] = NULLCHAR;
13727     return buf;
13728 }
13729
13730 #define PGN_MAX_LINE 75
13731
13732 #define PGN_SIDE_WHITE  0
13733 #define PGN_SIDE_BLACK  1
13734
13735 static int
13736 FindFirstMoveOutOfBook (int side)
13737 {
13738     int result = -1;
13739
13740     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13741         int index = backwardMostMove;
13742         int has_book_hit = 0;
13743
13744         if( (index % 2) != side ) {
13745             index++;
13746         }
13747
13748         while( index < forwardMostMove ) {
13749             /* Check to see if engine is in book */
13750             int depth = pvInfoList[index].depth;
13751             int score = pvInfoList[index].score;
13752             int in_book = 0;
13753
13754             if( depth <= 2 ) {
13755                 in_book = 1;
13756             }
13757             else if( score == 0 && depth == 63 ) {
13758                 in_book = 1; /* Zappa */
13759             }
13760             else if( score == 2 && depth == 99 ) {
13761                 in_book = 1; /* Abrok */
13762             }
13763
13764             has_book_hit += in_book;
13765
13766             if( ! in_book ) {
13767                 result = index;
13768
13769                 break;
13770             }
13771
13772             index += 2;
13773         }
13774     }
13775
13776     return result;
13777 }
13778
13779 void
13780 GetOutOfBookInfo (char * buf)
13781 {
13782     int oob[2];
13783     int i;
13784     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13785
13786     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13787     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13788
13789     *buf = '\0';
13790
13791     if( oob[0] >= 0 || oob[1] >= 0 ) {
13792         for( i=0; i<2; i++ ) {
13793             int idx = oob[i];
13794
13795             if( idx >= 0 ) {
13796                 if( i > 0 && oob[0] >= 0 ) {
13797                     strcat( buf, "   " );
13798                 }
13799
13800                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13801                 sprintf( buf+strlen(buf), "%s%.2f",
13802                     pvInfoList[idx].score >= 0 ? "+" : "",
13803                     pvInfoList[idx].score / 100.0 );
13804             }
13805         }
13806     }
13807 }
13808
13809 /* Save game in PGN style */
13810 static void
13811 SaveGamePGN2 (FILE *f)
13812 {
13813     int i, offset, linelen, newblock;
13814 //    char *movetext;
13815     char numtext[32];
13816     int movelen, numlen, blank;
13817     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13818
13819     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13820
13821     PrintPGNTags(f, &gameInfo);
13822
13823     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13824
13825     if (backwardMostMove > 0 || startedFromSetupPosition) {
13826         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13827         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13828         fprintf(f, "\n{--------------\n");
13829         PrintPosition(f, backwardMostMove);
13830         fprintf(f, "--------------}\n");
13831         free(fen);
13832     }
13833     else {
13834         /* [AS] Out of book annotation */
13835         if( appData.saveOutOfBookInfo ) {
13836             char buf[64];
13837
13838             GetOutOfBookInfo( buf );
13839
13840             if( buf[0] != '\0' ) {
13841                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13842             }
13843         }
13844
13845         fprintf(f, "\n");
13846     }
13847
13848     i = backwardMostMove;
13849     linelen = 0;
13850     newblock = TRUE;
13851
13852     while (i < forwardMostMove) {
13853         /* Print comments preceding this move */
13854         if (commentList[i] != NULL) {
13855             if (linelen > 0) fprintf(f, "\n");
13856             fprintf(f, "%s", commentList[i]);
13857             linelen = 0;
13858             newblock = TRUE;
13859         }
13860
13861         /* Format move number */
13862         if ((i % 2) == 0)
13863           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13864         else
13865           if (newblock)
13866             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13867           else
13868             numtext[0] = NULLCHAR;
13869
13870         numlen = strlen(numtext);
13871         newblock = FALSE;
13872
13873         /* Print move number */
13874         blank = linelen > 0 && numlen > 0;
13875         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13876             fprintf(f, "\n");
13877             linelen = 0;
13878             blank = 0;
13879         }
13880         if (blank) {
13881             fprintf(f, " ");
13882             linelen++;
13883         }
13884         fprintf(f, "%s", numtext);
13885         linelen += numlen;
13886
13887         /* Get move */
13888         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13889         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13890
13891         /* Print move */
13892         blank = linelen > 0 && movelen > 0;
13893         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13894             fprintf(f, "\n");
13895             linelen = 0;
13896             blank = 0;
13897         }
13898         if (blank) {
13899             fprintf(f, " ");
13900             linelen++;
13901         }
13902         fprintf(f, "%s", move_buffer);
13903         linelen += movelen;
13904
13905         /* [AS] Add PV info if present */
13906         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13907             /* [HGM] add time */
13908             char buf[MSG_SIZ]; int seconds;
13909
13910             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13911
13912             if( seconds <= 0)
13913               buf[0] = 0;
13914             else
13915               if( seconds < 30 )
13916                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13917               else
13918                 {
13919                   seconds = (seconds + 4)/10; // round to full seconds
13920                   if( seconds < 60 )
13921                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13922                   else
13923                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13924                 }
13925
13926             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13927                       pvInfoList[i].score >= 0 ? "+" : "",
13928                       pvInfoList[i].score / 100.0,
13929                       pvInfoList[i].depth,
13930                       buf );
13931
13932             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13933
13934             /* Print score/depth */
13935             blank = linelen > 0 && movelen > 0;
13936             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13937                 fprintf(f, "\n");
13938                 linelen = 0;
13939                 blank = 0;
13940             }
13941             if (blank) {
13942                 fprintf(f, " ");
13943                 linelen++;
13944             }
13945             fprintf(f, "%s", move_buffer);
13946             linelen += movelen;
13947         }
13948
13949         i++;
13950     }
13951
13952     /* Start a new line */
13953     if (linelen > 0) fprintf(f, "\n");
13954
13955     /* Print comments after last move */
13956     if (commentList[i] != NULL) {
13957         fprintf(f, "%s\n", commentList[i]);
13958     }
13959
13960     /* Print result */
13961     if (gameInfo.resultDetails != NULL &&
13962         gameInfo.resultDetails[0] != NULLCHAR) {
13963         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13964         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13965            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13966             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13967         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13968     } else {
13969         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13970     }
13971 }
13972
13973 /* Save game in PGN style and close the file */
13974 int
13975 SaveGamePGN (FILE *f)
13976 {
13977     SaveGamePGN2(f);
13978     fclose(f);
13979     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13980     return TRUE;
13981 }
13982
13983 /* Save game in old style and close the file */
13984 int
13985 SaveGameOldStyle (FILE *f)
13986 {
13987     int i, offset;
13988     time_t tm;
13989
13990     tm = time((time_t *) NULL);
13991
13992     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13993     PrintOpponents(f);
13994
13995     if (backwardMostMove > 0 || startedFromSetupPosition) {
13996         fprintf(f, "\n[--------------\n");
13997         PrintPosition(f, backwardMostMove);
13998         fprintf(f, "--------------]\n");
13999     } else {
14000         fprintf(f, "\n");
14001     }
14002
14003     i = backwardMostMove;
14004     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14005
14006     while (i < forwardMostMove) {
14007         if (commentList[i] != NULL) {
14008             fprintf(f, "[%s]\n", commentList[i]);
14009         }
14010
14011         if ((i % 2) == 1) {
14012             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14013             i++;
14014         } else {
14015             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14016             i++;
14017             if (commentList[i] != NULL) {
14018                 fprintf(f, "\n");
14019                 continue;
14020             }
14021             if (i >= forwardMostMove) {
14022                 fprintf(f, "\n");
14023                 break;
14024             }
14025             fprintf(f, "%s\n", parseList[i]);
14026             i++;
14027         }
14028     }
14029
14030     if (commentList[i] != NULL) {
14031         fprintf(f, "[%s]\n", commentList[i]);
14032     }
14033
14034     /* This isn't really the old style, but it's close enough */
14035     if (gameInfo.resultDetails != NULL &&
14036         gameInfo.resultDetails[0] != NULLCHAR) {
14037         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14038                 gameInfo.resultDetails);
14039     } else {
14040         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14041     }
14042
14043     fclose(f);
14044     return TRUE;
14045 }
14046
14047 /* Save the current game to open file f and close the file */
14048 int
14049 SaveGame (FILE *f, int dummy, char *dummy2)
14050 {
14051     if (gameMode == EditPosition) EditPositionDone(TRUE);
14052     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14053     if (appData.oldSaveStyle)
14054       return SaveGameOldStyle(f);
14055     else
14056       return SaveGamePGN(f);
14057 }
14058
14059 /* Save the current position to the given file */
14060 int
14061 SavePositionToFile (char *filename)
14062 {
14063     FILE *f;
14064     char buf[MSG_SIZ];
14065
14066     if (strcmp(filename, "-") == 0) {
14067         return SavePosition(stdout, 0, NULL);
14068     } else {
14069         f = fopen(filename, "a");
14070         if (f == NULL) {
14071             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14072             DisplayError(buf, errno);
14073             return FALSE;
14074         } else {
14075             safeStrCpy(buf, lastMsg, MSG_SIZ);
14076             DisplayMessage(_("Waiting for access to save file"), "");
14077             flock(fileno(f), LOCK_EX); // [HGM] lock
14078             DisplayMessage(_("Saving position"), "");
14079             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14080             SavePosition(f, 0, NULL);
14081             DisplayMessage(buf, "");
14082             return TRUE;
14083         }
14084     }
14085 }
14086
14087 /* Save the current position to the given open file and close the file */
14088 int
14089 SavePosition (FILE *f, int dummy, char *dummy2)
14090 {
14091     time_t tm;
14092     char *fen;
14093
14094     if (gameMode == EditPosition) EditPositionDone(TRUE);
14095     if (appData.oldSaveStyle) {
14096         tm = time((time_t *) NULL);
14097
14098         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14099         PrintOpponents(f);
14100         fprintf(f, "[--------------\n");
14101         PrintPosition(f, currentMove);
14102         fprintf(f, "--------------]\n");
14103     } else {
14104         fen = PositionToFEN(currentMove, NULL, 1);
14105         fprintf(f, "%s\n", fen);
14106         free(fen);
14107     }
14108     fclose(f);
14109     return TRUE;
14110 }
14111
14112 void
14113 ReloadCmailMsgEvent (int unregister)
14114 {
14115 #if !WIN32
14116     static char *inFilename = NULL;
14117     static char *outFilename;
14118     int i;
14119     struct stat inbuf, outbuf;
14120     int status;
14121
14122     /* Any registered moves are unregistered if unregister is set, */
14123     /* i.e. invoked by the signal handler */
14124     if (unregister) {
14125         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14126             cmailMoveRegistered[i] = FALSE;
14127             if (cmailCommentList[i] != NULL) {
14128                 free(cmailCommentList[i]);
14129                 cmailCommentList[i] = NULL;
14130             }
14131         }
14132         nCmailMovesRegistered = 0;
14133     }
14134
14135     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14136         cmailResult[i] = CMAIL_NOT_RESULT;
14137     }
14138     nCmailResults = 0;
14139
14140     if (inFilename == NULL) {
14141         /* Because the filenames are static they only get malloced once  */
14142         /* and they never get freed                                      */
14143         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14144         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14145
14146         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14147         sprintf(outFilename, "%s.out", appData.cmailGameName);
14148     }
14149
14150     status = stat(outFilename, &outbuf);
14151     if (status < 0) {
14152         cmailMailedMove = FALSE;
14153     } else {
14154         status = stat(inFilename, &inbuf);
14155         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14156     }
14157
14158     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14159        counts the games, notes how each one terminated, etc.
14160
14161        It would be nice to remove this kludge and instead gather all
14162        the information while building the game list.  (And to keep it
14163        in the game list nodes instead of having a bunch of fixed-size
14164        parallel arrays.)  Note this will require getting each game's
14165        termination from the PGN tags, as the game list builder does
14166        not process the game moves.  --mann
14167        */
14168     cmailMsgLoaded = TRUE;
14169     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14170
14171     /* Load first game in the file or popup game menu */
14172     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14173
14174 #endif /* !WIN32 */
14175     return;
14176 }
14177
14178 int
14179 RegisterMove ()
14180 {
14181     FILE *f;
14182     char string[MSG_SIZ];
14183
14184     if (   cmailMailedMove
14185         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14186         return TRUE;            /* Allow free viewing  */
14187     }
14188
14189     /* Unregister move to ensure that we don't leave RegisterMove        */
14190     /* with the move registered when the conditions for registering no   */
14191     /* longer hold                                                       */
14192     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14193         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14194         nCmailMovesRegistered --;
14195
14196         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14197           {
14198               free(cmailCommentList[lastLoadGameNumber - 1]);
14199               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14200           }
14201     }
14202
14203     if (cmailOldMove == -1) {
14204         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14205         return FALSE;
14206     }
14207
14208     if (currentMove > cmailOldMove + 1) {
14209         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14210         return FALSE;
14211     }
14212
14213     if (currentMove < cmailOldMove) {
14214         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14215         return FALSE;
14216     }
14217
14218     if (forwardMostMove > currentMove) {
14219         /* Silently truncate extra moves */
14220         TruncateGame();
14221     }
14222
14223     if (   (currentMove == cmailOldMove + 1)
14224         || (   (currentMove == cmailOldMove)
14225             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14226                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14227         if (gameInfo.result != GameUnfinished) {
14228             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14229         }
14230
14231         if (commentList[currentMove] != NULL) {
14232             cmailCommentList[lastLoadGameNumber - 1]
14233               = StrSave(commentList[currentMove]);
14234         }
14235         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14236
14237         if (appData.debugMode)
14238           fprintf(debugFP, "Saving %s for game %d\n",
14239                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14240
14241         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14242
14243         f = fopen(string, "w");
14244         if (appData.oldSaveStyle) {
14245             SaveGameOldStyle(f); /* also closes the file */
14246
14247             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14248             f = fopen(string, "w");
14249             SavePosition(f, 0, NULL); /* also closes the file */
14250         } else {
14251             fprintf(f, "{--------------\n");
14252             PrintPosition(f, currentMove);
14253             fprintf(f, "--------------}\n\n");
14254
14255             SaveGame(f, 0, NULL); /* also closes the file*/
14256         }
14257
14258         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14259         nCmailMovesRegistered ++;
14260     } else if (nCmailGames == 1) {
14261         DisplayError(_("You have not made a move yet"), 0);
14262         return FALSE;
14263     }
14264
14265     return TRUE;
14266 }
14267
14268 void
14269 MailMoveEvent ()
14270 {
14271 #if !WIN32
14272     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14273     FILE *commandOutput;
14274     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14275     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14276     int nBuffers;
14277     int i;
14278     int archived;
14279     char *arcDir;
14280
14281     if (! cmailMsgLoaded) {
14282         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14283         return;
14284     }
14285
14286     if (nCmailGames == nCmailResults) {
14287         DisplayError(_("No unfinished games"), 0);
14288         return;
14289     }
14290
14291 #if CMAIL_PROHIBIT_REMAIL
14292     if (cmailMailedMove) {
14293       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);
14294         DisplayError(msg, 0);
14295         return;
14296     }
14297 #endif
14298
14299     if (! (cmailMailedMove || RegisterMove())) return;
14300
14301     if (   cmailMailedMove
14302         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14303       snprintf(string, MSG_SIZ, partCommandString,
14304                appData.debugMode ? " -v" : "", appData.cmailGameName);
14305         commandOutput = popen(string, "r");
14306
14307         if (commandOutput == NULL) {
14308             DisplayError(_("Failed to invoke cmail"), 0);
14309         } else {
14310             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14311                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14312             }
14313             if (nBuffers > 1) {
14314                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14315                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14316                 nBytes = MSG_SIZ - 1;
14317             } else {
14318                 (void) memcpy(msg, buffer, nBytes);
14319             }
14320             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14321
14322             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14323                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14324
14325                 archived = TRUE;
14326                 for (i = 0; i < nCmailGames; i ++) {
14327                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14328                         archived = FALSE;
14329                     }
14330                 }
14331                 if (   archived
14332                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14333                         != NULL)) {
14334                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14335                            arcDir,
14336                            appData.cmailGameName,
14337                            gameInfo.date);
14338                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14339                     cmailMsgLoaded = FALSE;
14340                 }
14341             }
14342
14343             DisplayInformation(msg);
14344             pclose(commandOutput);
14345         }
14346     } else {
14347         if ((*cmailMsg) != '\0') {
14348             DisplayInformation(cmailMsg);
14349         }
14350     }
14351
14352     return;
14353 #endif /* !WIN32 */
14354 }
14355
14356 char *
14357 CmailMsg ()
14358 {
14359 #if WIN32
14360     return NULL;
14361 #else
14362     int  prependComma = 0;
14363     char number[5];
14364     char string[MSG_SIZ];       /* Space for game-list */
14365     int  i;
14366
14367     if (!cmailMsgLoaded) return "";
14368
14369     if (cmailMailedMove) {
14370       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14371     } else {
14372         /* Create a list of games left */
14373       snprintf(string, MSG_SIZ, "[");
14374         for (i = 0; i < nCmailGames; i ++) {
14375             if (! (   cmailMoveRegistered[i]
14376                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14377                 if (prependComma) {
14378                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14379                 } else {
14380                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14381                     prependComma = 1;
14382                 }
14383
14384                 strcat(string, number);
14385             }
14386         }
14387         strcat(string, "]");
14388
14389         if (nCmailMovesRegistered + nCmailResults == 0) {
14390             switch (nCmailGames) {
14391               case 1:
14392                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14393                 break;
14394
14395               case 2:
14396                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14397                 break;
14398
14399               default:
14400                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14401                          nCmailGames);
14402                 break;
14403             }
14404         } else {
14405             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14406               case 1:
14407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14408                          string);
14409                 break;
14410
14411               case 0:
14412                 if (nCmailResults == nCmailGames) {
14413                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14414                 } else {
14415                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14416                 }
14417                 break;
14418
14419               default:
14420                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14421                          string);
14422             }
14423         }
14424     }
14425     return cmailMsg;
14426 #endif /* WIN32 */
14427 }
14428
14429 void
14430 ResetGameEvent ()
14431 {
14432     if (gameMode == Training)
14433       SetTrainingModeOff();
14434
14435     Reset(TRUE, TRUE);
14436     cmailMsgLoaded = FALSE;
14437     if (appData.icsActive) {
14438       SendToICS(ics_prefix);
14439       SendToICS("refresh\n");
14440     }
14441 }
14442
14443 void
14444 ExitEvent (int status)
14445 {
14446     exiting++;
14447     if (exiting > 2) {
14448       /* Give up on clean exit */
14449       exit(status);
14450     }
14451     if (exiting > 1) {
14452       /* Keep trying for clean exit */
14453       return;
14454     }
14455
14456     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14457     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14458
14459     if (telnetISR != NULL) {
14460       RemoveInputSource(telnetISR);
14461     }
14462     if (icsPR != NoProc) {
14463       DestroyChildProcess(icsPR, TRUE);
14464     }
14465
14466     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14467     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14468
14469     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14470     /* make sure this other one finishes before killing it!                  */
14471     if(endingGame) { int count = 0;
14472         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14473         while(endingGame && count++ < 10) DoSleep(1);
14474         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14475     }
14476
14477     /* Kill off chess programs */
14478     if (first.pr != NoProc) {
14479         ExitAnalyzeMode();
14480
14481         DoSleep( appData.delayBeforeQuit );
14482         SendToProgram("quit\n", &first);
14483         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14484     }
14485     if (second.pr != NoProc) {
14486         DoSleep( appData.delayBeforeQuit );
14487         SendToProgram("quit\n", &second);
14488         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14489     }
14490     if (first.isr != NULL) {
14491         RemoveInputSource(first.isr);
14492     }
14493     if (second.isr != NULL) {
14494         RemoveInputSource(second.isr);
14495     }
14496
14497     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14498     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14499
14500     ShutDownFrontEnd();
14501     exit(status);
14502 }
14503
14504 void
14505 PauseEngine (ChessProgramState *cps)
14506 {
14507     SendToProgram("pause\n", cps);
14508     cps->pause = 2;
14509 }
14510
14511 void
14512 UnPauseEngine (ChessProgramState *cps)
14513 {
14514     SendToProgram("resume\n", cps);
14515     cps->pause = 1;
14516 }
14517
14518 void
14519 PauseEvent ()
14520 {
14521     if (appData.debugMode)
14522         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14523     if (pausing) {
14524         pausing = FALSE;
14525         ModeHighlight();
14526         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14527             StartClocks();
14528             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14529                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14530                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14531             }
14532             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14533             HandleMachineMove(stashedInputMove, stalledEngine);
14534             stalledEngine = NULL;
14535             return;
14536         }
14537         if (gameMode == MachinePlaysWhite ||
14538             gameMode == TwoMachinesPlay   ||
14539             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14540             if(first.pause)  UnPauseEngine(&first);
14541             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14542             if(second.pause) UnPauseEngine(&second);
14543             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14544             StartClocks();
14545         } else {
14546             DisplayBothClocks();
14547         }
14548         if (gameMode == PlayFromGameFile) {
14549             if (appData.timeDelay >= 0)
14550                 AutoPlayGameLoop();
14551         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14552             Reset(FALSE, TRUE);
14553             SendToICS(ics_prefix);
14554             SendToICS("refresh\n");
14555         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14556             ForwardInner(forwardMostMove);
14557         }
14558         pauseExamInvalid = FALSE;
14559     } else {
14560         switch (gameMode) {
14561           default:
14562             return;
14563           case IcsExamining:
14564             pauseExamForwardMostMove = forwardMostMove;
14565             pauseExamInvalid = FALSE;
14566             /* fall through */
14567           case IcsObserving:
14568           case IcsPlayingWhite:
14569           case IcsPlayingBlack:
14570             pausing = TRUE;
14571             ModeHighlight();
14572             return;
14573           case PlayFromGameFile:
14574             (void) StopLoadGameTimer();
14575             pausing = TRUE;
14576             ModeHighlight();
14577             break;
14578           case BeginningOfGame:
14579             if (appData.icsActive) return;
14580             /* else fall through */
14581           case MachinePlaysWhite:
14582           case MachinePlaysBlack:
14583           case TwoMachinesPlay:
14584             if (forwardMostMove == 0)
14585               return;           /* don't pause if no one has moved */
14586             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14587                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14588                 if(onMove->pause) {           // thinking engine can be paused
14589                     PauseEngine(onMove);      // do it
14590                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14591                         PauseEngine(onMove->other);
14592                     else
14593                         SendToProgram("easy\n", onMove->other);
14594                     StopClocks();
14595                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14596             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14597                 if(first.pause) {
14598                     PauseEngine(&first);
14599                     StopClocks();
14600                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14601             } else { // human on move, pause pondering by either method
14602                 if(first.pause)
14603                     PauseEngine(&first);
14604                 else if(appData.ponderNextMove)
14605                     SendToProgram("easy\n", &first);
14606                 StopClocks();
14607             }
14608             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14609           case AnalyzeMode:
14610             pausing = TRUE;
14611             ModeHighlight();
14612             break;
14613         }
14614     }
14615 }
14616
14617 void
14618 EditCommentEvent ()
14619 {
14620     char title[MSG_SIZ];
14621
14622     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14623       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14624     } else {
14625       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14626                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14627                parseList[currentMove - 1]);
14628     }
14629
14630     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14631 }
14632
14633
14634 void
14635 EditTagsEvent ()
14636 {
14637     char *tags = PGNTags(&gameInfo);
14638     bookUp = FALSE;
14639     EditTagsPopUp(tags, NULL);
14640     free(tags);
14641 }
14642
14643 void
14644 ToggleSecond ()
14645 {
14646   if(second.analyzing) {
14647     SendToProgram("exit\n", &second);
14648     second.analyzing = FALSE;
14649   } else {
14650     if (second.pr == NoProc) StartChessProgram(&second);
14651     InitChessProgram(&second, FALSE);
14652     FeedMovesToProgram(&second, currentMove);
14653
14654     SendToProgram("analyze\n", &second);
14655     second.analyzing = TRUE;
14656   }
14657 }
14658
14659 /* Toggle ShowThinking */
14660 void
14661 ToggleShowThinking()
14662 {
14663   appData.showThinking = !appData.showThinking;
14664   ShowThinkingEvent();
14665 }
14666
14667 int
14668 AnalyzeModeEvent ()
14669 {
14670     char buf[MSG_SIZ];
14671
14672     if (!first.analysisSupport) {
14673       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14674       DisplayError(buf, 0);
14675       return 0;
14676     }
14677     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14678     if (appData.icsActive) {
14679         if (gameMode != IcsObserving) {
14680           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14681             DisplayError(buf, 0);
14682             /* secure check */
14683             if (appData.icsEngineAnalyze) {
14684                 if (appData.debugMode)
14685                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14686                 ExitAnalyzeMode();
14687                 ModeHighlight();
14688             }
14689             return 0;
14690         }
14691         /* if enable, user wants to disable icsEngineAnalyze */
14692         if (appData.icsEngineAnalyze) {
14693                 ExitAnalyzeMode();
14694                 ModeHighlight();
14695                 return 0;
14696         }
14697         appData.icsEngineAnalyze = TRUE;
14698         if (appData.debugMode)
14699             fprintf(debugFP, "ICS engine analyze starting... \n");
14700     }
14701
14702     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14703     if (appData.noChessProgram || gameMode == AnalyzeMode)
14704       return 0;
14705
14706     if (gameMode != AnalyzeFile) {
14707         if (!appData.icsEngineAnalyze) {
14708                EditGameEvent();
14709                if (gameMode != EditGame) return 0;
14710         }
14711         if (!appData.showThinking) ToggleShowThinking();
14712         ResurrectChessProgram();
14713         SendToProgram("analyze\n", &first);
14714         first.analyzing = TRUE;
14715         /*first.maybeThinking = TRUE;*/
14716         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14717         EngineOutputPopUp();
14718     }
14719     if (!appData.icsEngineAnalyze) {
14720         gameMode = AnalyzeMode;
14721         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14722     }
14723     pausing = FALSE;
14724     ModeHighlight();
14725     SetGameInfo();
14726
14727     StartAnalysisClock();
14728     GetTimeMark(&lastNodeCountTime);
14729     lastNodeCount = 0;
14730     return 1;
14731 }
14732
14733 void
14734 AnalyzeFileEvent ()
14735 {
14736     if (appData.noChessProgram || gameMode == AnalyzeFile)
14737       return;
14738
14739     if (!first.analysisSupport) {
14740       char buf[MSG_SIZ];
14741       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14742       DisplayError(buf, 0);
14743       return;
14744     }
14745
14746     if (gameMode != AnalyzeMode) {
14747         keepInfo = 1; // mere annotating should not alter PGN tags
14748         EditGameEvent();
14749         keepInfo = 0;
14750         if (gameMode != EditGame) return;
14751         if (!appData.showThinking) ToggleShowThinking();
14752         ResurrectChessProgram();
14753         SendToProgram("analyze\n", &first);
14754         first.analyzing = TRUE;
14755         /*first.maybeThinking = TRUE;*/
14756         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14757         EngineOutputPopUp();
14758     }
14759     gameMode = AnalyzeFile;
14760     pausing = FALSE;
14761     ModeHighlight();
14762
14763     StartAnalysisClock();
14764     GetTimeMark(&lastNodeCountTime);
14765     lastNodeCount = 0;
14766     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14767     AnalysisPeriodicEvent(1);
14768 }
14769
14770 void
14771 MachineWhiteEvent ()
14772 {
14773     char buf[MSG_SIZ];
14774     char *bookHit = NULL;
14775
14776     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14777       return;
14778
14779
14780     if (gameMode == PlayFromGameFile ||
14781         gameMode == TwoMachinesPlay  ||
14782         gameMode == Training         ||
14783         gameMode == AnalyzeMode      ||
14784         gameMode == EndOfGame)
14785         EditGameEvent();
14786
14787     if (gameMode == EditPosition)
14788         EditPositionDone(TRUE);
14789
14790     if (!WhiteOnMove(currentMove)) {
14791         DisplayError(_("It is not White's turn"), 0);
14792         return;
14793     }
14794
14795     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14796       ExitAnalyzeMode();
14797
14798     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14799         gameMode == AnalyzeFile)
14800         TruncateGame();
14801
14802     ResurrectChessProgram();    /* in case it isn't running */
14803     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14804         gameMode = MachinePlaysWhite;
14805         ResetClocks();
14806     } else
14807     gameMode = MachinePlaysWhite;
14808     pausing = FALSE;
14809     ModeHighlight();
14810     SetGameInfo();
14811     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14812     DisplayTitle(buf);
14813     if (first.sendName) {
14814       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14815       SendToProgram(buf, &first);
14816     }
14817     if (first.sendTime) {
14818       if (first.useColors) {
14819         SendToProgram("black\n", &first); /*gnu kludge*/
14820       }
14821       SendTimeRemaining(&first, TRUE);
14822     }
14823     if (first.useColors) {
14824       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14825     }
14826     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14827     SetMachineThinkingEnables();
14828     first.maybeThinking = TRUE;
14829     StartClocks();
14830     firstMove = FALSE;
14831
14832     if (appData.autoFlipView && !flipView) {
14833       flipView = !flipView;
14834       DrawPosition(FALSE, NULL);
14835       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14836     }
14837
14838     if(bookHit) { // [HGM] book: simulate book reply
14839         static char bookMove[MSG_SIZ]; // a bit generous?
14840
14841         programStats.nodes = programStats.depth = programStats.time =
14842         programStats.score = programStats.got_only_move = 0;
14843         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14844
14845         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14846         strcat(bookMove, bookHit);
14847         HandleMachineMove(bookMove, &first);
14848     }
14849 }
14850
14851 void
14852 MachineBlackEvent ()
14853 {
14854   char buf[MSG_SIZ];
14855   char *bookHit = NULL;
14856
14857     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14858         return;
14859
14860
14861     if (gameMode == PlayFromGameFile ||
14862         gameMode == TwoMachinesPlay  ||
14863         gameMode == Training         ||
14864         gameMode == AnalyzeMode      ||
14865         gameMode == EndOfGame)
14866         EditGameEvent();
14867
14868     if (gameMode == EditPosition)
14869         EditPositionDone(TRUE);
14870
14871     if (WhiteOnMove(currentMove)) {
14872         DisplayError(_("It is not Black's turn"), 0);
14873         return;
14874     }
14875
14876     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14877       ExitAnalyzeMode();
14878
14879     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14880         gameMode == AnalyzeFile)
14881         TruncateGame();
14882
14883     ResurrectChessProgram();    /* in case it isn't running */
14884     gameMode = MachinePlaysBlack;
14885     pausing = FALSE;
14886     ModeHighlight();
14887     SetGameInfo();
14888     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14889     DisplayTitle(buf);
14890     if (first.sendName) {
14891       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14892       SendToProgram(buf, &first);
14893     }
14894     if (first.sendTime) {
14895       if (first.useColors) {
14896         SendToProgram("white\n", &first); /*gnu kludge*/
14897       }
14898       SendTimeRemaining(&first, FALSE);
14899     }
14900     if (first.useColors) {
14901       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14902     }
14903     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14904     SetMachineThinkingEnables();
14905     first.maybeThinking = TRUE;
14906     StartClocks();
14907
14908     if (appData.autoFlipView && flipView) {
14909       flipView = !flipView;
14910       DrawPosition(FALSE, NULL);
14911       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14912     }
14913     if(bookHit) { // [HGM] book: simulate book reply
14914         static char bookMove[MSG_SIZ]; // a bit generous?
14915
14916         programStats.nodes = programStats.depth = programStats.time =
14917         programStats.score = programStats.got_only_move = 0;
14918         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14919
14920         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14921         strcat(bookMove, bookHit);
14922         HandleMachineMove(bookMove, &first);
14923     }
14924 }
14925
14926
14927 void
14928 DisplayTwoMachinesTitle ()
14929 {
14930     char buf[MSG_SIZ];
14931     if (appData.matchGames > 0) {
14932         if(appData.tourneyFile[0]) {
14933           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14934                    gameInfo.white, _("vs."), gameInfo.black,
14935                    nextGame+1, appData.matchGames+1,
14936                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14937         } else
14938         if (first.twoMachinesColor[0] == 'w') {
14939           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14940                    gameInfo.white, _("vs."),  gameInfo.black,
14941                    first.matchWins, second.matchWins,
14942                    matchGame - 1 - (first.matchWins + second.matchWins));
14943         } else {
14944           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14945                    gameInfo.white, _("vs."), gameInfo.black,
14946                    second.matchWins, first.matchWins,
14947                    matchGame - 1 - (first.matchWins + second.matchWins));
14948         }
14949     } else {
14950       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14951     }
14952     DisplayTitle(buf);
14953 }
14954
14955 void
14956 SettingsMenuIfReady ()
14957 {
14958   if (second.lastPing != second.lastPong) {
14959     DisplayMessage("", _("Waiting for second chess program"));
14960     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14961     return;
14962   }
14963   ThawUI();
14964   DisplayMessage("", "");
14965   SettingsPopUp(&second);
14966 }
14967
14968 int
14969 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14970 {
14971     char buf[MSG_SIZ];
14972     if (cps->pr == NoProc) {
14973         StartChessProgram(cps);
14974         if (cps->protocolVersion == 1) {
14975           retry();
14976           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14977         } else {
14978           /* kludge: allow timeout for initial "feature" command */
14979           if(retry != TwoMachinesEventIfReady) FreezeUI();
14980           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14981           DisplayMessage("", buf);
14982           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14983         }
14984         return 1;
14985     }
14986     return 0;
14987 }
14988
14989 void
14990 TwoMachinesEvent P((void))
14991 {
14992     int i;
14993     char buf[MSG_SIZ];
14994     ChessProgramState *onmove;
14995     char *bookHit = NULL;
14996     static int stalling = 0;
14997     TimeMark now;
14998     long wait;
14999
15000     if (appData.noChessProgram) return;
15001
15002     switch (gameMode) {
15003       case TwoMachinesPlay:
15004         return;
15005       case MachinePlaysWhite:
15006       case MachinePlaysBlack:
15007         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15008             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15009             return;
15010         }
15011         /* fall through */
15012       case BeginningOfGame:
15013       case PlayFromGameFile:
15014       case EndOfGame:
15015         EditGameEvent();
15016         if (gameMode != EditGame) return;
15017         break;
15018       case EditPosition:
15019         EditPositionDone(TRUE);
15020         break;
15021       case AnalyzeMode:
15022       case AnalyzeFile:
15023         ExitAnalyzeMode();
15024         break;
15025       case EditGame:
15026       default:
15027         break;
15028     }
15029
15030 //    forwardMostMove = currentMove;
15031     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15032     startingEngine = TRUE;
15033
15034     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15035
15036     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15037     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15038       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15039       return;
15040     }
15041   if(!appData.epd) {
15042     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15043
15044     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15045                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15046         startingEngine = matchMode = FALSE;
15047         DisplayError("second engine does not play this", 0);
15048         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15049         EditGameEvent(); // switch back to EditGame mode
15050         return;
15051     }
15052
15053     if(!stalling) {
15054       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15055       SendToProgram("force\n", &second);
15056       stalling = 1;
15057       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15058       return;
15059     }
15060   }
15061     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15062     if(appData.matchPause>10000 || appData.matchPause<10)
15063                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15064     wait = SubtractTimeMarks(&now, &pauseStart);
15065     if(wait < appData.matchPause) {
15066         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15067         return;
15068     }
15069     // we are now committed to starting the game
15070     stalling = 0;
15071     DisplayMessage("", "");
15072   if(!appData.epd) {
15073     if (startedFromSetupPosition) {
15074         SendBoard(&second, backwardMostMove);
15075     if (appData.debugMode) {
15076         fprintf(debugFP, "Two Machines\n");
15077     }
15078     }
15079     for (i = backwardMostMove; i < forwardMostMove; i++) {
15080         SendMoveToProgram(i, &second);
15081     }
15082   }
15083
15084     gameMode = TwoMachinesPlay;
15085     pausing = startingEngine = FALSE;
15086     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15087     SetGameInfo();
15088     DisplayTwoMachinesTitle();
15089     firstMove = TRUE;
15090     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15091         onmove = &first;
15092     } else {
15093         onmove = &second;
15094     }
15095     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15096     SendToProgram(first.computerString, &first);
15097     if (first.sendName) {
15098       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15099       SendToProgram(buf, &first);
15100     }
15101   if(!appData.epd) {
15102     SendToProgram(second.computerString, &second);
15103     if (second.sendName) {
15104       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15105       SendToProgram(buf, &second);
15106     }
15107   }
15108
15109     ResetClocks();
15110     if (!first.sendTime || !second.sendTime) {
15111         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15112         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15113     }
15114     if (onmove->sendTime) {
15115       if (onmove->useColors) {
15116         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15117       }
15118       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15119     }
15120     if (onmove->useColors) {
15121       SendToProgram(onmove->twoMachinesColor, onmove);
15122     }
15123     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15124 //    SendToProgram("go\n", onmove);
15125     onmove->maybeThinking = TRUE;
15126     SetMachineThinkingEnables();
15127
15128     StartClocks();
15129
15130     if(bookHit) { // [HGM] book: simulate book reply
15131         static char bookMove[MSG_SIZ]; // a bit generous?
15132
15133         programStats.nodes = programStats.depth = programStats.time =
15134         programStats.score = programStats.got_only_move = 0;
15135         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15136
15137         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15138         strcat(bookMove, bookHit);
15139         savedMessage = bookMove; // args for deferred call
15140         savedState = onmove;
15141         ScheduleDelayedEvent(DeferredBookMove, 1);
15142     }
15143 }
15144
15145 void
15146 TrainingEvent ()
15147 {
15148     if (gameMode == Training) {
15149       SetTrainingModeOff();
15150       gameMode = PlayFromGameFile;
15151       DisplayMessage("", _("Training mode off"));
15152     } else {
15153       gameMode = Training;
15154       animateTraining = appData.animate;
15155
15156       /* make sure we are not already at the end of the game */
15157       if (currentMove < forwardMostMove) {
15158         SetTrainingModeOn();
15159         DisplayMessage("", _("Training mode on"));
15160       } else {
15161         gameMode = PlayFromGameFile;
15162         DisplayError(_("Already at end of game"), 0);
15163       }
15164     }
15165     ModeHighlight();
15166 }
15167
15168 void
15169 IcsClientEvent ()
15170 {
15171     if (!appData.icsActive) return;
15172     switch (gameMode) {
15173       case IcsPlayingWhite:
15174       case IcsPlayingBlack:
15175       case IcsObserving:
15176       case IcsIdle:
15177       case BeginningOfGame:
15178       case IcsExamining:
15179         return;
15180
15181       case EditGame:
15182         break;
15183
15184       case EditPosition:
15185         EditPositionDone(TRUE);
15186         break;
15187
15188       case AnalyzeMode:
15189       case AnalyzeFile:
15190         ExitAnalyzeMode();
15191         break;
15192
15193       default:
15194         EditGameEvent();
15195         break;
15196     }
15197
15198     gameMode = IcsIdle;
15199     ModeHighlight();
15200     return;
15201 }
15202
15203 void
15204 EditGameEvent ()
15205 {
15206     int i;
15207
15208     switch (gameMode) {
15209       case Training:
15210         SetTrainingModeOff();
15211         break;
15212       case MachinePlaysWhite:
15213       case MachinePlaysBlack:
15214       case BeginningOfGame:
15215         SendToProgram("force\n", &first);
15216         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15217             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15218                 char buf[MSG_SIZ];
15219                 abortEngineThink = TRUE;
15220                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15221                 SendToProgram(buf, &first);
15222                 DisplayMessage("Aborting engine think", "");
15223                 FreezeUI();
15224             }
15225         }
15226         SetUserThinkingEnables();
15227         break;
15228       case PlayFromGameFile:
15229         (void) StopLoadGameTimer();
15230         if (gameFileFP != NULL) {
15231             gameFileFP = NULL;
15232         }
15233         break;
15234       case EditPosition:
15235         EditPositionDone(TRUE);
15236         break;
15237       case AnalyzeMode:
15238       case AnalyzeFile:
15239         ExitAnalyzeMode();
15240         SendToProgram("force\n", &first);
15241         break;
15242       case TwoMachinesPlay:
15243         GameEnds(EndOfFile, NULL, GE_PLAYER);
15244         ResurrectChessProgram();
15245         SetUserThinkingEnables();
15246         break;
15247       case EndOfGame:
15248         ResurrectChessProgram();
15249         break;
15250       case IcsPlayingBlack:
15251       case IcsPlayingWhite:
15252         DisplayError(_("Warning: You are still playing a game"), 0);
15253         break;
15254       case IcsObserving:
15255         DisplayError(_("Warning: You are still observing a game"), 0);
15256         break;
15257       case IcsExamining:
15258         DisplayError(_("Warning: You are still examining a game"), 0);
15259         break;
15260       case IcsIdle:
15261         break;
15262       case EditGame:
15263       default:
15264         return;
15265     }
15266
15267     pausing = FALSE;
15268     StopClocks();
15269     first.offeredDraw = second.offeredDraw = 0;
15270
15271     if (gameMode == PlayFromGameFile) {
15272         whiteTimeRemaining = timeRemaining[0][currentMove];
15273         blackTimeRemaining = timeRemaining[1][currentMove];
15274         DisplayTitle("");
15275     }
15276
15277     if (gameMode == MachinePlaysWhite ||
15278         gameMode == MachinePlaysBlack ||
15279         gameMode == TwoMachinesPlay ||
15280         gameMode == EndOfGame) {
15281         i = forwardMostMove;
15282         while (i > currentMove) {
15283             SendToProgram("undo\n", &first);
15284             i--;
15285         }
15286         if(!adjustedClock) {
15287         whiteTimeRemaining = timeRemaining[0][currentMove];
15288         blackTimeRemaining = timeRemaining[1][currentMove];
15289         DisplayBothClocks();
15290         }
15291         if (whiteFlag || blackFlag) {
15292             whiteFlag = blackFlag = 0;
15293         }
15294         DisplayTitle("");
15295     }
15296
15297     gameMode = EditGame;
15298     ModeHighlight();
15299     SetGameInfo();
15300 }
15301
15302 void
15303 EditPositionEvent ()
15304 {
15305     int i;
15306     if (gameMode == EditPosition) {
15307         EditGameEvent();
15308         return;
15309     }
15310
15311     EditGameEvent();
15312     if (gameMode != EditGame) return;
15313
15314     gameMode = EditPosition;
15315     ModeHighlight();
15316     SetGameInfo();
15317     CopyBoard(rightsBoard, nullBoard);
15318     if (currentMove > 0)
15319       CopyBoard(boards[0], boards[currentMove]);
15320     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15321       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15322
15323     blackPlaysFirst = !WhiteOnMove(currentMove);
15324     ResetClocks();
15325     currentMove = forwardMostMove = backwardMostMove = 0;
15326     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15327     DisplayMove(-1);
15328     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15329 }
15330
15331 void
15332 ExitAnalyzeMode ()
15333 {
15334     /* [DM] icsEngineAnalyze - possible call from other functions */
15335     if (appData.icsEngineAnalyze) {
15336         appData.icsEngineAnalyze = FALSE;
15337
15338         DisplayMessage("",_("Close ICS engine analyze..."));
15339     }
15340     if (first.analysisSupport && first.analyzing) {
15341       SendToBoth("exit\n");
15342       first.analyzing = second.analyzing = FALSE;
15343     }
15344     thinkOutput[0] = NULLCHAR;
15345 }
15346
15347 void
15348 EditPositionDone (Boolean fakeRights)
15349 {
15350     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15351
15352     startedFromSetupPosition = TRUE;
15353     InitChessProgram(&first, FALSE);
15354     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15355       int r, f;
15356       boards[0][EP_STATUS] = EP_NONE;
15357       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15358       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15359         if(rightsBoard[r][f]) {
15360           ChessSquare p = boards[0][r][f];
15361           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15362           else if(p == king) boards[0][CASTLING][2] = f;
15363           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15364           else rightsBoard[r][f] = 2; // mark for second pass
15365         }
15366       }
15367       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15368         if(rightsBoard[r][f] == 2) {
15369           ChessSquare p = boards[0][r][f];
15370           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15371           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15372         }
15373       }
15374     }
15375     SendToProgram("force\n", &first);
15376     if (blackPlaysFirst) {
15377         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15378         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15379         currentMove = forwardMostMove = backwardMostMove = 1;
15380         CopyBoard(boards[1], boards[0]);
15381     } else {
15382         currentMove = forwardMostMove = backwardMostMove = 0;
15383     }
15384     SendBoard(&first, forwardMostMove);
15385     if (appData.debugMode) {
15386         fprintf(debugFP, "EditPosDone\n");
15387     }
15388     DisplayTitle("");
15389     DisplayMessage("", "");
15390     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15391     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15392     gameMode = EditGame;
15393     ModeHighlight();
15394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15395     ClearHighlights(); /* [AS] */
15396 }
15397
15398 /* Pause for `ms' milliseconds */
15399 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15400 void
15401 TimeDelay (long ms)
15402 {
15403     TimeMark m1, m2;
15404
15405     GetTimeMark(&m1);
15406     do {
15407         GetTimeMark(&m2);
15408     } while (SubtractTimeMarks(&m2, &m1) < ms);
15409 }
15410
15411 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15412 void
15413 SendMultiLineToICS (char *buf)
15414 {
15415     char temp[MSG_SIZ+1], *p;
15416     int len;
15417
15418     len = strlen(buf);
15419     if (len > MSG_SIZ)
15420       len = MSG_SIZ;
15421
15422     strncpy(temp, buf, len);
15423     temp[len] = 0;
15424
15425     p = temp;
15426     while (*p) {
15427         if (*p == '\n' || *p == '\r')
15428           *p = ' ';
15429         ++p;
15430     }
15431
15432     strcat(temp, "\n");
15433     SendToICS(temp);
15434     SendToPlayer(temp, strlen(temp));
15435 }
15436
15437 void
15438 SetWhiteToPlayEvent ()
15439 {
15440     if (gameMode == EditPosition) {
15441         blackPlaysFirst = FALSE;
15442         DisplayBothClocks();    /* works because currentMove is 0 */
15443     } else if (gameMode == IcsExamining) {
15444         SendToICS(ics_prefix);
15445         SendToICS("tomove white\n");
15446     }
15447 }
15448
15449 void
15450 SetBlackToPlayEvent ()
15451 {
15452     if (gameMode == EditPosition) {
15453         blackPlaysFirst = TRUE;
15454         currentMove = 1;        /* kludge */
15455         DisplayBothClocks();
15456         currentMove = 0;
15457     } else if (gameMode == IcsExamining) {
15458         SendToICS(ics_prefix);
15459         SendToICS("tomove black\n");
15460     }
15461 }
15462
15463 void
15464 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15465 {
15466     char buf[MSG_SIZ];
15467     ChessSquare piece = boards[0][y][x];
15468     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15469     static int lastVariant;
15470     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15471
15472     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15473
15474     switch (selection) {
15475       case ClearBoard:
15476         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15477         MarkTargetSquares(1);
15478         CopyBoard(currentBoard, boards[0]);
15479         CopyBoard(menuBoard, initialPosition);
15480         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15481             SendToICS(ics_prefix);
15482             SendToICS("bsetup clear\n");
15483         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15484             SendToICS(ics_prefix);
15485             SendToICS("clearboard\n");
15486         } else {
15487             int nonEmpty = 0;
15488             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15489                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15490                 for (y = 0; y < BOARD_HEIGHT; y++) {
15491                     if (gameMode == IcsExamining) {
15492                         if (boards[currentMove][y][x] != EmptySquare) {
15493                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15494                                     AAA + x, ONE + y);
15495                             SendToICS(buf);
15496                         }
15497                     } else if(boards[0][y][x] != DarkSquare) {
15498                         if(boards[0][y][x] != p) nonEmpty++;
15499                         boards[0][y][x] = p;
15500                     }
15501                 }
15502             }
15503             CopyBoard(rightsBoard, nullBoard);
15504             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15505                 int r, i;
15506                 for(r = 0; r < BOARD_HEIGHT; r++) {
15507                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15508                     ChessSquare p = menuBoard[r][x];
15509                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15510                   }
15511                 }
15512                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15513                 DisplayMessage("Clicking clock again restores position", "");
15514                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15515                 if(!nonEmpty) { // asked to clear an empty board
15516                     CopyBoard(boards[0], menuBoard);
15517                 } else
15518                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15519                     CopyBoard(boards[0], initialPosition);
15520                 } else
15521                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15522                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15523                     CopyBoard(boards[0], erasedBoard);
15524                 } else
15525                     CopyBoard(erasedBoard, currentBoard);
15526
15527                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15528                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15529             }
15530         }
15531         if (gameMode == EditPosition) {
15532             DrawPosition(FALSE, boards[0]);
15533         }
15534         break;
15535
15536       case WhitePlay:
15537         SetWhiteToPlayEvent();
15538         break;
15539
15540       case BlackPlay:
15541         SetBlackToPlayEvent();
15542         break;
15543
15544       case EmptySquare:
15545         if (gameMode == IcsExamining) {
15546             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15547             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15548             SendToICS(buf);
15549         } else {
15550             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15551                 if(x == BOARD_LEFT-2) {
15552                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15553                     boards[0][y][1] = 0;
15554                 } else
15555                 if(x == BOARD_RGHT+1) {
15556                     if(y >= gameInfo.holdingsSize) break;
15557                     boards[0][y][BOARD_WIDTH-2] = 0;
15558                 } else break;
15559             }
15560             boards[0][y][x] = EmptySquare;
15561             DrawPosition(FALSE, boards[0]);
15562         }
15563         break;
15564
15565       case PromotePiece:
15566         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15567            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15568             selection = (ChessSquare) (PROMOTED(piece));
15569         } else if(piece == EmptySquare) selection = WhiteSilver;
15570         else selection = (ChessSquare)((int)piece - 1);
15571         goto defaultlabel;
15572
15573       case DemotePiece:
15574         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15575            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15576             selection = (ChessSquare) (DEMOTED(piece));
15577         } else if(piece == EmptySquare) selection = BlackSilver;
15578         else selection = (ChessSquare)((int)piece + 1);
15579         goto defaultlabel;
15580
15581       case WhiteQueen:
15582       case BlackQueen:
15583         if(gameInfo.variant == VariantShatranj ||
15584            gameInfo.variant == VariantXiangqi  ||
15585            gameInfo.variant == VariantCourier  ||
15586            gameInfo.variant == VariantASEAN    ||
15587            gameInfo.variant == VariantMakruk     )
15588             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15589         goto defaultlabel;
15590
15591       case WhiteRook:
15592         baseRank = 0;
15593       case BlackRook:
15594         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15595         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15596         goto defaultlabel;
15597
15598       case WhiteKing:
15599         baseRank = 0;
15600       case BlackKing:
15601         if(gameInfo.variant == VariantXiangqi)
15602             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15603         if(gameInfo.variant == VariantKnightmate)
15604             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15605         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15606       default:
15607         defaultlabel:
15608         if (gameMode == IcsExamining) {
15609             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15610             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15611                      PieceToChar(selection), AAA + x, ONE + y);
15612             SendToICS(buf);
15613         } else {
15614             rightsBoard[y][x] = hasRights;
15615             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15616                 int n;
15617                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15618                     n = PieceToNumber(selection - BlackPawn);
15619                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15620                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15621                     boards[0][BOARD_HEIGHT-1-n][1]++;
15622                 } else
15623                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15624                     n = PieceToNumber(selection);
15625                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15626                     boards[0][n][BOARD_WIDTH-1] = selection;
15627                     boards[0][n][BOARD_WIDTH-2]++;
15628                 }
15629             } else
15630             boards[0][y][x] = selection;
15631             DrawPosition(TRUE, boards[0]);
15632             ClearHighlights();
15633             fromX = fromY = -1;
15634         }
15635         break;
15636     }
15637 }
15638
15639
15640 void
15641 DropMenuEvent (ChessSquare selection, int x, int y)
15642 {
15643     ChessMove moveType;
15644
15645     switch (gameMode) {
15646       case IcsPlayingWhite:
15647       case MachinePlaysBlack:
15648         if (!WhiteOnMove(currentMove)) {
15649             DisplayMoveError(_("It is Black's turn"));
15650             return;
15651         }
15652         moveType = WhiteDrop;
15653         break;
15654       case IcsPlayingBlack:
15655       case MachinePlaysWhite:
15656         if (WhiteOnMove(currentMove)) {
15657             DisplayMoveError(_("It is White's turn"));
15658             return;
15659         }
15660         moveType = BlackDrop;
15661         break;
15662       case EditGame:
15663         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15664         break;
15665       default:
15666         return;
15667     }
15668
15669     if (moveType == BlackDrop && selection < BlackPawn) {
15670       selection = (ChessSquare) ((int) selection
15671                                  + (int) BlackPawn - (int) WhitePawn);
15672     }
15673     if (boards[currentMove][y][x] != EmptySquare) {
15674         DisplayMoveError(_("That square is occupied"));
15675         return;
15676     }
15677
15678     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15679 }
15680
15681 void
15682 AcceptEvent ()
15683 {
15684     /* Accept a pending offer of any kind from opponent */
15685
15686     if (appData.icsActive) {
15687         SendToICS(ics_prefix);
15688         SendToICS("accept\n");
15689     } else if (cmailMsgLoaded) {
15690         if (currentMove == cmailOldMove &&
15691             commentList[cmailOldMove] != NULL &&
15692             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15693                    "Black offers a draw" : "White offers a draw")) {
15694             TruncateGame();
15695             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15697         } else {
15698             DisplayError(_("There is no pending offer on this move"), 0);
15699             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15700         }
15701     } else {
15702         /* Not used for offers from chess program */
15703     }
15704 }
15705
15706 void
15707 DeclineEvent ()
15708 {
15709     /* Decline a pending offer of any kind from opponent */
15710
15711     if (appData.icsActive) {
15712         SendToICS(ics_prefix);
15713         SendToICS("decline\n");
15714     } else if (cmailMsgLoaded) {
15715         if (currentMove == cmailOldMove &&
15716             commentList[cmailOldMove] != NULL &&
15717             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15718                    "Black offers a draw" : "White offers a draw")) {
15719 #ifdef NOTDEF
15720             AppendComment(cmailOldMove, "Draw declined", TRUE);
15721             DisplayComment(cmailOldMove - 1, "Draw declined");
15722 #endif /*NOTDEF*/
15723         } else {
15724             DisplayError(_("There is no pending offer on this move"), 0);
15725         }
15726     } else {
15727         /* Not used for offers from chess program */
15728     }
15729 }
15730
15731 void
15732 RematchEvent ()
15733 {
15734     /* Issue ICS rematch command */
15735     if (appData.icsActive) {
15736         SendToICS(ics_prefix);
15737         SendToICS("rematch\n");
15738     }
15739 }
15740
15741 void
15742 CallFlagEvent ()
15743 {
15744     /* Call your opponent's flag (claim a win on time) */
15745     if (appData.icsActive) {
15746         SendToICS(ics_prefix);
15747         SendToICS("flag\n");
15748     } else {
15749         switch (gameMode) {
15750           default:
15751             return;
15752           case MachinePlaysWhite:
15753             if (whiteFlag) {
15754                 if (blackFlag)
15755                   GameEnds(GameIsDrawn, "Both players ran out of time",
15756                            GE_PLAYER);
15757                 else
15758                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15759             } else {
15760                 DisplayError(_("Your opponent is not out of time"), 0);
15761             }
15762             break;
15763           case MachinePlaysBlack:
15764             if (blackFlag) {
15765                 if (whiteFlag)
15766                   GameEnds(GameIsDrawn, "Both players ran out of time",
15767                            GE_PLAYER);
15768                 else
15769                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15770             } else {
15771                 DisplayError(_("Your opponent is not out of time"), 0);
15772             }
15773             break;
15774         }
15775     }
15776 }
15777
15778 void
15779 ClockClick (int which)
15780 {       // [HGM] code moved to back-end from winboard.c
15781         if(which) { // black clock
15782           if (gameMode == EditPosition || gameMode == IcsExamining) {
15783             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15784             SetBlackToPlayEvent();
15785           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15786                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15787           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15788           } else if (shiftKey) {
15789             AdjustClock(which, -1);
15790           } else if (gameMode == IcsPlayingWhite ||
15791                      gameMode == MachinePlaysBlack) {
15792             CallFlagEvent();
15793           }
15794         } else { // white clock
15795           if (gameMode == EditPosition || gameMode == IcsExamining) {
15796             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15797             SetWhiteToPlayEvent();
15798           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15799                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15800           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15801           } else if (shiftKey) {
15802             AdjustClock(which, -1);
15803           } else if (gameMode == IcsPlayingBlack ||
15804                    gameMode == MachinePlaysWhite) {
15805             CallFlagEvent();
15806           }
15807         }
15808 }
15809
15810 void
15811 DrawEvent ()
15812 {
15813     /* Offer draw or accept pending draw offer from opponent */
15814
15815     if (appData.icsActive) {
15816         /* Note: tournament rules require draw offers to be
15817            made after you make your move but before you punch
15818            your clock.  Currently ICS doesn't let you do that;
15819            instead, you immediately punch your clock after making
15820            a move, but you can offer a draw at any time. */
15821
15822         SendToICS(ics_prefix);
15823         SendToICS("draw\n");
15824         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15825     } else if (cmailMsgLoaded) {
15826         if (currentMove == cmailOldMove &&
15827             commentList[cmailOldMove] != NULL &&
15828             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15829                    "Black offers a draw" : "White offers a draw")) {
15830             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15831             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15832         } else if (currentMove == cmailOldMove + 1) {
15833             char *offer = WhiteOnMove(cmailOldMove) ?
15834               "White offers a draw" : "Black offers a draw";
15835             AppendComment(currentMove, offer, TRUE);
15836             DisplayComment(currentMove - 1, offer);
15837             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15838         } else {
15839             DisplayError(_("You must make your move before offering a draw"), 0);
15840             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15841         }
15842     } else if (first.offeredDraw) {
15843         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15844     } else {
15845         if (first.sendDrawOffers) {
15846             SendToProgram("draw\n", &first);
15847             userOfferedDraw = TRUE;
15848         }
15849     }
15850 }
15851
15852 void
15853 AdjournEvent ()
15854 {
15855     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15856
15857     if (appData.icsActive) {
15858         SendToICS(ics_prefix);
15859         SendToICS("adjourn\n");
15860     } else {
15861         /* Currently GNU Chess doesn't offer or accept Adjourns */
15862     }
15863 }
15864
15865
15866 void
15867 AbortEvent ()
15868 {
15869     /* Offer Abort or accept pending Abort offer from opponent */
15870
15871     if (appData.icsActive) {
15872         SendToICS(ics_prefix);
15873         SendToICS("abort\n");
15874     } else {
15875         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15876     }
15877 }
15878
15879 void
15880 ResignEvent ()
15881 {
15882     /* Resign.  You can do this even if it's not your turn. */
15883
15884     if (appData.icsActive) {
15885         SendToICS(ics_prefix);
15886         SendToICS("resign\n");
15887     } else {
15888         switch (gameMode) {
15889           case MachinePlaysWhite:
15890             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15891             break;
15892           case MachinePlaysBlack:
15893             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15894             break;
15895           case EditGame:
15896             if (cmailMsgLoaded) {
15897                 TruncateGame();
15898                 if (WhiteOnMove(cmailOldMove)) {
15899                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15900                 } else {
15901                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15902                 }
15903                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15904             }
15905             break;
15906           default:
15907             break;
15908         }
15909     }
15910 }
15911
15912
15913 void
15914 StopObservingEvent ()
15915 {
15916     /* Stop observing current games */
15917     SendToICS(ics_prefix);
15918     SendToICS("unobserve\n");
15919 }
15920
15921 void
15922 StopExaminingEvent ()
15923 {
15924     /* Stop observing current game */
15925     SendToICS(ics_prefix);
15926     SendToICS("unexamine\n");
15927 }
15928
15929 void
15930 ForwardInner (int target)
15931 {
15932     int limit; int oldSeekGraphUp = seekGraphUp;
15933
15934     if (appData.debugMode)
15935         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15936                 target, currentMove, forwardMostMove);
15937
15938     if (gameMode == EditPosition)
15939       return;
15940
15941     seekGraphUp = FALSE;
15942     MarkTargetSquares(1);
15943     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15944
15945     if (gameMode == PlayFromGameFile && !pausing)
15946       PauseEvent();
15947
15948     if (gameMode == IcsExamining && pausing)
15949       limit = pauseExamForwardMostMove;
15950     else
15951       limit = forwardMostMove;
15952
15953     if (target > limit) target = limit;
15954
15955     if (target > 0 && moveList[target - 1][0]) {
15956         int fromX, fromY, toX, toY;
15957         toX = moveList[target - 1][2] - AAA;
15958         toY = moveList[target - 1][3] - ONE;
15959         if (moveList[target - 1][1] == '@') {
15960             if (appData.highlightLastMove) {
15961                 SetHighlights(-1, -1, toX, toY);
15962             }
15963         } else {
15964             fromX = moveList[target - 1][0] - AAA;
15965             fromY = moveList[target - 1][1] - ONE;
15966             if (target == currentMove + 1) {
15967                 if(moveList[target - 1][4] == ';') { // multi-leg
15968                     killX = moveList[target - 1][5] - AAA;
15969                     killY = moveList[target - 1][6] - ONE;
15970                 }
15971                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15972                 killX = killY = -1;
15973             }
15974             if (appData.highlightLastMove) {
15975                 SetHighlights(fromX, fromY, toX, toY);
15976             }
15977         }
15978     }
15979     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15980         gameMode == Training || gameMode == PlayFromGameFile ||
15981         gameMode == AnalyzeFile) {
15982         while (currentMove < target) {
15983             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15984             SendMoveToProgram(currentMove++, &first);
15985         }
15986     } else {
15987         currentMove = target;
15988     }
15989
15990     if (gameMode == EditGame || gameMode == EndOfGame) {
15991         whiteTimeRemaining = timeRemaining[0][currentMove];
15992         blackTimeRemaining = timeRemaining[1][currentMove];
15993     }
15994     DisplayBothClocks();
15995     DisplayMove(currentMove - 1);
15996     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15997     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15998     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15999         DisplayComment(currentMove - 1, commentList[currentMove]);
16000     }
16001     ClearMap(); // [HGM] exclude: invalidate map
16002 }
16003
16004
16005 void
16006 ForwardEvent ()
16007 {
16008     if (gameMode == IcsExamining && !pausing) {
16009         SendToICS(ics_prefix);
16010         SendToICS("forward\n");
16011     } else {
16012         ForwardInner(currentMove + 1);
16013     }
16014 }
16015
16016 void
16017 ToEndEvent ()
16018 {
16019     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16020         /* to optimze, we temporarily turn off analysis mode while we feed
16021          * the remaining moves to the engine. Otherwise we get analysis output
16022          * after each move.
16023          */
16024         if (first.analysisSupport) {
16025           SendToProgram("exit\nforce\n", &first);
16026           first.analyzing = FALSE;
16027         }
16028     }
16029
16030     if (gameMode == IcsExamining && !pausing) {
16031         SendToICS(ics_prefix);
16032         SendToICS("forward 999999\n");
16033     } else {
16034         ForwardInner(forwardMostMove);
16035     }
16036
16037     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16038         /* we have fed all the moves, so reactivate analysis mode */
16039         SendToProgram("analyze\n", &first);
16040         first.analyzing = TRUE;
16041         /*first.maybeThinking = TRUE;*/
16042         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16043     }
16044 }
16045
16046 void
16047 BackwardInner (int target)
16048 {
16049     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16050
16051     if (appData.debugMode)
16052         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16053                 target, currentMove, forwardMostMove);
16054
16055     if (gameMode == EditPosition) return;
16056     seekGraphUp = FALSE;
16057     MarkTargetSquares(1);
16058     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16059     if (currentMove <= backwardMostMove) {
16060         ClearHighlights();
16061         DrawPosition(full_redraw, boards[currentMove]);
16062         return;
16063     }
16064     if (gameMode == PlayFromGameFile && !pausing)
16065       PauseEvent();
16066
16067     if (moveList[target][0]) {
16068         int fromX, fromY, toX, toY;
16069         toX = moveList[target][2] - AAA;
16070         toY = moveList[target][3] - ONE;
16071         if (moveList[target][1] == '@') {
16072             if (appData.highlightLastMove) {
16073                 SetHighlights(-1, -1, toX, toY);
16074             }
16075         } else {
16076             fromX = moveList[target][0] - AAA;
16077             fromY = moveList[target][1] - ONE;
16078             if (target == currentMove - 1) {
16079                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16080             }
16081             if (appData.highlightLastMove) {
16082                 SetHighlights(fromX, fromY, toX, toY);
16083             }
16084         }
16085     }
16086     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16087         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16088         while (currentMove > target) {
16089             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16090                 // null move cannot be undone. Reload program with move history before it.
16091                 int i;
16092                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16093                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16094                 }
16095                 SendBoard(&first, i);
16096               if(second.analyzing) SendBoard(&second, i);
16097                 for(currentMove=i; currentMove<target; currentMove++) {
16098                     SendMoveToProgram(currentMove, &first);
16099                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16100                 }
16101                 break;
16102             }
16103             SendToBoth("undo\n");
16104             currentMove--;
16105         }
16106     } else {
16107         currentMove = target;
16108     }
16109
16110     if (gameMode == EditGame || gameMode == EndOfGame) {
16111         whiteTimeRemaining = timeRemaining[0][currentMove];
16112         blackTimeRemaining = timeRemaining[1][currentMove];
16113     }
16114     DisplayBothClocks();
16115     DisplayMove(currentMove - 1);
16116     DrawPosition(full_redraw, boards[currentMove]);
16117     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16118     // [HGM] PV info: routine tests if comment empty
16119     DisplayComment(currentMove - 1, commentList[currentMove]);
16120     ClearMap(); // [HGM] exclude: invalidate map
16121 }
16122
16123 void
16124 BackwardEvent ()
16125 {
16126     if (gameMode == IcsExamining && !pausing) {
16127         SendToICS(ics_prefix);
16128         SendToICS("backward\n");
16129     } else {
16130         BackwardInner(currentMove - 1);
16131     }
16132 }
16133
16134 void
16135 ToStartEvent ()
16136 {
16137     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16138         /* to optimize, we temporarily turn off analysis mode while we undo
16139          * all the moves. Otherwise we get analysis output after each undo.
16140          */
16141         if (first.analysisSupport) {
16142           SendToProgram("exit\nforce\n", &first);
16143           first.analyzing = FALSE;
16144         }
16145     }
16146
16147     if (gameMode == IcsExamining && !pausing) {
16148         SendToICS(ics_prefix);
16149         SendToICS("backward 999999\n");
16150     } else {
16151         BackwardInner(backwardMostMove);
16152     }
16153
16154     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16155         /* we have fed all the moves, so reactivate analysis mode */
16156         SendToProgram("analyze\n", &first);
16157         first.analyzing = TRUE;
16158         /*first.maybeThinking = TRUE;*/
16159         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16160     }
16161 }
16162
16163 void
16164 ToNrEvent (int to)
16165 {
16166   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16167   if (to >= forwardMostMove) to = forwardMostMove;
16168   if (to <= backwardMostMove) to = backwardMostMove;
16169   if (to < currentMove) {
16170     BackwardInner(to);
16171   } else {
16172     ForwardInner(to);
16173   }
16174 }
16175
16176 void
16177 RevertEvent (Boolean annotate)
16178 {
16179     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16180         return;
16181     }
16182     if (gameMode != IcsExamining) {
16183         DisplayError(_("You are not examining a game"), 0);
16184         return;
16185     }
16186     if (pausing) {
16187         DisplayError(_("You can't revert while pausing"), 0);
16188         return;
16189     }
16190     SendToICS(ics_prefix);
16191     SendToICS("revert\n");
16192 }
16193
16194 void
16195 RetractMoveEvent ()
16196 {
16197     switch (gameMode) {
16198       case MachinePlaysWhite:
16199       case MachinePlaysBlack:
16200         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16201             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16202             return;
16203         }
16204         if (forwardMostMove < 2) return;
16205         currentMove = forwardMostMove = forwardMostMove - 2;
16206         whiteTimeRemaining = timeRemaining[0][currentMove];
16207         blackTimeRemaining = timeRemaining[1][currentMove];
16208         DisplayBothClocks();
16209         DisplayMove(currentMove - 1);
16210         ClearHighlights();/*!! could figure this out*/
16211         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16212         SendToProgram("remove\n", &first);
16213         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16214         break;
16215
16216       case BeginningOfGame:
16217       default:
16218         break;
16219
16220       case IcsPlayingWhite:
16221       case IcsPlayingBlack:
16222         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16223             SendToICS(ics_prefix);
16224             SendToICS("takeback 2\n");
16225         } else {
16226             SendToICS(ics_prefix);
16227             SendToICS("takeback 1\n");
16228         }
16229         break;
16230     }
16231 }
16232
16233 void
16234 MoveNowEvent ()
16235 {
16236     ChessProgramState *cps;
16237
16238     switch (gameMode) {
16239       case MachinePlaysWhite:
16240         if (!WhiteOnMove(forwardMostMove)) {
16241             DisplayError(_("It is your turn"), 0);
16242             return;
16243         }
16244         cps = &first;
16245         break;
16246       case MachinePlaysBlack:
16247         if (WhiteOnMove(forwardMostMove)) {
16248             DisplayError(_("It is your turn"), 0);
16249             return;
16250         }
16251         cps = &first;
16252         break;
16253       case TwoMachinesPlay:
16254         if (WhiteOnMove(forwardMostMove) ==
16255             (first.twoMachinesColor[0] == 'w')) {
16256             cps = &first;
16257         } else {
16258             cps = &second;
16259         }
16260         break;
16261       case BeginningOfGame:
16262       default:
16263         return;
16264     }
16265     SendToProgram("?\n", cps);
16266 }
16267
16268 void
16269 TruncateGameEvent ()
16270 {
16271     EditGameEvent();
16272     if (gameMode != EditGame) return;
16273     TruncateGame();
16274 }
16275
16276 void
16277 TruncateGame ()
16278 {
16279     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16280     if (forwardMostMove > currentMove) {
16281         if (gameInfo.resultDetails != NULL) {
16282             free(gameInfo.resultDetails);
16283             gameInfo.resultDetails = NULL;
16284             gameInfo.result = GameUnfinished;
16285         }
16286         forwardMostMove = currentMove;
16287         HistorySet(parseList, backwardMostMove, forwardMostMove,
16288                    currentMove-1);
16289     }
16290 }
16291
16292 void
16293 HintEvent ()
16294 {
16295     if (appData.noChessProgram) return;
16296     switch (gameMode) {
16297       case MachinePlaysWhite:
16298         if (WhiteOnMove(forwardMostMove)) {
16299             DisplayError(_("Wait until your turn."), 0);
16300             return;
16301         }
16302         break;
16303       case BeginningOfGame:
16304       case MachinePlaysBlack:
16305         if (!WhiteOnMove(forwardMostMove)) {
16306             DisplayError(_("Wait until your turn."), 0);
16307             return;
16308         }
16309         break;
16310       default:
16311         DisplayError(_("No hint available"), 0);
16312         return;
16313     }
16314     SendToProgram("hint\n", &first);
16315     hintRequested = TRUE;
16316 }
16317
16318 int
16319 SaveSelected (FILE *g, int dummy, char *dummy2)
16320 {
16321     ListGame * lg = (ListGame *) gameList.head;
16322     int nItem, cnt=0;
16323     FILE *f;
16324
16325     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16326         DisplayError(_("Game list not loaded or empty"), 0);
16327         return 0;
16328     }
16329
16330     creatingBook = TRUE; // suppresses stuff during load game
16331
16332     /* Get list size */
16333     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16334         if(lg->position >= 0) { // selected?
16335             LoadGame(f, nItem, "", TRUE);
16336             SaveGamePGN2(g); // leaves g open
16337             cnt++; DoEvents();
16338         }
16339         lg = (ListGame *) lg->node.succ;
16340     }
16341
16342     fclose(g);
16343     creatingBook = FALSE;
16344
16345     return cnt;
16346 }
16347
16348 void
16349 CreateBookEvent ()
16350 {
16351     ListGame * lg = (ListGame *) gameList.head;
16352     FILE *f, *g;
16353     int nItem;
16354     static int secondTime = FALSE;
16355
16356     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16357         DisplayError(_("Game list not loaded or empty"), 0);
16358         return;
16359     }
16360
16361     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16362         fclose(g);
16363         secondTime++;
16364         DisplayNote(_("Book file exists! Try again for overwrite."));
16365         return;
16366     }
16367
16368     creatingBook = TRUE;
16369     secondTime = FALSE;
16370
16371     /* Get list size */
16372     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16373         if(lg->position >= 0) {
16374             LoadGame(f, nItem, "", TRUE);
16375             AddGameToBook(TRUE);
16376             DoEvents();
16377         }
16378         lg = (ListGame *) lg->node.succ;
16379     }
16380
16381     creatingBook = FALSE;
16382     FlushBook();
16383 }
16384
16385 void
16386 BookEvent ()
16387 {
16388     if (appData.noChessProgram) return;
16389     switch (gameMode) {
16390       case MachinePlaysWhite:
16391         if (WhiteOnMove(forwardMostMove)) {
16392             DisplayError(_("Wait until your turn."), 0);
16393             return;
16394         }
16395         break;
16396       case BeginningOfGame:
16397       case MachinePlaysBlack:
16398         if (!WhiteOnMove(forwardMostMove)) {
16399             DisplayError(_("Wait until your turn."), 0);
16400             return;
16401         }
16402         break;
16403       case EditPosition:
16404         EditPositionDone(TRUE);
16405         break;
16406       case TwoMachinesPlay:
16407         return;
16408       default:
16409         break;
16410     }
16411     SendToProgram("bk\n", &first);
16412     bookOutput[0] = NULLCHAR;
16413     bookRequested = TRUE;
16414 }
16415
16416 void
16417 AboutGameEvent ()
16418 {
16419     char *tags = PGNTags(&gameInfo);
16420     TagsPopUp(tags, CmailMsg());
16421     free(tags);
16422 }
16423
16424 /* end button procedures */
16425
16426 void
16427 PrintPosition (FILE *fp, int move)
16428 {
16429     int i, j;
16430
16431     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16432         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16433             char c = PieceToChar(boards[move][i][j]);
16434             fputc(c == '?' ? '.' : c, fp);
16435             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16436         }
16437     }
16438     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16439       fprintf(fp, "white to play\n");
16440     else
16441       fprintf(fp, "black to play\n");
16442 }
16443
16444 void
16445 PrintOpponents (FILE *fp)
16446 {
16447     if (gameInfo.white != NULL) {
16448         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16449     } else {
16450         fprintf(fp, "\n");
16451     }
16452 }
16453
16454 /* Find last component of program's own name, using some heuristics */
16455 void
16456 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16457 {
16458     char *p, *q, c;
16459     int local = (strcmp(host, "localhost") == 0);
16460     while (!local && (p = strchr(prog, ';')) != NULL) {
16461         p++;
16462         while (*p == ' ') p++;
16463         prog = p;
16464     }
16465     if (*prog == '"' || *prog == '\'') {
16466         q = strchr(prog + 1, *prog);
16467     } else {
16468         q = strchr(prog, ' ');
16469     }
16470     if (q == NULL) q = prog + strlen(prog);
16471     p = q;
16472     while (p >= prog && *p != '/' && *p != '\\') p--;
16473     p++;
16474     if(p == prog && *p == '"') p++;
16475     c = *q; *q = 0;
16476     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16477     memcpy(buf, p, q - p);
16478     buf[q - p] = NULLCHAR;
16479     if (!local) {
16480         strcat(buf, "@");
16481         strcat(buf, host);
16482     }
16483 }
16484
16485 char *
16486 TimeControlTagValue ()
16487 {
16488     char buf[MSG_SIZ];
16489     if (!appData.clockMode) {
16490       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16491     } else if (movesPerSession > 0) {
16492       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16493     } else if (timeIncrement == 0) {
16494       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16495     } else {
16496       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16497     }
16498     return StrSave(buf);
16499 }
16500
16501 void
16502 SetGameInfo ()
16503 {
16504     /* This routine is used only for certain modes */
16505     VariantClass v = gameInfo.variant;
16506     ChessMove r = GameUnfinished;
16507     char *p = NULL;
16508
16509     if(keepInfo) return;
16510
16511     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16512         r = gameInfo.result;
16513         p = gameInfo.resultDetails;
16514         gameInfo.resultDetails = NULL;
16515     }
16516     ClearGameInfo(&gameInfo);
16517     gameInfo.variant = v;
16518
16519     switch (gameMode) {
16520       case MachinePlaysWhite:
16521         gameInfo.event = StrSave( appData.pgnEventHeader );
16522         gameInfo.site = StrSave(HostName());
16523         gameInfo.date = PGNDate();
16524         gameInfo.round = StrSave("-");
16525         gameInfo.white = StrSave(first.tidy);
16526         gameInfo.black = StrSave(UserName());
16527         gameInfo.timeControl = TimeControlTagValue();
16528         break;
16529
16530       case MachinePlaysBlack:
16531         gameInfo.event = StrSave( appData.pgnEventHeader );
16532         gameInfo.site = StrSave(HostName());
16533         gameInfo.date = PGNDate();
16534         gameInfo.round = StrSave("-");
16535         gameInfo.white = StrSave(UserName());
16536         gameInfo.black = StrSave(first.tidy);
16537         gameInfo.timeControl = TimeControlTagValue();
16538         break;
16539
16540       case TwoMachinesPlay:
16541         gameInfo.event = StrSave( appData.pgnEventHeader );
16542         gameInfo.site = StrSave(HostName());
16543         gameInfo.date = PGNDate();
16544         if (roundNr > 0) {
16545             char buf[MSG_SIZ];
16546             snprintf(buf, MSG_SIZ, "%d", roundNr);
16547             gameInfo.round = StrSave(buf);
16548         } else {
16549             gameInfo.round = StrSave("-");
16550         }
16551         if (first.twoMachinesColor[0] == 'w') {
16552             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16553             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16554         } else {
16555             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16556             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16557         }
16558         gameInfo.timeControl = TimeControlTagValue();
16559         break;
16560
16561       case EditGame:
16562         gameInfo.event = StrSave("Edited game");
16563         gameInfo.site = StrSave(HostName());
16564         gameInfo.date = PGNDate();
16565         gameInfo.round = StrSave("-");
16566         gameInfo.white = StrSave("-");
16567         gameInfo.black = StrSave("-");
16568         gameInfo.result = r;
16569         gameInfo.resultDetails = p;
16570         break;
16571
16572       case EditPosition:
16573         gameInfo.event = StrSave("Edited position");
16574         gameInfo.site = StrSave(HostName());
16575         gameInfo.date = PGNDate();
16576         gameInfo.round = StrSave("-");
16577         gameInfo.white = StrSave("-");
16578         gameInfo.black = StrSave("-");
16579         break;
16580
16581       case IcsPlayingWhite:
16582       case IcsPlayingBlack:
16583       case IcsObserving:
16584       case IcsExamining:
16585         break;
16586
16587       case PlayFromGameFile:
16588         gameInfo.event = StrSave("Game from non-PGN file");
16589         gameInfo.site = StrSave(HostName());
16590         gameInfo.date = PGNDate();
16591         gameInfo.round = StrSave("-");
16592         gameInfo.white = StrSave("?");
16593         gameInfo.black = StrSave("?");
16594         break;
16595
16596       default:
16597         break;
16598     }
16599 }
16600
16601 void
16602 ReplaceComment (int index, char *text)
16603 {
16604     int len;
16605     char *p;
16606     float score;
16607
16608     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16609        pvInfoList[index-1].depth == len &&
16610        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16611        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16612     while (*text == '\n') text++;
16613     len = strlen(text);
16614     while (len > 0 && text[len - 1] == '\n') len--;
16615
16616     if (commentList[index] != NULL)
16617       free(commentList[index]);
16618
16619     if (len == 0) {
16620         commentList[index] = NULL;
16621         return;
16622     }
16623   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16624       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16625       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16626     commentList[index] = (char *) malloc(len + 2);
16627     strncpy(commentList[index], text, len);
16628     commentList[index][len] = '\n';
16629     commentList[index][len + 1] = NULLCHAR;
16630   } else {
16631     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16632     char *p;
16633     commentList[index] = (char *) malloc(len + 7);
16634     safeStrCpy(commentList[index], "{\n", 3);
16635     safeStrCpy(commentList[index]+2, text, len+1);
16636     commentList[index][len+2] = NULLCHAR;
16637     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16638     strcat(commentList[index], "\n}\n");
16639   }
16640 }
16641
16642 void
16643 CrushCRs (char *text)
16644 {
16645   char *p = text;
16646   char *q = text;
16647   char ch;
16648
16649   do {
16650     ch = *p++;
16651     if (ch == '\r') continue;
16652     *q++ = ch;
16653   } while (ch != '\0');
16654 }
16655
16656 void
16657 AppendComment (int index, char *text, Boolean addBraces)
16658 /* addBraces  tells if we should add {} */
16659 {
16660     int oldlen, len;
16661     char *old;
16662
16663 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16664     if(addBraces == 3) addBraces = 0; else // force appending literally
16665     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16666
16667     CrushCRs(text);
16668     while (*text == '\n') text++;
16669     len = strlen(text);
16670     while (len > 0 && text[len - 1] == '\n') len--;
16671     text[len] = NULLCHAR;
16672
16673     if (len == 0) return;
16674
16675     if (commentList[index] != NULL) {
16676       Boolean addClosingBrace = addBraces;
16677         old = commentList[index];
16678         oldlen = strlen(old);
16679         while(commentList[index][oldlen-1] ==  '\n')
16680           commentList[index][--oldlen] = NULLCHAR;
16681         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16682         safeStrCpy(commentList[index], old, oldlen + len + 6);
16683         free(old);
16684         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16685         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16686           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16687           while (*text == '\n') { text++; len--; }
16688           commentList[index][--oldlen] = NULLCHAR;
16689       }
16690         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16691         else          strcat(commentList[index], "\n");
16692         strcat(commentList[index], text);
16693         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16694         else          strcat(commentList[index], "\n");
16695     } else {
16696         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16697         if(addBraces)
16698           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16699         else commentList[index][0] = NULLCHAR;
16700         strcat(commentList[index], text);
16701         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16702         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16703     }
16704 }
16705
16706 static char *
16707 FindStr (char * text, char * sub_text)
16708 {
16709     char * result = strstr( text, sub_text );
16710
16711     if( result != NULL ) {
16712         result += strlen( sub_text );
16713     }
16714
16715     return result;
16716 }
16717
16718 /* [AS] Try to extract PV info from PGN comment */
16719 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16720 char *
16721 GetInfoFromComment (int index, char * text)
16722 {
16723     char * sep = text, *p;
16724
16725     if( text != NULL && index > 0 ) {
16726         int score = 0;
16727         int depth = 0;
16728         int time = -1, sec = 0, deci;
16729         char * s_eval = FindStr( text, "[%eval " );
16730         char * s_emt = FindStr( text, "[%emt " );
16731 #if 0
16732         if( s_eval != NULL || s_emt != NULL ) {
16733 #else
16734         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16735 #endif
16736             /* New style */
16737             char delim;
16738
16739             if( s_eval != NULL ) {
16740                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16741                     return text;
16742                 }
16743
16744                 if( delim != ']' ) {
16745                     return text;
16746                 }
16747             }
16748
16749             if( s_emt != NULL ) {
16750             }
16751                 return text;
16752         }
16753         else {
16754             /* We expect something like: [+|-]nnn.nn/dd */
16755             int score_lo = 0;
16756
16757             if(*text != '{') return text; // [HGM] braces: must be normal comment
16758
16759             sep = strchr( text, '/' );
16760             if( sep == NULL || sep < (text+4) ) {
16761                 return text;
16762             }
16763
16764             p = text;
16765             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16766             if(p[1] == '(') { // comment starts with PV
16767                p = strchr(p, ')'); // locate end of PV
16768                if(p == NULL || sep < p+5) return text;
16769                // at this point we have something like "{(.*) +0.23/6 ..."
16770                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16771                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16772                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16773             }
16774             time = -1; sec = -1; deci = -1;
16775             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16776                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16777                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16778                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16779                 return text;
16780             }
16781
16782             if( score_lo < 0 || score_lo >= 100 ) {
16783                 return text;
16784             }
16785
16786             if(sec >= 0) time = 600*time + 10*sec; else
16787             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16788
16789             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16790
16791             /* [HGM] PV time: now locate end of PV info */
16792             while( *++sep >= '0' && *sep <= '9'); // strip depth
16793             if(time >= 0)
16794             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16795             if(sec >= 0)
16796             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16797             if(deci >= 0)
16798             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16799             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16800         }
16801
16802         if( depth <= 0 ) {
16803             return text;
16804         }
16805
16806         if( time < 0 ) {
16807             time = -1;
16808         }
16809
16810         pvInfoList[index-1].depth = depth;
16811         pvInfoList[index-1].score = score;
16812         pvInfoList[index-1].time  = 10*time; // centi-sec
16813         if(*sep == '}') *sep = 0; else *--sep = '{';
16814         if(p != text) {
16815             while(*p++ = *sep++)
16816                                 ;
16817             sep = text;
16818         } // squeeze out space between PV and comment, and return both
16819     }
16820     return sep;
16821 }
16822
16823 void
16824 SendToProgram (char *message, ChessProgramState *cps)
16825 {
16826     int count, outCount, error;
16827     char buf[MSG_SIZ];
16828
16829     if (cps->pr == NoProc) return;
16830     Attention(cps);
16831
16832     if (appData.debugMode) {
16833         TimeMark now;
16834         GetTimeMark(&now);
16835         fprintf(debugFP, "%ld >%-6s: %s",
16836                 SubtractTimeMarks(&now, &programStartTime),
16837                 cps->which, message);
16838         if(serverFP)
16839             fprintf(serverFP, "%ld >%-6s: %s",
16840                 SubtractTimeMarks(&now, &programStartTime),
16841                 cps->which, message), fflush(serverFP);
16842     }
16843
16844     count = strlen(message);
16845     outCount = OutputToProcess(cps->pr, message, count, &error);
16846     if (outCount < count && !exiting
16847                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16848       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16849       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16850         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16851             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16852                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16853                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16854                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16855             } else {
16856                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16857                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16858                 gameInfo.result = res;
16859             }
16860             gameInfo.resultDetails = StrSave(buf);
16861         }
16862         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16863         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16864     }
16865 }
16866
16867 void
16868 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16869 {
16870     char *end_str;
16871     char buf[MSG_SIZ];
16872     ChessProgramState *cps = (ChessProgramState *)closure;
16873
16874     if (isr != cps->isr) return; /* Killed intentionally */
16875     if (count <= 0) {
16876         if (count == 0) {
16877             RemoveInputSource(cps->isr);
16878             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16879                     _(cps->which), cps->program);
16880             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16881             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16882                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16883                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16884                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16885                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16886                 } else {
16887                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16888                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16889                     gameInfo.result = res;
16890                 }
16891                 gameInfo.resultDetails = StrSave(buf);
16892             }
16893             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16894             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16895         } else {
16896             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16897                     _(cps->which), cps->program);
16898             RemoveInputSource(cps->isr);
16899
16900             /* [AS] Program is misbehaving badly... kill it */
16901             if( count == -2 ) {
16902                 DestroyChildProcess( cps->pr, 9 );
16903                 cps->pr = NoProc;
16904             }
16905
16906             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16907         }
16908         return;
16909     }
16910
16911     if ((end_str = strchr(message, '\r')) != NULL)
16912       *end_str = NULLCHAR;
16913     if ((end_str = strchr(message, '\n')) != NULL)
16914       *end_str = NULLCHAR;
16915
16916     if (appData.debugMode) {
16917         TimeMark now; int print = 1;
16918         char *quote = ""; char c; int i;
16919
16920         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16921                 char start = message[0];
16922                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16923                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16924                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16925                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16926                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16927                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16928                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16929                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16930                    sscanf(message, "hint: %c", &c)!=1 &&
16931                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16932                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16933                     print = (appData.engineComments >= 2);
16934                 }
16935                 message[0] = start; // restore original message
16936         }
16937         if(print) {
16938                 GetTimeMark(&now);
16939                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16940                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16941                         quote,
16942                         message);
16943                 if(serverFP)
16944                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16945                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16946                         quote,
16947                         message), fflush(serverFP);
16948         }
16949     }
16950
16951     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16952     if (appData.icsEngineAnalyze) {
16953         if (strstr(message, "whisper") != NULL ||
16954              strstr(message, "kibitz") != NULL ||
16955             strstr(message, "tellics") != NULL) return;
16956     }
16957
16958     HandleMachineMove(message, cps);
16959 }
16960
16961
16962 void
16963 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16964 {
16965     char buf[MSG_SIZ];
16966     int seconds;
16967
16968     if( timeControl_2 > 0 ) {
16969         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16970             tc = timeControl_2;
16971         }
16972     }
16973     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16974     inc /= cps->timeOdds;
16975     st  /= cps->timeOdds;
16976
16977     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16978
16979     if (st > 0) {
16980       /* Set exact time per move, normally using st command */
16981       if (cps->stKludge) {
16982         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16983         seconds = st % 60;
16984         if (seconds == 0) {
16985           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16986         } else {
16987           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16988         }
16989       } else {
16990         snprintf(buf, MSG_SIZ, "st %d\n", st);
16991       }
16992     } else {
16993       /* Set conventional or incremental time control, using level command */
16994       if (seconds == 0) {
16995         /* Note old gnuchess bug -- minutes:seconds used to not work.
16996            Fixed in later versions, but still avoid :seconds
16997            when seconds is 0. */
16998         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16999       } else {
17000         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17001                  seconds, inc/1000.);
17002       }
17003     }
17004     SendToProgram(buf, cps);
17005
17006     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17007     /* Orthogonally, limit search to given depth */
17008     if (sd > 0) {
17009       if (cps->sdKludge) {
17010         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17011       } else {
17012         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17013       }
17014       SendToProgram(buf, cps);
17015     }
17016
17017     if(cps->nps >= 0) { /* [HGM] nps */
17018         if(cps->supportsNPS == FALSE)
17019           cps->nps = -1; // don't use if engine explicitly says not supported!
17020         else {
17021           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17022           SendToProgram(buf, cps);
17023         }
17024     }
17025 }
17026
17027 ChessProgramState *
17028 WhitePlayer ()
17029 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17030 {
17031     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17032        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17033         return &second;
17034     return &first;
17035 }
17036
17037 void
17038 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17039 {
17040     char message[MSG_SIZ];
17041     long time, otime;
17042
17043     /* Note: this routine must be called when the clocks are stopped
17044        or when they have *just* been set or switched; otherwise
17045        it will be off by the time since the current tick started.
17046     */
17047     if (machineWhite) {
17048         time = whiteTimeRemaining / 10;
17049         otime = blackTimeRemaining / 10;
17050     } else {
17051         time = blackTimeRemaining / 10;
17052         otime = whiteTimeRemaining / 10;
17053     }
17054     /* [HGM] translate opponent's time by time-odds factor */
17055     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17056
17057     if (time <= 0) time = 1;
17058     if (otime <= 0) otime = 1;
17059
17060     snprintf(message, MSG_SIZ, "time %ld\n", time);
17061     SendToProgram(message, cps);
17062
17063     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17064     SendToProgram(message, cps);
17065 }
17066
17067 char *
17068 EngineDefinedVariant (ChessProgramState *cps, int n)
17069 {   // return name of n-th unknown variant that engine supports
17070     static char buf[MSG_SIZ];
17071     char *p, *s = cps->variants;
17072     if(!s) return NULL;
17073     do { // parse string from variants feature
17074       VariantClass v;
17075         p = strchr(s, ',');
17076         if(p) *p = NULLCHAR;
17077       v = StringToVariant(s);
17078       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17079         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17080             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17081                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17082                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17083                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17084             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17085         }
17086         if(p) *p++ = ',';
17087         if(n < 0) return buf;
17088     } while(s = p);
17089     return NULL;
17090 }
17091
17092 int
17093 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17094 {
17095   char buf[MSG_SIZ];
17096   int len = strlen(name);
17097   int val;
17098
17099   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17100     (*p) += len + 1;
17101     sscanf(*p, "%d", &val);
17102     *loc = (val != 0);
17103     while (**p && **p != ' ')
17104       (*p)++;
17105     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17106     SendToProgram(buf, cps);
17107     return TRUE;
17108   }
17109   return FALSE;
17110 }
17111
17112 int
17113 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17114 {
17115   char buf[MSG_SIZ];
17116   int len = strlen(name);
17117   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17118     (*p) += len + 1;
17119     sscanf(*p, "%d", loc);
17120     while (**p && **p != ' ') (*p)++;
17121     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17122     SendToProgram(buf, cps);
17123     return TRUE;
17124   }
17125   return FALSE;
17126 }
17127
17128 int
17129 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17130 {
17131   char buf[MSG_SIZ];
17132   int len = strlen(name);
17133   if (strncmp((*p), name, len) == 0
17134       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17135     (*p) += len + 2;
17136     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17137     sscanf(*p, "%[^\"]", *loc);
17138     while (**p && **p != '\"') (*p)++;
17139     if (**p == '\"') (*p)++;
17140     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17141     SendToProgram(buf, cps);
17142     return TRUE;
17143   }
17144   return FALSE;
17145 }
17146
17147 int
17148 ParseOption (Option *opt, ChessProgramState *cps)
17149 // [HGM] options: process the string that defines an engine option, and determine
17150 // name, type, default value, and allowed value range
17151 {
17152         char *p, *q, buf[MSG_SIZ];
17153         int n, min = (-1)<<31, max = 1<<31, def;
17154
17155         opt->target = &opt->value;   // OK for spin/slider and checkbox
17156         if(p = strstr(opt->name, " -spin ")) {
17157             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17158             if(max < min) max = min; // enforce consistency
17159             if(def < min) def = min;
17160             if(def > max) def = max;
17161             opt->value = def;
17162             opt->min = min;
17163             opt->max = max;
17164             opt->type = Spin;
17165         } else if((p = strstr(opt->name, " -slider "))) {
17166             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17167             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17168             if(max < min) max = min; // enforce consistency
17169             if(def < min) def = min;
17170             if(def > max) def = max;
17171             opt->value = def;
17172             opt->min = min;
17173             opt->max = max;
17174             opt->type = Spin; // Slider;
17175         } else if((p = strstr(opt->name, " -string "))) {
17176             opt->textValue = p+9;
17177             opt->type = TextBox;
17178             opt->target = &opt->textValue;
17179         } else if((p = strstr(opt->name, " -file "))) {
17180             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17181             opt->target = opt->textValue = p+7;
17182             opt->type = FileName; // FileName;
17183             opt->target = &opt->textValue;
17184         } else if((p = strstr(opt->name, " -path "))) {
17185             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17186             opt->target = opt->textValue = p+7;
17187             opt->type = PathName; // PathName;
17188             opt->target = &opt->textValue;
17189         } else if(p = strstr(opt->name, " -check ")) {
17190             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17191             opt->value = (def != 0);
17192             opt->type = CheckBox;
17193         } else if(p = strstr(opt->name, " -combo ")) {
17194             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17195             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17196             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17197             opt->value = n = 0;
17198             while(q = StrStr(q, " /// ")) {
17199                 n++; *q = 0;    // count choices, and null-terminate each of them
17200                 q += 5;
17201                 if(*q == '*') { // remember default, which is marked with * prefix
17202                     q++;
17203                     opt->value = n;
17204                 }
17205                 cps->comboList[cps->comboCnt++] = q;
17206             }
17207             cps->comboList[cps->comboCnt++] = NULL;
17208             opt->max = n + 1;
17209             opt->type = ComboBox;
17210         } else if(p = strstr(opt->name, " -button")) {
17211             opt->type = Button;
17212         } else if(p = strstr(opt->name, " -save")) {
17213             opt->type = SaveButton;
17214         } else return FALSE;
17215         *p = 0; // terminate option name
17216         // now look if the command-line options define a setting for this engine option.
17217         if(cps->optionSettings && cps->optionSettings[0])
17218             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17219         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17220           snprintf(buf, MSG_SIZ, "option %s", p);
17221                 if(p = strstr(buf, ",")) *p = 0;
17222                 if(q = strchr(buf, '=')) switch(opt->type) {
17223                     case ComboBox:
17224                         for(n=0; n<opt->max; n++)
17225                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17226                         break;
17227                     case TextBox:
17228                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17229                         break;
17230                     case Spin:
17231                     case CheckBox:
17232                         opt->value = atoi(q+1);
17233                     default:
17234                         break;
17235                 }
17236                 strcat(buf, "\n");
17237                 SendToProgram(buf, cps);
17238         }
17239         return TRUE;
17240 }
17241
17242 void
17243 FeatureDone (ChessProgramState *cps, int val)
17244 {
17245   DelayedEventCallback cb = GetDelayedEvent();
17246   if ((cb == InitBackEnd3 && cps == &first) ||
17247       (cb == SettingsMenuIfReady && cps == &second) ||
17248       (cb == LoadEngine) ||
17249       (cb == TwoMachinesEventIfReady)) {
17250     CancelDelayedEvent();
17251     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17252   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17253   cps->initDone = val;
17254   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17255 }
17256
17257 /* Parse feature command from engine */
17258 void
17259 ParseFeatures (char *args, ChessProgramState *cps)
17260 {
17261   char *p = args;
17262   char *q = NULL;
17263   int val;
17264   char buf[MSG_SIZ];
17265
17266   for (;;) {
17267     while (*p == ' ') p++;
17268     if (*p == NULLCHAR) return;
17269
17270     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17271     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17272     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17273     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17274     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17275     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17276     if (BoolFeature(&p, "reuse", &val, cps)) {
17277       /* Engine can disable reuse, but can't enable it if user said no */
17278       if (!val) cps->reuse = FALSE;
17279       continue;
17280     }
17281     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17282     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17283       if (gameMode == TwoMachinesPlay) {
17284         DisplayTwoMachinesTitle();
17285       } else {
17286         DisplayTitle("");
17287       }
17288       continue;
17289     }
17290     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17291     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17292     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17293     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17294     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17295     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17296     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17297     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17298     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17299     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17300     if (IntFeature(&p, "done", &val, cps)) {
17301       FeatureDone(cps, val);
17302       continue;
17303     }
17304     /* Added by Tord: */
17305     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17306     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17307     /* End of additions by Tord */
17308
17309     /* [HGM] added features: */
17310     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17311     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17312     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17313     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17314     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17315     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17316     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17317     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17318         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17319         FREE(cps->option[cps->nrOptions].name);
17320         cps->option[cps->nrOptions].name = q; q = NULL;
17321         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17322           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17323             SendToProgram(buf, cps);
17324             continue;
17325         }
17326         if(cps->nrOptions >= MAX_OPTIONS) {
17327             cps->nrOptions--;
17328             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17329             DisplayError(buf, 0);
17330         }
17331         continue;
17332     }
17333     /* End of additions by HGM */
17334
17335     /* unknown feature: complain and skip */
17336     q = p;
17337     while (*q && *q != '=') q++;
17338     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17339     SendToProgram(buf, cps);
17340     p = q;
17341     if (*p == '=') {
17342       p++;
17343       if (*p == '\"') {
17344         p++;
17345         while (*p && *p != '\"') p++;
17346         if (*p == '\"') p++;
17347       } else {
17348         while (*p && *p != ' ') p++;
17349       }
17350     }
17351   }
17352
17353 }
17354
17355 void
17356 PeriodicUpdatesEvent (int newState)
17357 {
17358     if (newState == appData.periodicUpdates)
17359       return;
17360
17361     appData.periodicUpdates=newState;
17362
17363     /* Display type changes, so update it now */
17364 //    DisplayAnalysis();
17365
17366     /* Get the ball rolling again... */
17367     if (newState) {
17368         AnalysisPeriodicEvent(1);
17369         StartAnalysisClock();
17370     }
17371 }
17372
17373 void
17374 PonderNextMoveEvent (int newState)
17375 {
17376     if (newState == appData.ponderNextMove) return;
17377     if (gameMode == EditPosition) EditPositionDone(TRUE);
17378     if (newState) {
17379         SendToProgram("hard\n", &first);
17380         if (gameMode == TwoMachinesPlay) {
17381             SendToProgram("hard\n", &second);
17382         }
17383     } else {
17384         SendToProgram("easy\n", &first);
17385         thinkOutput[0] = NULLCHAR;
17386         if (gameMode == TwoMachinesPlay) {
17387             SendToProgram("easy\n", &second);
17388         }
17389     }
17390     appData.ponderNextMove = newState;
17391 }
17392
17393 void
17394 NewSettingEvent (int option, int *feature, char *command, int value)
17395 {
17396     char buf[MSG_SIZ];
17397
17398     if (gameMode == EditPosition) EditPositionDone(TRUE);
17399     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17400     if(feature == NULL || *feature) SendToProgram(buf, &first);
17401     if (gameMode == TwoMachinesPlay) {
17402         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17403     }
17404 }
17405
17406 void
17407 ShowThinkingEvent ()
17408 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17409 {
17410     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17411     int newState = appData.showThinking
17412         // [HGM] thinking: other features now need thinking output as well
17413         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17414
17415     if (oldState == newState) return;
17416     oldState = newState;
17417     if (gameMode == EditPosition) EditPositionDone(TRUE);
17418     if (oldState) {
17419         SendToProgram("post\n", &first);
17420         if (gameMode == TwoMachinesPlay) {
17421             SendToProgram("post\n", &second);
17422         }
17423     } else {
17424         SendToProgram("nopost\n", &first);
17425         thinkOutput[0] = NULLCHAR;
17426         if (gameMode == TwoMachinesPlay) {
17427             SendToProgram("nopost\n", &second);
17428         }
17429     }
17430 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17431 }
17432
17433 void
17434 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17435 {
17436   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17437   if (pr == NoProc) return;
17438   AskQuestion(title, question, replyPrefix, pr);
17439 }
17440
17441 void
17442 TypeInEvent (char firstChar)
17443 {
17444     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17445         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17446         gameMode == AnalyzeMode || gameMode == EditGame ||
17447         gameMode == EditPosition || gameMode == IcsExamining ||
17448         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17449         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17450                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17451                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17452         gameMode == Training) PopUpMoveDialog(firstChar);
17453 }
17454
17455 void
17456 TypeInDoneEvent (char *move)
17457 {
17458         Board board;
17459         int n, fromX, fromY, toX, toY;
17460         char promoChar;
17461         ChessMove moveType;
17462
17463         // [HGM] FENedit
17464         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17465                 EditPositionPasteFEN(move);
17466                 return;
17467         }
17468         // [HGM] movenum: allow move number to be typed in any mode
17469         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17470           ToNrEvent(2*n-1);
17471           return;
17472         }
17473         // undocumented kludge: allow command-line option to be typed in!
17474         // (potentially fatal, and does not implement the effect of the option.)
17475         // should only be used for options that are values on which future decisions will be made,
17476         // and definitely not on options that would be used during initialization.
17477         if(strstr(move, "!!! -") == move) {
17478             ParseArgsFromString(move+4);
17479             return;
17480         }
17481
17482       if (gameMode != EditGame && currentMove != forwardMostMove &&
17483         gameMode != Training) {
17484         DisplayMoveError(_("Displayed move is not current"));
17485       } else {
17486         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17487           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17488         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17489         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17490           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17491           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17492         } else {
17493           DisplayMoveError(_("Could not parse move"));
17494         }
17495       }
17496 }
17497
17498 void
17499 DisplayMove (int moveNumber)
17500 {
17501     char message[MSG_SIZ];
17502     char res[MSG_SIZ];
17503     char cpThinkOutput[MSG_SIZ];
17504
17505     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17506
17507     if (moveNumber == forwardMostMove - 1 ||
17508         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17509
17510         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17511
17512         if (strchr(cpThinkOutput, '\n')) {
17513             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17514         }
17515     } else {
17516         *cpThinkOutput = NULLCHAR;
17517     }
17518
17519     /* [AS] Hide thinking from human user */
17520     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17521         *cpThinkOutput = NULLCHAR;
17522         if( thinkOutput[0] != NULLCHAR ) {
17523             int i;
17524
17525             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17526                 cpThinkOutput[i] = '.';
17527             }
17528             cpThinkOutput[i] = NULLCHAR;
17529             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17530         }
17531     }
17532
17533     if (moveNumber == forwardMostMove - 1 &&
17534         gameInfo.resultDetails != NULL) {
17535         if (gameInfo.resultDetails[0] == NULLCHAR) {
17536           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17537         } else {
17538           snprintf(res, MSG_SIZ, " {%s} %s",
17539                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17540         }
17541     } else {
17542         res[0] = NULLCHAR;
17543     }
17544
17545     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17546         DisplayMessage(res, cpThinkOutput);
17547     } else {
17548       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17549                 WhiteOnMove(moveNumber) ? " " : ".. ",
17550                 parseList[moveNumber], res);
17551         DisplayMessage(message, cpThinkOutput);
17552     }
17553 }
17554
17555 void
17556 DisplayComment (int moveNumber, char *text)
17557 {
17558     char title[MSG_SIZ];
17559
17560     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17561       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17562     } else {
17563       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17564               WhiteOnMove(moveNumber) ? " " : ".. ",
17565               parseList[moveNumber]);
17566     }
17567     if (text != NULL && (appData.autoDisplayComment || commentUp))
17568         CommentPopUp(title, text);
17569 }
17570
17571 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17572  * might be busy thinking or pondering.  It can be omitted if your
17573  * gnuchess is configured to stop thinking immediately on any user
17574  * input.  However, that gnuchess feature depends on the FIONREAD
17575  * ioctl, which does not work properly on some flavors of Unix.
17576  */
17577 void
17578 Attention (ChessProgramState *cps)
17579 {
17580 #if ATTENTION
17581     if (!cps->useSigint) return;
17582     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17583     switch (gameMode) {
17584       case MachinePlaysWhite:
17585       case MachinePlaysBlack:
17586       case TwoMachinesPlay:
17587       case IcsPlayingWhite:
17588       case IcsPlayingBlack:
17589       case AnalyzeMode:
17590       case AnalyzeFile:
17591         /* Skip if we know it isn't thinking */
17592         if (!cps->maybeThinking) return;
17593         if (appData.debugMode)
17594           fprintf(debugFP, "Interrupting %s\n", cps->which);
17595         InterruptChildProcess(cps->pr);
17596         cps->maybeThinking = FALSE;
17597         break;
17598       default:
17599         break;
17600     }
17601 #endif /*ATTENTION*/
17602 }
17603
17604 int
17605 CheckFlags ()
17606 {
17607     if (whiteTimeRemaining <= 0) {
17608         if (!whiteFlag) {
17609             whiteFlag = TRUE;
17610             if (appData.icsActive) {
17611                 if (appData.autoCallFlag &&
17612                     gameMode == IcsPlayingBlack && !blackFlag) {
17613                   SendToICS(ics_prefix);
17614                   SendToICS("flag\n");
17615                 }
17616             } else {
17617                 if (blackFlag) {
17618                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17619                 } else {
17620                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17621                     if (appData.autoCallFlag) {
17622                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17623                         return TRUE;
17624                     }
17625                 }
17626             }
17627         }
17628     }
17629     if (blackTimeRemaining <= 0) {
17630         if (!blackFlag) {
17631             blackFlag = TRUE;
17632             if (appData.icsActive) {
17633                 if (appData.autoCallFlag &&
17634                     gameMode == IcsPlayingWhite && !whiteFlag) {
17635                   SendToICS(ics_prefix);
17636                   SendToICS("flag\n");
17637                 }
17638             } else {
17639                 if (whiteFlag) {
17640                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17641                 } else {
17642                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17643                     if (appData.autoCallFlag) {
17644                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17645                         return TRUE;
17646                     }
17647                 }
17648             }
17649         }
17650     }
17651     return FALSE;
17652 }
17653
17654 void
17655 CheckTimeControl ()
17656 {
17657     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17658         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17659
17660     /*
17661      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17662      */
17663     if ( !WhiteOnMove(forwardMostMove) ) {
17664         /* White made time control */
17665         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17666         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17667         /* [HGM] time odds: correct new time quota for time odds! */
17668                                             / WhitePlayer()->timeOdds;
17669         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17670     } else {
17671         lastBlack -= blackTimeRemaining;
17672         /* Black made time control */
17673         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17674                                             / WhitePlayer()->other->timeOdds;
17675         lastWhite = whiteTimeRemaining;
17676     }
17677 }
17678
17679 void
17680 DisplayBothClocks ()
17681 {
17682     int wom = gameMode == EditPosition ?
17683       !blackPlaysFirst : WhiteOnMove(currentMove);
17684     DisplayWhiteClock(whiteTimeRemaining, wom);
17685     DisplayBlackClock(blackTimeRemaining, !wom);
17686 }
17687
17688
17689 /* Timekeeping seems to be a portability nightmare.  I think everyone
17690    has ftime(), but I'm really not sure, so I'm including some ifdefs
17691    to use other calls if you don't.  Clocks will be less accurate if
17692    you have neither ftime nor gettimeofday.
17693 */
17694
17695 /* VS 2008 requires the #include outside of the function */
17696 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17697 #include <sys/timeb.h>
17698 #endif
17699
17700 /* Get the current time as a TimeMark */
17701 void
17702 GetTimeMark (TimeMark *tm)
17703 {
17704 #if HAVE_GETTIMEOFDAY
17705
17706     struct timeval timeVal;
17707     struct timezone timeZone;
17708
17709     gettimeofday(&timeVal, &timeZone);
17710     tm->sec = (long) timeVal.tv_sec;
17711     tm->ms = (int) (timeVal.tv_usec / 1000L);
17712
17713 #else /*!HAVE_GETTIMEOFDAY*/
17714 #if HAVE_FTIME
17715
17716 // include <sys/timeb.h> / moved to just above start of function
17717     struct timeb timeB;
17718
17719     ftime(&timeB);
17720     tm->sec = (long) timeB.time;
17721     tm->ms = (int) timeB.millitm;
17722
17723 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17724     tm->sec = (long) time(NULL);
17725     tm->ms = 0;
17726 #endif
17727 #endif
17728 }
17729
17730 /* Return the difference in milliseconds between two
17731    time marks.  We assume the difference will fit in a long!
17732 */
17733 long
17734 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17735 {
17736     return 1000L*(tm2->sec - tm1->sec) +
17737            (long) (tm2->ms - tm1->ms);
17738 }
17739
17740
17741 /*
17742  * Code to manage the game clocks.
17743  *
17744  * In tournament play, black starts the clock and then white makes a move.
17745  * We give the human user a slight advantage if he is playing white---the
17746  * clocks don't run until he makes his first move, so it takes zero time.
17747  * Also, we don't account for network lag, so we could get out of sync
17748  * with GNU Chess's clock -- but then, referees are always right.
17749  */
17750
17751 static TimeMark tickStartTM;
17752 static long intendedTickLength;
17753
17754 long
17755 NextTickLength (long timeRemaining)
17756 {
17757     long nominalTickLength, nextTickLength;
17758
17759     if (timeRemaining > 0L && timeRemaining <= 10000L)
17760       nominalTickLength = 100L;
17761     else
17762       nominalTickLength = 1000L;
17763     nextTickLength = timeRemaining % nominalTickLength;
17764     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17765
17766     return nextTickLength;
17767 }
17768
17769 /* Adjust clock one minute up or down */
17770 void
17771 AdjustClock (Boolean which, int dir)
17772 {
17773     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17774     if(which) blackTimeRemaining += 60000*dir;
17775     else      whiteTimeRemaining += 60000*dir;
17776     DisplayBothClocks();
17777     adjustedClock = TRUE;
17778 }
17779
17780 /* Stop clocks and reset to a fresh time control */
17781 void
17782 ResetClocks ()
17783 {
17784     (void) StopClockTimer();
17785     if (appData.icsActive) {
17786         whiteTimeRemaining = blackTimeRemaining = 0;
17787     } else if (searchTime) {
17788         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17789         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17790     } else { /* [HGM] correct new time quote for time odds */
17791         whiteTC = blackTC = fullTimeControlString;
17792         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17793         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17794     }
17795     if (whiteFlag || blackFlag) {
17796         DisplayTitle("");
17797         whiteFlag = blackFlag = FALSE;
17798     }
17799     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17800     DisplayBothClocks();
17801     adjustedClock = FALSE;
17802 }
17803
17804 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17805
17806 /* Decrement running clock by amount of time that has passed */
17807 void
17808 DecrementClocks ()
17809 {
17810     long timeRemaining;
17811     long lastTickLength, fudge;
17812     TimeMark now;
17813
17814     if (!appData.clockMode) return;
17815     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17816
17817     GetTimeMark(&now);
17818
17819     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17820
17821     /* Fudge if we woke up a little too soon */
17822     fudge = intendedTickLength - lastTickLength;
17823     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17824
17825     if (WhiteOnMove(forwardMostMove)) {
17826         if(whiteNPS >= 0) lastTickLength = 0;
17827         timeRemaining = whiteTimeRemaining -= lastTickLength;
17828         if(timeRemaining < 0 && !appData.icsActive) {
17829             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17830             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17831                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17832                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17833             }
17834         }
17835         DisplayWhiteClock(whiteTimeRemaining - fudge,
17836                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17837     } else {
17838         if(blackNPS >= 0) lastTickLength = 0;
17839         timeRemaining = blackTimeRemaining -= lastTickLength;
17840         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17841             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17842             if(suddenDeath) {
17843                 blackStartMove = forwardMostMove;
17844                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17845             }
17846         }
17847         DisplayBlackClock(blackTimeRemaining - fudge,
17848                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17849     }
17850     if (CheckFlags()) return;
17851
17852     if(twoBoards) { // count down secondary board's clocks as well
17853         activePartnerTime -= lastTickLength;
17854         partnerUp = 1;
17855         if(activePartner == 'W')
17856             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17857         else
17858             DisplayBlackClock(activePartnerTime, TRUE);
17859         partnerUp = 0;
17860     }
17861
17862     tickStartTM = now;
17863     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17864     StartClockTimer(intendedTickLength);
17865
17866     /* if the time remaining has fallen below the alarm threshold, sound the
17867      * alarm. if the alarm has sounded and (due to a takeback or time control
17868      * with increment) the time remaining has increased to a level above the
17869      * threshold, reset the alarm so it can sound again.
17870      */
17871
17872     if (appData.icsActive && appData.icsAlarm) {
17873
17874         /* make sure we are dealing with the user's clock */
17875         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17876                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17877            )) return;
17878
17879         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17880             alarmSounded = FALSE;
17881         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17882             PlayAlarmSound();
17883             alarmSounded = TRUE;
17884         }
17885     }
17886 }
17887
17888
17889 /* A player has just moved, so stop the previously running
17890    clock and (if in clock mode) start the other one.
17891    We redisplay both clocks in case we're in ICS mode, because
17892    ICS gives us an update to both clocks after every move.
17893    Note that this routine is called *after* forwardMostMove
17894    is updated, so the last fractional tick must be subtracted
17895    from the color that is *not* on move now.
17896 */
17897 void
17898 SwitchClocks (int newMoveNr)
17899 {
17900     long lastTickLength;
17901     TimeMark now;
17902     int flagged = FALSE;
17903
17904     GetTimeMark(&now);
17905
17906     if (StopClockTimer() && appData.clockMode) {
17907         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17908         if (!WhiteOnMove(forwardMostMove)) {
17909             if(blackNPS >= 0) lastTickLength = 0;
17910             blackTimeRemaining -= lastTickLength;
17911            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17912 //         if(pvInfoList[forwardMostMove].time == -1)
17913                  pvInfoList[forwardMostMove].time =               // use GUI time
17914                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17915         } else {
17916            if(whiteNPS >= 0) lastTickLength = 0;
17917            whiteTimeRemaining -= lastTickLength;
17918            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17919 //         if(pvInfoList[forwardMostMove].time == -1)
17920                  pvInfoList[forwardMostMove].time =
17921                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17922         }
17923         flagged = CheckFlags();
17924     }
17925     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17926     CheckTimeControl();
17927
17928     if (flagged || !appData.clockMode) return;
17929
17930     switch (gameMode) {
17931       case MachinePlaysBlack:
17932       case MachinePlaysWhite:
17933       case BeginningOfGame:
17934         if (pausing) return;
17935         break;
17936
17937       case EditGame:
17938       case PlayFromGameFile:
17939       case IcsExamining:
17940         return;
17941
17942       default:
17943         break;
17944     }
17945
17946     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17947         if(WhiteOnMove(forwardMostMove))
17948              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17949         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17950     }
17951
17952     tickStartTM = now;
17953     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17954       whiteTimeRemaining : blackTimeRemaining);
17955     StartClockTimer(intendedTickLength);
17956 }
17957
17958
17959 /* Stop both clocks */
17960 void
17961 StopClocks ()
17962 {
17963     long lastTickLength;
17964     TimeMark now;
17965
17966     if (!StopClockTimer()) return;
17967     if (!appData.clockMode) return;
17968
17969     GetTimeMark(&now);
17970
17971     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17972     if (WhiteOnMove(forwardMostMove)) {
17973         if(whiteNPS >= 0) lastTickLength = 0;
17974         whiteTimeRemaining -= lastTickLength;
17975         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17976     } else {
17977         if(blackNPS >= 0) lastTickLength = 0;
17978         blackTimeRemaining -= lastTickLength;
17979         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17980     }
17981     CheckFlags();
17982 }
17983
17984 /* Start clock of player on move.  Time may have been reset, so
17985    if clock is already running, stop and restart it. */
17986 void
17987 StartClocks ()
17988 {
17989     (void) StopClockTimer(); /* in case it was running already */
17990     DisplayBothClocks();
17991     if (CheckFlags()) return;
17992
17993     if (!appData.clockMode) return;
17994     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17995
17996     GetTimeMark(&tickStartTM);
17997     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17998       whiteTimeRemaining : blackTimeRemaining);
17999
18000    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18001     whiteNPS = blackNPS = -1;
18002     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18003        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18004         whiteNPS = first.nps;
18005     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18006        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18007         blackNPS = first.nps;
18008     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18009         whiteNPS = second.nps;
18010     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18011         blackNPS = second.nps;
18012     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18013
18014     StartClockTimer(intendedTickLength);
18015 }
18016
18017 char *
18018 TimeString (long ms)
18019 {
18020     long second, minute, hour, day;
18021     char *sign = "";
18022     static char buf[32];
18023
18024     if (ms > 0 && ms <= 9900) {
18025       /* convert milliseconds to tenths, rounding up */
18026       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18027
18028       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18029       return buf;
18030     }
18031
18032     /* convert milliseconds to seconds, rounding up */
18033     /* use floating point to avoid strangeness of integer division
18034        with negative dividends on many machines */
18035     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18036
18037     if (second < 0) {
18038         sign = "-";
18039         second = -second;
18040     }
18041
18042     day = second / (60 * 60 * 24);
18043     second = second % (60 * 60 * 24);
18044     hour = second / (60 * 60);
18045     second = second % (60 * 60);
18046     minute = second / 60;
18047     second = second % 60;
18048
18049     if (day > 0)
18050       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18051               sign, day, hour, minute, second);
18052     else if (hour > 0)
18053       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18054     else
18055       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18056
18057     return buf;
18058 }
18059
18060
18061 /*
18062  * This is necessary because some C libraries aren't ANSI C compliant yet.
18063  */
18064 char *
18065 StrStr (char *string, char *match)
18066 {
18067     int i, length;
18068
18069     length = strlen(match);
18070
18071     for (i = strlen(string) - length; i >= 0; i--, string++)
18072       if (!strncmp(match, string, length))
18073         return string;
18074
18075     return NULL;
18076 }
18077
18078 char *
18079 StrCaseStr (char *string, char *match)
18080 {
18081     int i, j, length;
18082
18083     length = strlen(match);
18084
18085     for (i = strlen(string) - length; i >= 0; i--, string++) {
18086         for (j = 0; j < length; j++) {
18087             if (ToLower(match[j]) != ToLower(string[j]))
18088               break;
18089         }
18090         if (j == length) return string;
18091     }
18092
18093     return NULL;
18094 }
18095
18096 #ifndef _amigados
18097 int
18098 StrCaseCmp (char *s1, char *s2)
18099 {
18100     char c1, c2;
18101
18102     for (;;) {
18103         c1 = ToLower(*s1++);
18104         c2 = ToLower(*s2++);
18105         if (c1 > c2) return 1;
18106         if (c1 < c2) return -1;
18107         if (c1 == NULLCHAR) return 0;
18108     }
18109 }
18110
18111
18112 int
18113 ToLower (int c)
18114 {
18115     return isupper(c) ? tolower(c) : c;
18116 }
18117
18118
18119 int
18120 ToUpper (int c)
18121 {
18122     return islower(c) ? toupper(c) : c;
18123 }
18124 #endif /* !_amigados    */
18125
18126 char *
18127 StrSave (char *s)
18128 {
18129   char *ret;
18130
18131   if ((ret = (char *) malloc(strlen(s) + 1)))
18132     {
18133       safeStrCpy(ret, s, strlen(s)+1);
18134     }
18135   return ret;
18136 }
18137
18138 char *
18139 StrSavePtr (char *s, char **savePtr)
18140 {
18141     if (*savePtr) {
18142         free(*savePtr);
18143     }
18144     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18145       safeStrCpy(*savePtr, s, strlen(s)+1);
18146     }
18147     return(*savePtr);
18148 }
18149
18150 char *
18151 PGNDate ()
18152 {
18153     time_t clock;
18154     struct tm *tm;
18155     char buf[MSG_SIZ];
18156
18157     clock = time((time_t *)NULL);
18158     tm = localtime(&clock);
18159     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18160             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18161     return StrSave(buf);
18162 }
18163
18164
18165 char *
18166 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18167 {
18168     int i, j, fromX, fromY, toX, toY;
18169     int whiteToPlay, haveRights = nrCastlingRights;
18170     char buf[MSG_SIZ];
18171     char *p, *q;
18172     int emptycount;
18173     ChessSquare piece;
18174
18175     whiteToPlay = (gameMode == EditPosition) ?
18176       !blackPlaysFirst : (move % 2 == 0);
18177     p = buf;
18178
18179     /* Piece placement data */
18180     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18181         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18182         emptycount = 0;
18183         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18184             if (boards[move][i][j] == EmptySquare) {
18185                 emptycount++;
18186             } else { ChessSquare piece = boards[move][i][j];
18187                 if (emptycount > 0) {
18188                     if(emptycount<10) /* [HGM] can be >= 10 */
18189                         *p++ = '0' + emptycount;
18190                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18191                     emptycount = 0;
18192                 }
18193                 if(PieceToChar(piece) == '+') {
18194                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18195                     *p++ = '+';
18196                     piece = (ChessSquare)(CHUDEMOTED(piece));
18197                 }
18198                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18199                 if(*p = PieceSuffix(piece)) p++;
18200                 if(p[-1] == '~') {
18201                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18202                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18203                     *p++ = '~';
18204                 }
18205             }
18206         }
18207         if (emptycount > 0) {
18208             if(emptycount<10) /* [HGM] can be >= 10 */
18209                 *p++ = '0' + emptycount;
18210             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18211             emptycount = 0;
18212         }
18213         *p++ = '/';
18214     }
18215     *(p - 1) = ' ';
18216
18217     /* [HGM] print Crazyhouse or Shogi holdings */
18218     if( gameInfo.holdingsWidth ) {
18219         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18220         q = p;
18221         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18222             piece = boards[move][i][BOARD_WIDTH-1];
18223             if( piece != EmptySquare )
18224               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18225                   *p++ = PieceToChar(piece);
18226         }
18227         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18228             piece = boards[move][BOARD_HEIGHT-i-1][0];
18229             if( piece != EmptySquare )
18230               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18231                   *p++ = PieceToChar(piece);
18232         }
18233
18234         if( q == p ) *p++ = '-';
18235         *p++ = ']';
18236         *p++ = ' ';
18237     }
18238
18239     /* Active color */
18240     *p++ = whiteToPlay ? 'w' : 'b';
18241     *p++ = ' ';
18242
18243   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18244     haveRights = 0; q = p;
18245     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18246       piece = boards[move][0][i];
18247       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18248         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18249       }
18250     }
18251     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18252       piece = boards[move][BOARD_HEIGHT-1][i];
18253       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18254         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18255       }
18256     }
18257     if(p == q) *p++ = '-';
18258     *p++ = ' ';
18259   }
18260
18261   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18262     while(*p++ = *q++)
18263                       ;
18264     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18265   } else {
18266   if(haveRights) {
18267      int handW=0, handB=0;
18268      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18269         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18270         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18271      }
18272      q = p;
18273      if(appData.fischerCastling) {
18274         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18275            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18276                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18277         } else {
18278        /* [HGM] write directly from rights */
18279            if(boards[move][CASTLING][2] != NoRights &&
18280               boards[move][CASTLING][0] != NoRights   )
18281                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18282            if(boards[move][CASTLING][2] != NoRights &&
18283               boards[move][CASTLING][1] != NoRights   )
18284                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18285         }
18286         if(handB) {
18287            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18288                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18289         } else {
18290            if(boards[move][CASTLING][5] != NoRights &&
18291               boards[move][CASTLING][3] != NoRights   )
18292                 *p++ = boards[move][CASTLING][3] + AAA;
18293            if(boards[move][CASTLING][5] != NoRights &&
18294               boards[move][CASTLING][4] != NoRights   )
18295                 *p++ = boards[move][CASTLING][4] + AAA;
18296         }
18297      } else {
18298
18299         /* [HGM] write true castling rights */
18300         if( nrCastlingRights == 6 ) {
18301             int q, k=0;
18302             if(boards[move][CASTLING][0] != NoRights &&
18303                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18304             q = (boards[move][CASTLING][1] != NoRights &&
18305                  boards[move][CASTLING][2] != NoRights  );
18306             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18307                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18308                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18309                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18310             }
18311             if(q) *p++ = 'Q';
18312             k = 0;
18313             if(boards[move][CASTLING][3] != NoRights &&
18314                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18315             q = (boards[move][CASTLING][4] != NoRights &&
18316                  boards[move][CASTLING][5] != NoRights  );
18317             if(handB) {
18318                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18319                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18320                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18321             }
18322             if(q) *p++ = 'q';
18323         }
18324      }
18325      if (q == p) *p++ = '-'; /* No castling rights */
18326      *p++ = ' ';
18327   }
18328
18329   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18330      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18331      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18332     /* En passant target square */
18333     if (move > backwardMostMove) {
18334         fromX = moveList[move - 1][0] - AAA;
18335         fromY = moveList[move - 1][1] - ONE;
18336         toX = moveList[move - 1][2] - AAA;
18337         toY = moveList[move - 1][3] - ONE;
18338         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18339             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18340             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18341             fromX == toX) {
18342             /* 2-square pawn move just happened */
18343             *p++ = toX + AAA;
18344             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18345         } else {
18346             *p++ = '-';
18347         }
18348     } else if(move == backwardMostMove) {
18349         // [HGM] perhaps we should always do it like this, and forget the above?
18350         if((signed char)boards[move][EP_STATUS] >= 0) {
18351             *p++ = boards[move][EP_STATUS] + AAA;
18352             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18353         } else {
18354             *p++ = '-';
18355         }
18356     } else {
18357         *p++ = '-';
18358     }
18359     *p++ = ' ';
18360   }
18361   }
18362
18363     if(moveCounts)
18364     {   int i = 0, j=move;
18365
18366         /* [HGM] find reversible plies */
18367         if (appData.debugMode) { int k;
18368             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18369             for(k=backwardMostMove; k<=forwardMostMove; k++)
18370                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18371
18372         }
18373
18374         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18375         if( j == backwardMostMove ) i += initialRulePlies;
18376         sprintf(p, "%d ", i);
18377         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18378
18379         /* Fullmove number */
18380         sprintf(p, "%d", (move / 2) + 1);
18381     } else *--p = NULLCHAR;
18382
18383     return StrSave(buf);
18384 }
18385
18386 Boolean
18387 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18388 {
18389     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18390     char *p, c;
18391     int emptycount, virgin[BOARD_FILES];
18392     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18393
18394     p = fen;
18395
18396     /* Piece placement data */
18397     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18398         j = 0;
18399         for (;;) {
18400             if (*p == '/' || *p == ' ' || *p == '[' ) {
18401                 if(j > w) w = j;
18402                 emptycount = gameInfo.boardWidth - j;
18403                 while (emptycount--)
18404                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18405                 if (*p == '/') p++;
18406                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18407                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18408                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18409                     }
18410                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18411                 }
18412                 break;
18413 #if(BOARD_FILES >= 10)*0
18414             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18415                 p++; emptycount=10;
18416                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18417                 while (emptycount--)
18418                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18419 #endif
18420             } else if (*p == '*') {
18421                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18422             } else if (isdigit(*p)) {
18423                 emptycount = *p++ - '0';
18424                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18425                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18426                 while (emptycount--)
18427                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18428             } else if (*p == '<') {
18429                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18430                 else if (i != 0 || !shuffle) return FALSE;
18431                 p++;
18432             } else if (shuffle && *p == '>') {
18433                 p++; // for now ignore closing shuffle range, and assume rank-end
18434             } else if (*p == '?') {
18435                 if (j >= gameInfo.boardWidth) return FALSE;
18436                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18437                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18438             } else if (*p == '+' || isalpha(*p)) {
18439                 char *q, *s = SUFFIXES;
18440                 if (j >= gameInfo.boardWidth) return FALSE;
18441                 if(*p=='+') {
18442                     char c = *++p;
18443                     if(q = strchr(s, p[1])) p++;
18444                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18445                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18446                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18447                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18448                 } else {
18449                     char c = *p++;
18450                     if(q = strchr(s, *p)) p++;
18451                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18452                 }
18453
18454                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18455                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18456                     piece = (ChessSquare) (PROMOTED(piece));
18457                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18458                     p++;
18459                 }
18460                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18461                 if(piece == king) wKingRank = i;
18462                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18463             } else {
18464                 return FALSE;
18465             }
18466         }
18467     }
18468     while (*p == '/' || *p == ' ') p++;
18469
18470     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18471
18472     /* [HGM] by default clear Crazyhouse holdings, if present */
18473     if(gameInfo.holdingsWidth) {
18474        for(i=0; i<BOARD_HEIGHT; i++) {
18475            board[i][0]             = EmptySquare; /* black holdings */
18476            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18477            board[i][1]             = (ChessSquare) 0; /* black counts */
18478            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18479        }
18480     }
18481
18482     /* [HGM] look for Crazyhouse holdings here */
18483     while(*p==' ') p++;
18484     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18485         int swap=0, wcnt=0, bcnt=0;
18486         if(*p == '[') p++;
18487         if(*p == '<') swap++, p++;
18488         if(*p == '-' ) p++; /* empty holdings */ else {
18489             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18490             /* if we would allow FEN reading to set board size, we would   */
18491             /* have to add holdings and shift the board read so far here   */
18492             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18493                 p++;
18494                 if((int) piece >= (int) BlackPawn ) {
18495                     i = (int)piece - (int)BlackPawn;
18496                     i = PieceToNumber((ChessSquare)i);
18497                     if( i >= gameInfo.holdingsSize ) return FALSE;
18498                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18499                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18500                     bcnt++;
18501                 } else {
18502                     i = (int)piece - (int)WhitePawn;
18503                     i = PieceToNumber((ChessSquare)i);
18504                     if( i >= gameInfo.holdingsSize ) return FALSE;
18505                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18506                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18507                     wcnt++;
18508                 }
18509             }
18510             if(subst) { // substitute back-rank question marks by holdings pieces
18511                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18512                     int k, m, n = bcnt + 1;
18513                     if(board[0][j] == ClearBoard) {
18514                         if(!wcnt) return FALSE;
18515                         n = rand() % wcnt;
18516                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18517                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18518                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18519                             break;
18520                         }
18521                     }
18522                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18523                         if(!bcnt) return FALSE;
18524                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18525                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18526                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18527                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18528                             break;
18529                         }
18530                     }
18531                 }
18532                 subst = 0;
18533             }
18534         }
18535         if(*p == ']') p++;
18536     }
18537
18538     if(subst) return FALSE; // substitution requested, but no holdings
18539
18540     while(*p == ' ') p++;
18541
18542     /* Active color */
18543     c = *p++;
18544     if(appData.colorNickNames) {
18545       if( c == appData.colorNickNames[0] ) c = 'w'; else
18546       if( c == appData.colorNickNames[1] ) c = 'b';
18547     }
18548     switch (c) {
18549       case 'w':
18550         *blackPlaysFirst = FALSE;
18551         break;
18552       case 'b':
18553         *blackPlaysFirst = TRUE;
18554         break;
18555       default:
18556         return FALSE;
18557     }
18558
18559     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18560     /* return the extra info in global variiables             */
18561
18562     while(*p==' ') p++;
18563
18564     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18565         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18566         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18567     }
18568
18569     /* set defaults in case FEN is incomplete */
18570     board[EP_STATUS] = EP_UNKNOWN;
18571     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18572     for(i=0; i<nrCastlingRights; i++ ) {
18573         board[CASTLING][i] =
18574             appData.fischerCastling ? NoRights : initialRights[i];
18575     }   /* assume possible unless obviously impossible */
18576     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18577     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18578     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18579                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18580     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18581     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18582     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18583                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18584     FENrulePlies = 0;
18585
18586     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18587       char *q = p;
18588       int w=0, b=0;
18589       while(isalpha(*p)) {
18590         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18591         if(islower(*p)) b |= 1 << (*p++ - 'a');
18592       }
18593       if(*p == '-') p++;
18594       if(p != q) {
18595         board[TOUCHED_W] = ~w;
18596         board[TOUCHED_B] = ~b;
18597         while(*p == ' ') p++;
18598       }
18599     } else
18600
18601     if(nrCastlingRights) {
18602       int fischer = 0;
18603       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18604       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18605           /* castling indicator present, so default becomes no castlings */
18606           for(i=0; i<nrCastlingRights; i++ ) {
18607                  board[CASTLING][i] = NoRights;
18608           }
18609       }
18610       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18611              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18612              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18613              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18614         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18615
18616         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18617             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18618             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18619         }
18620         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18621             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18622         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18623                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18624         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18625                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18626         switch(c) {
18627           case'K':
18628               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18629               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18630               board[CASTLING][2] = whiteKingFile;
18631               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18632               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18633               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18634               break;
18635           case'Q':
18636               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18637               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18638               board[CASTLING][2] = whiteKingFile;
18639               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18640               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18641               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18642               break;
18643           case'k':
18644               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18645               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18646               board[CASTLING][5] = blackKingFile;
18647               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18648               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18649               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18650               break;
18651           case'q':
18652               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18653               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18654               board[CASTLING][5] = blackKingFile;
18655               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18656               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18657               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18658           case '-':
18659               break;
18660           default: /* FRC castlings */
18661               if(c >= 'a') { /* black rights */
18662                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18663                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18664                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18665                   if(i == BOARD_RGHT) break;
18666                   board[CASTLING][5] = i;
18667                   c -= AAA;
18668                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18669                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18670                   if(c > i)
18671                       board[CASTLING][3] = c;
18672                   else
18673                       board[CASTLING][4] = c;
18674               } else { /* white rights */
18675                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18676                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18677                     if(board[0][i] == WhiteKing) break;
18678                   if(i == BOARD_RGHT) break;
18679                   board[CASTLING][2] = i;
18680                   c -= AAA - 'a' + 'A';
18681                   if(board[0][c] >= WhiteKing) break;
18682                   if(c > i)
18683                       board[CASTLING][0] = c;
18684                   else
18685                       board[CASTLING][1] = c;
18686               }
18687         }
18688       }
18689       for(i=0; i<nrCastlingRights; i++)
18690         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18691       if(gameInfo.variant == VariantSChess)
18692         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18693       if(fischer && shuffle) appData.fischerCastling = TRUE;
18694     if (appData.debugMode) {
18695         fprintf(debugFP, "FEN castling rights:");
18696         for(i=0; i<nrCastlingRights; i++)
18697         fprintf(debugFP, " %d", board[CASTLING][i]);
18698         fprintf(debugFP, "\n");
18699     }
18700
18701       while(*p==' ') p++;
18702     }
18703
18704     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18705
18706     /* read e.p. field in games that know e.p. capture */
18707     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18708        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18709        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18710       if(*p=='-') {
18711         p++; board[EP_STATUS] = EP_NONE;
18712       } else {
18713          char c = *p++ - AAA;
18714
18715          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18716          if(*p >= '0' && *p <='9') p++;
18717          board[EP_STATUS] = c;
18718       }
18719     }
18720
18721
18722     if(sscanf(p, "%d", &i) == 1) {
18723         FENrulePlies = i; /* 50-move ply counter */
18724         /* (The move number is still ignored)    */
18725     }
18726
18727     return TRUE;
18728 }
18729
18730 void
18731 EditPositionPasteFEN (char *fen)
18732 {
18733   if (fen != NULL) {
18734     Board initial_position;
18735
18736     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18737       DisplayError(_("Bad FEN position in clipboard"), 0);
18738       return ;
18739     } else {
18740       int savedBlackPlaysFirst = blackPlaysFirst;
18741       EditPositionEvent();
18742       blackPlaysFirst = savedBlackPlaysFirst;
18743       CopyBoard(boards[0], initial_position);
18744       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18745       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18746       DisplayBothClocks();
18747       DrawPosition(FALSE, boards[currentMove]);
18748     }
18749   }
18750 }
18751
18752 static char cseq[12] = "\\   ";
18753
18754 Boolean
18755 set_cont_sequence (char *new_seq)
18756 {
18757     int len;
18758     Boolean ret;
18759
18760     // handle bad attempts to set the sequence
18761         if (!new_seq)
18762                 return 0; // acceptable error - no debug
18763
18764     len = strlen(new_seq);
18765     ret = (len > 0) && (len < sizeof(cseq));
18766     if (ret)
18767       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18768     else if (appData.debugMode)
18769       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18770     return ret;
18771 }
18772
18773 /*
18774     reformat a source message so words don't cross the width boundary.  internal
18775     newlines are not removed.  returns the wrapped size (no null character unless
18776     included in source message).  If dest is NULL, only calculate the size required
18777     for the dest buffer.  lp argument indicats line position upon entry, and it's
18778     passed back upon exit.
18779 */
18780 int
18781 wrap (char *dest, char *src, int count, int width, int *lp)
18782 {
18783     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18784
18785     cseq_len = strlen(cseq);
18786     old_line = line = *lp;
18787     ansi = len = clen = 0;
18788
18789     for (i=0; i < count; i++)
18790     {
18791         if (src[i] == '\033')
18792             ansi = 1;
18793
18794         // if we hit the width, back up
18795         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18796         {
18797             // store i & len in case the word is too long
18798             old_i = i, old_len = len;
18799
18800             // find the end of the last word
18801             while (i && src[i] != ' ' && src[i] != '\n')
18802             {
18803                 i--;
18804                 len--;
18805             }
18806
18807             // word too long?  restore i & len before splitting it
18808             if ((old_i-i+clen) >= width)
18809             {
18810                 i = old_i;
18811                 len = old_len;
18812             }
18813
18814             // extra space?
18815             if (i && src[i-1] == ' ')
18816                 len--;
18817
18818             if (src[i] != ' ' && src[i] != '\n')
18819             {
18820                 i--;
18821                 if (len)
18822                     len--;
18823             }
18824
18825             // now append the newline and continuation sequence
18826             if (dest)
18827                 dest[len] = '\n';
18828             len++;
18829             if (dest)
18830                 strncpy(dest+len, cseq, cseq_len);
18831             len += cseq_len;
18832             line = cseq_len;
18833             clen = cseq_len;
18834             continue;
18835         }
18836
18837         if (dest)
18838             dest[len] = src[i];
18839         len++;
18840         if (!ansi)
18841             line++;
18842         if (src[i] == '\n')
18843             line = 0;
18844         if (src[i] == 'm')
18845             ansi = 0;
18846     }
18847     if (dest && appData.debugMode)
18848     {
18849         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18850             count, width, line, len, *lp);
18851         show_bytes(debugFP, src, count);
18852         fprintf(debugFP, "\ndest: ");
18853         show_bytes(debugFP, dest, len);
18854         fprintf(debugFP, "\n");
18855     }
18856     *lp = dest ? line : old_line;
18857
18858     return len;
18859 }
18860
18861 // [HGM] vari: routines for shelving variations
18862 Boolean modeRestore = FALSE;
18863
18864 void
18865 PushInner (int firstMove, int lastMove)
18866 {
18867         int i, j, nrMoves = lastMove - firstMove;
18868
18869         // push current tail of game on stack
18870         savedResult[storedGames] = gameInfo.result;
18871         savedDetails[storedGames] = gameInfo.resultDetails;
18872         gameInfo.resultDetails = NULL;
18873         savedFirst[storedGames] = firstMove;
18874         savedLast [storedGames] = lastMove;
18875         savedFramePtr[storedGames] = framePtr;
18876         framePtr -= nrMoves; // reserve space for the boards
18877         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18878             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18879             for(j=0; j<MOVE_LEN; j++)
18880                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18881             for(j=0; j<2*MOVE_LEN; j++)
18882                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18883             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18884             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18885             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18886             pvInfoList[firstMove+i-1].depth = 0;
18887             commentList[framePtr+i] = commentList[firstMove+i];
18888             commentList[firstMove+i] = NULL;
18889         }
18890
18891         storedGames++;
18892         forwardMostMove = firstMove; // truncate game so we can start variation
18893 }
18894
18895 void
18896 PushTail (int firstMove, int lastMove)
18897 {
18898         if(appData.icsActive) { // only in local mode
18899                 forwardMostMove = currentMove; // mimic old ICS behavior
18900                 return;
18901         }
18902         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18903
18904         PushInner(firstMove, lastMove);
18905         if(storedGames == 1) GreyRevert(FALSE);
18906         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18907 }
18908
18909 void
18910 PopInner (Boolean annotate)
18911 {
18912         int i, j, nrMoves;
18913         char buf[8000], moveBuf[20];
18914
18915         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18916         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18917         nrMoves = savedLast[storedGames] - currentMove;
18918         if(annotate) {
18919                 int cnt = 10;
18920                 if(!WhiteOnMove(currentMove))
18921                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18922                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18923                 for(i=currentMove; i<forwardMostMove; i++) {
18924                         if(WhiteOnMove(i))
18925                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18926                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18927                         strcat(buf, moveBuf);
18928                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18929                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18930                 }
18931                 strcat(buf, ")");
18932         }
18933         for(i=1; i<=nrMoves; i++) { // copy last variation back
18934             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18935             for(j=0; j<MOVE_LEN; j++)
18936                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18937             for(j=0; j<2*MOVE_LEN; j++)
18938                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18939             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18940             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18941             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18942             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18943             commentList[currentMove+i] = commentList[framePtr+i];
18944             commentList[framePtr+i] = NULL;
18945         }
18946         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18947         framePtr = savedFramePtr[storedGames];
18948         gameInfo.result = savedResult[storedGames];
18949         if(gameInfo.resultDetails != NULL) {
18950             free(gameInfo.resultDetails);
18951       }
18952         gameInfo.resultDetails = savedDetails[storedGames];
18953         forwardMostMove = currentMove + nrMoves;
18954 }
18955
18956 Boolean
18957 PopTail (Boolean annotate)
18958 {
18959         if(appData.icsActive) return FALSE; // only in local mode
18960         if(!storedGames) return FALSE; // sanity
18961         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18962
18963         PopInner(annotate);
18964         if(currentMove < forwardMostMove) ForwardEvent(); else
18965         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18966
18967         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18968         return TRUE;
18969 }
18970
18971 void
18972 CleanupTail ()
18973 {       // remove all shelved variations
18974         int i;
18975         for(i=0; i<storedGames; i++) {
18976             if(savedDetails[i])
18977                 free(savedDetails[i]);
18978             savedDetails[i] = NULL;
18979         }
18980         for(i=framePtr; i<MAX_MOVES; i++) {
18981                 if(commentList[i]) free(commentList[i]);
18982                 commentList[i] = NULL;
18983         }
18984         framePtr = MAX_MOVES-1;
18985         storedGames = 0;
18986 }
18987
18988 void
18989 LoadVariation (int index, char *text)
18990 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18991         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18992         int level = 0, move;
18993
18994         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18995         // first find outermost bracketing variation
18996         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18997             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18998                 if(*p == '{') wait = '}'; else
18999                 if(*p == '[') wait = ']'; else
19000                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19001                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19002             }
19003             if(*p == wait) wait = NULLCHAR; // closing ]} found
19004             p++;
19005         }
19006         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19007         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19008         end[1] = NULLCHAR; // clip off comment beyond variation
19009         ToNrEvent(currentMove-1);
19010         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19011         // kludge: use ParsePV() to append variation to game
19012         move = currentMove;
19013         ParsePV(start, TRUE, TRUE);
19014         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19015         ClearPremoveHighlights();
19016         CommentPopDown();
19017         ToNrEvent(currentMove+1);
19018 }
19019
19020 void
19021 LoadTheme ()
19022 {
19023     char *p, *q, buf[MSG_SIZ];
19024     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19025         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19026         ParseArgsFromString(buf);
19027         ActivateTheme(TRUE); // also redo colors
19028         return;
19029     }
19030     p = nickName;
19031     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19032     {
19033         int len;
19034         q = appData.themeNames;
19035         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19036       if(appData.useBitmaps) {
19037         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19038                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19039                 appData.liteBackTextureMode,
19040                 appData.darkBackTextureMode );
19041       } else {
19042         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19043                 Col2Text(2),   // lightSquareColor
19044                 Col2Text(3) ); // darkSquareColor
19045       }
19046       if(appData.useBorder) {
19047         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19048                 appData.border);
19049       } else {
19050         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19051       }
19052       if(appData.useFont) {
19053         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19054                 appData.renderPiecesWithFont,
19055                 appData.fontToPieceTable,
19056                 Col2Text(9),    // appData.fontBackColorWhite
19057                 Col2Text(10) ); // appData.fontForeColorBlack
19058       } else {
19059         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19060                 appData.pieceDirectory);
19061         if(!appData.pieceDirectory[0])
19062           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19063                 Col2Text(0),   // whitePieceColor
19064                 Col2Text(1) ); // blackPieceColor
19065       }
19066       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19067                 Col2Text(4),   // highlightSquareColor
19068                 Col2Text(5) ); // premoveHighlightColor
19069         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19070         if(insert != q) insert[-1] = NULLCHAR;
19071         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19072         if(q)   free(q);
19073     }
19074     ActivateTheme(FALSE);
19075 }