Add -fen option
[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];
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         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         } else if(*appData.fen != NULLCHAR) {
1747             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1748                 startedFromPositionFile = TRUE;
1749                 Reset(TRUE, TRUE);
1750             }
1751         }
1752         if (initialMode == AnalyzeMode) {
1753           if (appData.noChessProgram) {
1754             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1755             return;
1756           }
1757           if (appData.icsActive) {
1758             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1759             return;
1760           }
1761           AnalyzeModeEvent();
1762         } else if (initialMode == AnalyzeFile) {
1763           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1764           ShowThinkingEvent();
1765           AnalyzeFileEvent();
1766           AnalysisPeriodicEvent(1);
1767         } else if (initialMode == MachinePlaysWhite) {
1768           if (appData.noChessProgram) {
1769             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1770                               0, 2);
1771             return;
1772           }
1773           if (appData.icsActive) {
1774             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1775                               0, 2);
1776             return;
1777           }
1778           MachineWhiteEvent();
1779         } else if (initialMode == MachinePlaysBlack) {
1780           if (appData.noChessProgram) {
1781             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1782                               0, 2);
1783             return;
1784           }
1785           if (appData.icsActive) {
1786             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1787                               0, 2);
1788             return;
1789           }
1790           MachineBlackEvent();
1791         } else if (initialMode == TwoMachinesPlay) {
1792           if (appData.noChessProgram) {
1793             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1794                               0, 2);
1795             return;
1796           }
1797           if (appData.icsActive) {
1798             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1799                               0, 2);
1800             return;
1801           }
1802           TwoMachinesEvent();
1803         } else if (initialMode == EditGame) {
1804           EditGameEvent();
1805         } else if (initialMode == EditPosition) {
1806           EditPositionEvent();
1807         } else if (initialMode == Training) {
1808           if (*appData.loadGameFile == NULLCHAR) {
1809             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1810             return;
1811           }
1812           TrainingEvent();
1813         }
1814     }
1815 }
1816
1817 void
1818 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1819 {
1820     DisplayBook(current+1);
1821
1822     MoveHistorySet( movelist, first, last, current, pvInfoList );
1823
1824     EvalGraphSet( first, last, current, pvInfoList );
1825
1826     MakeEngineOutputTitle();
1827 }
1828
1829 /*
1830  * Establish will establish a contact to a remote host.port.
1831  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1832  *  used to talk to the host.
1833  * Returns 0 if okay, error code if not.
1834  */
1835 int
1836 establish ()
1837 {
1838     char buf[MSG_SIZ];
1839
1840     if (*appData.icsCommPort != NULLCHAR) {
1841         /* Talk to the host through a serial comm port */
1842         return OpenCommPort(appData.icsCommPort, &icsPR);
1843
1844     } else if (*appData.gateway != NULLCHAR) {
1845         if (*appData.remoteShell == NULLCHAR) {
1846             /* Use the rcmd protocol to run telnet program on a gateway host */
1847             snprintf(buf, sizeof(buf), "%s %s %s",
1848                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1849             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1850
1851         } else {
1852             /* Use the rsh program to run telnet program on a gateway host */
1853             if (*appData.remoteUser == NULLCHAR) {
1854                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1855                         appData.gateway, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             } else {
1858                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1859                         appData.remoteShell, appData.gateway,
1860                         appData.remoteUser, appData.telnetProgram,
1861                         appData.icsHost, appData.icsPort);
1862             }
1863             return StartChildProcess(buf, "", &icsPR);
1864
1865         }
1866     } else if (appData.useTelnet) {
1867         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1868
1869     } else {
1870         /* TCP socket interface differs somewhat between
1871            Unix and NT; handle details in the front end.
1872            */
1873         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1874     }
1875 }
1876
1877 void
1878 EscapeExpand (char *p, char *q)
1879 {       // [HGM] initstring: routine to shape up string arguments
1880         while(*p++ = *q++) if(p[-1] == '\\')
1881             switch(*q++) {
1882                 case 'n': p[-1] = '\n'; break;
1883                 case 'r': p[-1] = '\r'; break;
1884                 case 't': p[-1] = '\t'; break;
1885                 case '\\': p[-1] = '\\'; break;
1886                 case 0: *p = 0; return;
1887                 default: p[-1] = q[-1]; break;
1888             }
1889 }
1890
1891 void
1892 show_bytes (FILE *fp, char *buf, int count)
1893 {
1894     while (count--) {
1895         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1896             fprintf(fp, "\\%03o", *buf & 0xff);
1897         } else {
1898             putc(*buf, fp);
1899         }
1900         buf++;
1901     }
1902     fflush(fp);
1903 }
1904
1905 /* Returns an errno value */
1906 int
1907 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1908 {
1909     char buf[8192], *p, *q, *buflim;
1910     int left, newcount, outcount;
1911
1912     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1913         *appData.gateway != NULLCHAR) {
1914         if (appData.debugMode) {
1915             fprintf(debugFP, ">ICS: ");
1916             show_bytes(debugFP, message, count);
1917             fprintf(debugFP, "\n");
1918         }
1919         return OutputToProcess(pr, message, count, outError);
1920     }
1921
1922     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1923     p = message;
1924     q = buf;
1925     left = count;
1926     newcount = 0;
1927     while (left) {
1928         if (q >= buflim) {
1929             if (appData.debugMode) {
1930                 fprintf(debugFP, ">ICS: ");
1931                 show_bytes(debugFP, buf, newcount);
1932                 fprintf(debugFP, "\n");
1933             }
1934             outcount = OutputToProcess(pr, buf, newcount, outError);
1935             if (outcount < newcount) return -1; /* to be sure */
1936             q = buf;
1937             newcount = 0;
1938         }
1939         if (*p == '\n') {
1940             *q++ = '\r';
1941             newcount++;
1942         } else if (((unsigned char) *p) == TN_IAC) {
1943             *q++ = (char) TN_IAC;
1944             newcount ++;
1945         }
1946         *q++ = *p++;
1947         newcount++;
1948         left--;
1949     }
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, buf, newcount);
1953         fprintf(debugFP, "\n");
1954     }
1955     outcount = OutputToProcess(pr, buf, newcount, outError);
1956     if (outcount < newcount) return -1; /* to be sure */
1957     return count;
1958 }
1959
1960 void
1961 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1962 {
1963     int outError, outCount;
1964     static int gotEof = 0;
1965     static FILE *ini;
1966
1967     /* Pass data read from player on to ICS */
1968     if (count > 0) {
1969         gotEof = 0;
1970         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1971         if (outCount < count) {
1972             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1973         }
1974         if(have_sent_ICS_logon == 2) {
1975           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1976             fprintf(ini, "%s", message);
1977             have_sent_ICS_logon = 3;
1978           } else
1979             have_sent_ICS_logon = 1;
1980         } else if(have_sent_ICS_logon == 3) {
1981             fprintf(ini, "%s", message);
1982             fclose(ini);
1983           have_sent_ICS_logon = 1;
1984         }
1985     } else if (count < 0) {
1986         RemoveInputSource(isr);
1987         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1988     } else if (gotEof++ > 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1991     }
1992 }
1993
1994 void
1995 KeepAlive ()
1996 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1997     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1998     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1999     SendToICS("date\n");
2000     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2001 }
2002
2003 /* added routine for printf style output to ics */
2004 void
2005 ics_printf (char *format, ...)
2006 {
2007     char buffer[MSG_SIZ];
2008     va_list args;
2009
2010     va_start(args, format);
2011     vsnprintf(buffer, sizeof(buffer), format, args);
2012     buffer[sizeof(buffer)-1] = '\0';
2013     SendToICS(buffer);
2014     va_end(args);
2015 }
2016
2017 void
2018 SendToICS (char *s)
2019 {
2020     int count, outCount, outError;
2021
2022     if (icsPR == NoProc) return;
2023
2024     count = strlen(s);
2025     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2026     if (outCount < count) {
2027         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2028     }
2029 }
2030
2031 /* This is used for sending logon scripts to the ICS. Sending
2032    without a delay causes problems when using timestamp on ICC
2033    (at least on my machine). */
2034 void
2035 SendToICSDelayed (char *s, long msdelay)
2036 {
2037     int count, outCount, outError;
2038
2039     if (icsPR == NoProc) return;
2040
2041     count = strlen(s);
2042     if (appData.debugMode) {
2043         fprintf(debugFP, ">ICS: ");
2044         show_bytes(debugFP, s, count);
2045         fprintf(debugFP, "\n");
2046     }
2047     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2048                                       msdelay);
2049     if (outCount < count) {
2050         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2051     }
2052 }
2053
2054
2055 /* Remove all highlighting escape sequences in s
2056    Also deletes any suffix starting with '('
2057    */
2058 char *
2059 StripHighlightAndTitle (char *s)
2060 {
2061     static char retbuf[MSG_SIZ];
2062     char *p = retbuf;
2063
2064     while (*s != NULLCHAR) {
2065         while (*s == '\033') {
2066             while (*s != NULLCHAR && !isalpha(*s)) s++;
2067             if (*s != NULLCHAR) s++;
2068         }
2069         while (*s != NULLCHAR && *s != '\033') {
2070             if (*s == '(' || *s == '[') {
2071                 *p = NULLCHAR;
2072                 return retbuf;
2073             }
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 /* Remove all highlighting escape sequences in s */
2082 char *
2083 StripHighlight (char *s)
2084 {
2085     static char retbuf[MSG_SIZ];
2086     char *p = retbuf;
2087
2088     while (*s != NULLCHAR) {
2089         while (*s == '\033') {
2090             while (*s != NULLCHAR && !isalpha(*s)) s++;
2091             if (*s != NULLCHAR) s++;
2092         }
2093         while (*s != NULLCHAR && *s != '\033') {
2094             *p++ = *s++;
2095         }
2096     }
2097     *p = NULLCHAR;
2098     return retbuf;
2099 }
2100
2101 char engineVariant[MSG_SIZ];
2102 char *variantNames[] = VARIANT_NAMES;
2103 char *
2104 VariantName (VariantClass v)
2105 {
2106     if(v == VariantUnknown || *engineVariant) return engineVariant;
2107     return variantNames[v];
2108 }
2109
2110
2111 /* Identify a variant from the strings the chess servers use or the
2112    PGN Variant tag names we use. */
2113 VariantClass
2114 StringToVariant (char *e)
2115 {
2116     char *p;
2117     int wnum = -1;
2118     VariantClass v = VariantNormal;
2119     int i, found = FALSE;
2120     char buf[MSG_SIZ], c;
2121     int len;
2122
2123     if (!e) return v;
2124
2125     /* [HGM] skip over optional board-size prefixes */
2126     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2127         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2128         while( *e++ != '_');
2129     }
2130
2131     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2132         v = VariantNormal;
2133         found = TRUE;
2134     } else
2135     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2136       if (p = StrCaseStr(e, variantNames[i])) {
2137         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2138         v = (VariantClass) i;
2139         found = TRUE;
2140         break;
2141       }
2142     }
2143
2144     if (!found) {
2145       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2146           || StrCaseStr(e, "wild/fr")
2147           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2148         v = VariantFischeRandom;
2149       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2150                  (i = 1, p = StrCaseStr(e, "w"))) {
2151         p += i;
2152         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2153         if (isdigit(*p)) {
2154           wnum = atoi(p);
2155         } else {
2156           wnum = -1;
2157         }
2158         switch (wnum) {
2159         case 0: /* FICS only, actually */
2160         case 1:
2161           /* Castling legal even if K starts on d-file */
2162           v = VariantWildCastle;
2163           break;
2164         case 2:
2165         case 3:
2166         case 4:
2167           /* Castling illegal even if K & R happen to start in
2168              normal positions. */
2169           v = VariantNoCastle;
2170           break;
2171         case 5:
2172         case 7:
2173         case 8:
2174         case 10:
2175         case 11:
2176         case 12:
2177         case 13:
2178         case 14:
2179         case 15:
2180         case 18:
2181         case 19:
2182           /* Castling legal iff K & R start in normal positions */
2183           v = VariantNormal;
2184           break;
2185         case 6:
2186         case 20:
2187         case 21:
2188           /* Special wilds for position setup; unclear what to do here */
2189           v = VariantLoadable;
2190           break;
2191         case 9:
2192           /* Bizarre ICC game */
2193           v = VariantTwoKings;
2194           break;
2195         case 16:
2196           v = VariantKriegspiel;
2197           break;
2198         case 17:
2199           v = VariantLosers;
2200           break;
2201         case 22:
2202           v = VariantFischeRandom;
2203           break;
2204         case 23:
2205           v = VariantCrazyhouse;
2206           break;
2207         case 24:
2208           v = VariantBughouse;
2209           break;
2210         case 25:
2211           v = Variant3Check;
2212           break;
2213         case 26:
2214           /* Not quite the same as FICS suicide! */
2215           v = VariantGiveaway;
2216           break;
2217         case 27:
2218           v = VariantAtomic;
2219           break;
2220         case 28:
2221           v = VariantShatranj;
2222           break;
2223
2224         /* Temporary names for future ICC types.  The name *will* change in
2225            the next xboard/WinBoard release after ICC defines it. */
2226         case 29:
2227           v = Variant29;
2228           break;
2229         case 30:
2230           v = Variant30;
2231           break;
2232         case 31:
2233           v = Variant31;
2234           break;
2235         case 32:
2236           v = Variant32;
2237           break;
2238         case 33:
2239           v = Variant33;
2240           break;
2241         case 34:
2242           v = Variant34;
2243           break;
2244         case 35:
2245           v = Variant35;
2246           break;
2247         case 36:
2248           v = Variant36;
2249           break;
2250         case 37:
2251           v = VariantShogi;
2252           break;
2253         case 38:
2254           v = VariantXiangqi;
2255           break;
2256         case 39:
2257           v = VariantCourier;
2258           break;
2259         case 40:
2260           v = VariantGothic;
2261           break;
2262         case 41:
2263           v = VariantCapablanca;
2264           break;
2265         case 42:
2266           v = VariantKnightmate;
2267           break;
2268         case 43:
2269           v = VariantFairy;
2270           break;
2271         case 44:
2272           v = VariantCylinder;
2273           break;
2274         case 45:
2275           v = VariantFalcon;
2276           break;
2277         case 46:
2278           v = VariantCapaRandom;
2279           break;
2280         case 47:
2281           v = VariantBerolina;
2282           break;
2283         case 48:
2284           v = VariantJanus;
2285           break;
2286         case 49:
2287           v = VariantSuper;
2288           break;
2289         case 50:
2290           v = VariantGreat;
2291           break;
2292         case -1:
2293           /* Found "wild" or "w" in the string but no number;
2294              must assume it's normal chess. */
2295           v = VariantNormal;
2296           break;
2297         default:
2298           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2299           if( (len >= MSG_SIZ) && appData.debugMode )
2300             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2301
2302           DisplayError(buf, 0);
2303           v = VariantUnknown;
2304           break;
2305         }
2306       }
2307     }
2308     if (appData.debugMode) {
2309       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2310               e, wnum, VariantName(v));
2311     }
2312     return v;
2313 }
2314
2315 static int leftover_start = 0, leftover_len = 0;
2316 char star_match[STAR_MATCH_N][MSG_SIZ];
2317
2318 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2319    advance *index beyond it, and set leftover_start to the new value of
2320    *index; else return FALSE.  If pattern contains the character '*', it
2321    matches any sequence of characters not containing '\r', '\n', or the
2322    character following the '*' (if any), and the matched sequence(s) are
2323    copied into star_match.
2324    */
2325 int
2326 looking_at ( char *buf, int *index, char *pattern)
2327 {
2328     char *bufp = &buf[*index], *patternp = pattern;
2329     int star_count = 0;
2330     char *matchp = star_match[0];
2331
2332     for (;;) {
2333         if (*patternp == NULLCHAR) {
2334             *index = leftover_start = bufp - buf;
2335             *matchp = NULLCHAR;
2336             return TRUE;
2337         }
2338         if (*bufp == NULLCHAR) return FALSE;
2339         if (*patternp == '*') {
2340             if (*bufp == *(patternp + 1)) {
2341                 *matchp = NULLCHAR;
2342                 matchp = star_match[++star_count];
2343                 patternp += 2;
2344                 bufp++;
2345                 continue;
2346             } else if (*bufp == '\n' || *bufp == '\r') {
2347                 patternp++;
2348                 if (*patternp == NULLCHAR)
2349                   continue;
2350                 else
2351                   return FALSE;
2352             } else {
2353                 *matchp++ = *bufp++;
2354                 continue;
2355             }
2356         }
2357         if (*patternp != *bufp) return FALSE;
2358         patternp++;
2359         bufp++;
2360     }
2361 }
2362
2363 void
2364 SendToPlayer (char *data, int length)
2365 {
2366     int error, outCount;
2367     outCount = OutputToProcess(NoProc, data, length, &error);
2368     if (outCount < length) {
2369         DisplayFatalError(_("Error writing to display"), error, 1);
2370     }
2371 }
2372
2373 void
2374 PackHolding (char packed[], char *holding)
2375 {
2376     char *p = holding;
2377     char *q = packed;
2378     int runlength = 0;
2379     int curr = 9999;
2380     do {
2381         if (*p == curr) {
2382             runlength++;
2383         } else {
2384             switch (runlength) {
2385               case 0:
2386                 break;
2387               case 1:
2388                 *q++ = curr;
2389                 break;
2390               case 2:
2391                 *q++ = curr;
2392                 *q++ = curr;
2393                 break;
2394               default:
2395                 sprintf(q, "%d", runlength);
2396                 while (*q) q++;
2397                 *q++ = curr;
2398                 break;
2399             }
2400             runlength = 1;
2401             curr = *p;
2402         }
2403     } while (*p++);
2404     *q = NULLCHAR;
2405 }
2406
2407 /* Telnet protocol requests from the front end */
2408 void
2409 TelnetRequest (unsigned char ddww, unsigned char option)
2410 {
2411     unsigned char msg[3];
2412     int outCount, outError;
2413
2414     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2415
2416     if (appData.debugMode) {
2417         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2418         switch (ddww) {
2419           case TN_DO:
2420             ddwwStr = "DO";
2421             break;
2422           case TN_DONT:
2423             ddwwStr = "DONT";
2424             break;
2425           case TN_WILL:
2426             ddwwStr = "WILL";
2427             break;
2428           case TN_WONT:
2429             ddwwStr = "WONT";
2430             break;
2431           default:
2432             ddwwStr = buf1;
2433             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2434             break;
2435         }
2436         switch (option) {
2437           case TN_ECHO:
2438             optionStr = "ECHO";
2439             break;
2440           default:
2441             optionStr = buf2;
2442             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2443             break;
2444         }
2445         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2446     }
2447     msg[0] = TN_IAC;
2448     msg[1] = ddww;
2449     msg[2] = option;
2450     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2451     if (outCount < 3) {
2452         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2453     }
2454 }
2455
2456 void
2457 DoEcho ()
2458 {
2459     if (!appData.icsActive) return;
2460     TelnetRequest(TN_DO, TN_ECHO);
2461 }
2462
2463 void
2464 DontEcho ()
2465 {
2466     if (!appData.icsActive) return;
2467     TelnetRequest(TN_DONT, TN_ECHO);
2468 }
2469
2470 void
2471 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2472 {
2473     /* put the holdings sent to us by the server on the board holdings area */
2474     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2475     char p;
2476     ChessSquare piece;
2477
2478     if(gameInfo.holdingsWidth < 2)  return;
2479     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2480         return; // prevent overwriting by pre-board holdings
2481
2482     if( (int)lowestPiece >= BlackPawn ) {
2483         holdingsColumn = 0;
2484         countsColumn = 1;
2485         holdingsStartRow = BOARD_HEIGHT-1;
2486         direction = -1;
2487     } else {
2488         holdingsColumn = BOARD_WIDTH-1;
2489         countsColumn = BOARD_WIDTH-2;
2490         holdingsStartRow = 0;
2491         direction = 1;
2492     }
2493
2494     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2495         board[i][holdingsColumn] = EmptySquare;
2496         board[i][countsColumn]   = (ChessSquare) 0;
2497     }
2498     while( (p=*holdings++) != NULLCHAR ) {
2499         piece = CharToPiece( ToUpper(p) );
2500         if(piece == EmptySquare) continue;
2501         /*j = (int) piece - (int) WhitePawn;*/
2502         j = PieceToNumber(piece);
2503         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2504         if(j < 0) continue;               /* should not happen */
2505         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2506         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2507         board[holdingsStartRow+j*direction][countsColumn]++;
2508     }
2509 }
2510
2511
2512 void
2513 VariantSwitch (Board board, VariantClass newVariant)
2514 {
2515    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2516    static Board oldBoard;
2517
2518    startedFromPositionFile = FALSE;
2519    if(gameInfo.variant == newVariant) return;
2520
2521    /* [HGM] This routine is called each time an assignment is made to
2522     * gameInfo.variant during a game, to make sure the board sizes
2523     * are set to match the new variant. If that means adding or deleting
2524     * holdings, we shift the playing board accordingly
2525     * This kludge is needed because in ICS observe mode, we get boards
2526     * of an ongoing game without knowing the variant, and learn about the
2527     * latter only later. This can be because of the move list we requested,
2528     * in which case the game history is refilled from the beginning anyway,
2529     * but also when receiving holdings of a crazyhouse game. In the latter
2530     * case we want to add those holdings to the already received position.
2531     */
2532
2533
2534    if (appData.debugMode) {
2535      fprintf(debugFP, "Switch board from %s to %s\n",
2536              VariantName(gameInfo.variant), VariantName(newVariant));
2537      setbuf(debugFP, NULL);
2538    }
2539    shuffleOpenings = 0;       /* [HGM] shuffle */
2540    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2541    switch(newVariant)
2542      {
2543      case VariantShogi:
2544        newWidth = 9;  newHeight = 9;
2545        gameInfo.holdingsSize = 7;
2546      case VariantBughouse:
2547      case VariantCrazyhouse:
2548        newHoldingsWidth = 2; break;
2549      case VariantGreat:
2550        newWidth = 10;
2551      case VariantSuper:
2552        newHoldingsWidth = 2;
2553        gameInfo.holdingsSize = 8;
2554        break;
2555      case VariantGothic:
2556      case VariantCapablanca:
2557      case VariantCapaRandom:
2558        newWidth = 10;
2559      default:
2560        newHoldingsWidth = gameInfo.holdingsSize = 0;
2561      };
2562
2563    if(newWidth  != gameInfo.boardWidth  ||
2564       newHeight != gameInfo.boardHeight ||
2565       newHoldingsWidth != gameInfo.holdingsWidth ) {
2566
2567      /* shift position to new playing area, if needed */
2568      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573        for(i=0; i<newHeight; i++) {
2574          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2575          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2576        }
2577      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2578        for(i=0; i<BOARD_HEIGHT; i++)
2579          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2580            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2581              board[i][j];
2582      }
2583      board[HOLDINGS_SET] = 0;
2584      gameInfo.boardWidth  = newWidth;
2585      gameInfo.boardHeight = newHeight;
2586      gameInfo.holdingsWidth = newHoldingsWidth;
2587      gameInfo.variant = newVariant;
2588      InitDrawingSizes(-2, 0);
2589    } else gameInfo.variant = newVariant;
2590    CopyBoard(oldBoard, board);   // remember correctly formatted board
2591      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2592    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2593 }
2594
2595 static int loggedOn = FALSE;
2596
2597 /*-- Game start info cache: --*/
2598 int gs_gamenum;
2599 char gs_kind[MSG_SIZ];
2600 static char player1Name[128] = "";
2601 static char player2Name[128] = "";
2602 static char cont_seq[] = "\n\\   ";
2603 static int player1Rating = -1;
2604 static int player2Rating = -1;
2605 /*----------------------------*/
2606
2607 ColorClass curColor = ColorNormal;
2608 int suppressKibitz = 0;
2609
2610 // [HGM] seekgraph
2611 Boolean soughtPending = FALSE;
2612 Boolean seekGraphUp;
2613 #define MAX_SEEK_ADS 200
2614 #define SQUARE 0x80
2615 char *seekAdList[MAX_SEEK_ADS];
2616 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2617 float tcList[MAX_SEEK_ADS];
2618 char colorList[MAX_SEEK_ADS];
2619 int nrOfSeekAds = 0;
2620 int minRating = 1010, maxRating = 2800;
2621 int hMargin = 10, vMargin = 20, h, w;
2622 extern int squareSize, lineGap;
2623
2624 void
2625 PlotSeekAd (int i)
2626 {
2627         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2628         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2629         if(r < minRating+100 && r >=0 ) r = minRating+100;
2630         if(r > maxRating) r = maxRating;
2631         if(tc < 1.f) tc = 1.f;
2632         if(tc > 95.f) tc = 95.f;
2633         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2634         y = ((double)r - minRating)/(maxRating - minRating)
2635             * (h-vMargin-squareSize/8-1) + vMargin;
2636         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2637         if(strstr(seekAdList[i], " u ")) color = 1;
2638         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2639            !strstr(seekAdList[i], "bullet") &&
2640            !strstr(seekAdList[i], "blitz") &&
2641            !strstr(seekAdList[i], "standard") ) color = 2;
2642         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2643         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2644 }
2645
2646 void
2647 PlotSingleSeekAd (int i)
2648 {
2649         PlotSeekAd(i);
2650 }
2651
2652 void
2653 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2654 {
2655         char buf[MSG_SIZ], *ext = "";
2656         VariantClass v = StringToVariant(type);
2657         if(strstr(type, "wild")) {
2658             ext = type + 4; // append wild number
2659             if(v == VariantFischeRandom) type = "chess960"; else
2660             if(v == VariantLoadable) type = "setup"; else
2661             type = VariantName(v);
2662         }
2663         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2664         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2665             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2666             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2667             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2668             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2669             seekNrList[nrOfSeekAds] = nr;
2670             zList[nrOfSeekAds] = 0;
2671             seekAdList[nrOfSeekAds++] = StrSave(buf);
2672             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2673         }
2674 }
2675
2676 void
2677 EraseSeekDot (int i)
2678 {
2679     int x = xList[i], y = yList[i], d=squareSize/4, k;
2680     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2681     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2682     // now replot every dot that overlapped
2683     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2684         int xx = xList[k], yy = yList[k];
2685         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2686             DrawSeekDot(xx, yy, colorList[k]);
2687     }
2688 }
2689
2690 void
2691 RemoveSeekAd (int nr)
2692 {
2693         int i;
2694         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2695             EraseSeekDot(i);
2696             if(seekAdList[i]) free(seekAdList[i]);
2697             seekAdList[i] = seekAdList[--nrOfSeekAds];
2698             seekNrList[i] = seekNrList[nrOfSeekAds];
2699             ratingList[i] = ratingList[nrOfSeekAds];
2700             colorList[i]  = colorList[nrOfSeekAds];
2701             tcList[i] = tcList[nrOfSeekAds];
2702             xList[i]  = xList[nrOfSeekAds];
2703             yList[i]  = yList[nrOfSeekAds];
2704             zList[i]  = zList[nrOfSeekAds];
2705             seekAdList[nrOfSeekAds] = NULL;
2706             break;
2707         }
2708 }
2709
2710 Boolean
2711 MatchSoughtLine (char *line)
2712 {
2713     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2714     int nr, base, inc, u=0; char dummy;
2715
2716     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2717        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2718        (u=1) &&
2719        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2721         // match: compact and save the line
2722         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2723         return TRUE;
2724     }
2725     return FALSE;
2726 }
2727
2728 int
2729 DrawSeekGraph ()
2730 {
2731     int i;
2732     if(!seekGraphUp) return FALSE;
2733     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2734     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2735
2736     DrawSeekBackground(0, 0, w, h);
2737     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2738     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2739     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2740         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2741         yy = h-1-yy;
2742         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2743         if(i%500 == 0) {
2744             char buf[MSG_SIZ];
2745             snprintf(buf, MSG_SIZ, "%d", i);
2746             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2747         }
2748     }
2749     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2750     for(i=1; i<100; i+=(i<10?1:5)) {
2751         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2752         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2753         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2754             char buf[MSG_SIZ];
2755             snprintf(buf, MSG_SIZ, "%d", i);
2756             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2757         }
2758     }
2759     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2760     return TRUE;
2761 }
2762
2763 int
2764 SeekGraphClick (ClickType click, int x, int y, int moving)
2765 {
2766     static int lastDown = 0, displayed = 0, lastSecond;
2767     if(y < 0) return FALSE;
2768     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2769         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2770         if(!seekGraphUp) return FALSE;
2771         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2772         DrawPosition(TRUE, NULL);
2773         return TRUE;
2774     }
2775     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2776         if(click == Release || moving) return FALSE;
2777         nrOfSeekAds = 0;
2778         soughtPending = TRUE;
2779         SendToICS(ics_prefix);
2780         SendToICS("sought\n"); // should this be "sought all"?
2781     } else { // issue challenge based on clicked ad
2782         int dist = 10000; int i, closest = 0, second = 0;
2783         for(i=0; i<nrOfSeekAds; i++) {
2784             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2785             if(d < dist) { dist = d; closest = i; }
2786             second += (d - zList[i] < 120); // count in-range ads
2787             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2788         }
2789         if(dist < 120) {
2790             char buf[MSG_SIZ];
2791             second = (second > 1);
2792             if(displayed != closest || second != lastSecond) {
2793                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2794                 lastSecond = second; displayed = closest;
2795             }
2796             if(click == Press) {
2797                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2798                 lastDown = closest;
2799                 return TRUE;
2800             } // on press 'hit', only show info
2801             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2802             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2803             SendToICS(ics_prefix);
2804             SendToICS(buf);
2805             return TRUE; // let incoming board of started game pop down the graph
2806         } else if(click == Release) { // release 'miss' is ignored
2807             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2808             if(moving == 2) { // right up-click
2809                 nrOfSeekAds = 0; // refresh graph
2810                 soughtPending = TRUE;
2811                 SendToICS(ics_prefix);
2812                 SendToICS("sought\n"); // should this be "sought all"?
2813             }
2814             return TRUE;
2815         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2816         // press miss or release hit 'pop down' seek graph
2817         seekGraphUp = FALSE;
2818         DrawPosition(TRUE, NULL);
2819     }
2820     return TRUE;
2821 }
2822
2823 void
2824 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2825 {
2826 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2827 #define STARTED_NONE 0
2828 #define STARTED_MOVES 1
2829 #define STARTED_BOARD 2
2830 #define STARTED_OBSERVE 3
2831 #define STARTED_HOLDINGS 4
2832 #define STARTED_CHATTER 5
2833 #define STARTED_COMMENT 6
2834 #define STARTED_MOVES_NOHIDE 7
2835
2836     static int started = STARTED_NONE;
2837     static char parse[20000];
2838     static int parse_pos = 0;
2839     static char buf[BUF_SIZE + 1];
2840     static int firstTime = TRUE, intfSet = FALSE;
2841     static ColorClass prevColor = ColorNormal;
2842     static int savingComment = FALSE;
2843     static int cmatch = 0; // continuation sequence match
2844     char *bp;
2845     char str[MSG_SIZ];
2846     int i, oldi;
2847     int buf_len;
2848     int next_out;
2849     int tkind;
2850     int backup;    /* [DM] For zippy color lines */
2851     char *p;
2852     char talker[MSG_SIZ]; // [HGM] chat
2853     int channel, collective=0;
2854
2855     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2856
2857     if (appData.debugMode) {
2858       if (!error) {
2859         fprintf(debugFP, "<ICS: ");
2860         show_bytes(debugFP, data, count);
2861         fprintf(debugFP, "\n");
2862       }
2863     }
2864
2865     if (appData.debugMode) { int f = forwardMostMove;
2866         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2867                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2868                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2869     }
2870     if (count > 0) {
2871         /* If last read ended with a partial line that we couldn't parse,
2872            prepend it to the new read and try again. */
2873         if (leftover_len > 0) {
2874             for (i=0; i<leftover_len; i++)
2875               buf[i] = buf[leftover_start + i];
2876         }
2877
2878     /* copy new characters into the buffer */
2879     bp = buf + leftover_len;
2880     buf_len=leftover_len;
2881     for (i=0; i<count; i++)
2882     {
2883         // ignore these
2884         if (data[i] == '\r')
2885             continue;
2886
2887         // join lines split by ICS?
2888         if (!appData.noJoin)
2889         {
2890             /*
2891                 Joining just consists of finding matches against the
2892                 continuation sequence, and discarding that sequence
2893                 if found instead of copying it.  So, until a match
2894                 fails, there's nothing to do since it might be the
2895                 complete sequence, and thus, something we don't want
2896                 copied.
2897             */
2898             if (data[i] == cont_seq[cmatch])
2899             {
2900                 cmatch++;
2901                 if (cmatch == strlen(cont_seq))
2902                 {
2903                     cmatch = 0; // complete match.  just reset the counter
2904
2905                     /*
2906                         it's possible for the ICS to not include the space
2907                         at the end of the last word, making our [correct]
2908                         join operation fuse two separate words.  the server
2909                         does this when the space occurs at the width setting.
2910                     */
2911                     if (!buf_len || buf[buf_len-1] != ' ')
2912                     {
2913                         *bp++ = ' ';
2914                         buf_len++;
2915                     }
2916                 }
2917                 continue;
2918             }
2919             else if (cmatch)
2920             {
2921                 /*
2922                     match failed, so we have to copy what matched before
2923                     falling through and copying this character.  In reality,
2924                     this will only ever be just the newline character, but
2925                     it doesn't hurt to be precise.
2926                 */
2927                 strncpy(bp, cont_seq, cmatch);
2928                 bp += cmatch;
2929                 buf_len += cmatch;
2930                 cmatch = 0;
2931             }
2932         }
2933
2934         // copy this char
2935         *bp++ = data[i];
2936         buf_len++;
2937     }
2938
2939         buf[buf_len] = NULLCHAR;
2940 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2941         next_out = 0;
2942         leftover_start = 0;
2943
2944         i = 0;
2945         while (i < buf_len) {
2946             /* Deal with part of the TELNET option negotiation
2947                protocol.  We refuse to do anything beyond the
2948                defaults, except that we allow the WILL ECHO option,
2949                which ICS uses to turn off password echoing when we are
2950                directly connected to it.  We reject this option
2951                if localLineEditing mode is on (always on in xboard)
2952                and we are talking to port 23, which might be a real
2953                telnet server that will try to keep WILL ECHO on permanently.
2954              */
2955             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2956                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2957                 unsigned char option;
2958                 oldi = i;
2959                 switch ((unsigned char) buf[++i]) {
2960                   case TN_WILL:
2961                     if (appData.debugMode)
2962                       fprintf(debugFP, "\n<WILL ");
2963                     switch (option = (unsigned char) buf[++i]) {
2964                       case TN_ECHO:
2965                         if (appData.debugMode)
2966                           fprintf(debugFP, "ECHO ");
2967                         /* Reply only if this is a change, according
2968                            to the protocol rules. */
2969                         if (remoteEchoOption) break;
2970                         if (appData.localLineEditing &&
2971                             atoi(appData.icsPort) == TN_PORT) {
2972                             TelnetRequest(TN_DONT, TN_ECHO);
2973                         } else {
2974                             EchoOff();
2975                             TelnetRequest(TN_DO, TN_ECHO);
2976                             remoteEchoOption = TRUE;
2977                         }
2978                         break;
2979                       default:
2980                         if (appData.debugMode)
2981                           fprintf(debugFP, "%d ", option);
2982                         /* Whatever this is, we don't want it. */
2983                         TelnetRequest(TN_DONT, option);
2984                         break;
2985                     }
2986                     break;
2987                   case TN_WONT:
2988                     if (appData.debugMode)
2989                       fprintf(debugFP, "\n<WONT ");
2990                     switch (option = (unsigned char) buf[++i]) {
2991                       case TN_ECHO:
2992                         if (appData.debugMode)
2993                           fprintf(debugFP, "ECHO ");
2994                         /* Reply only if this is a change, according
2995                            to the protocol rules. */
2996                         if (!remoteEchoOption) break;
2997                         EchoOn();
2998                         TelnetRequest(TN_DONT, TN_ECHO);
2999                         remoteEchoOption = FALSE;
3000                         break;
3001                       default:
3002                         if (appData.debugMode)
3003                           fprintf(debugFP, "%d ", (unsigned char) option);
3004                         /* Whatever this is, it must already be turned
3005                            off, because we never agree to turn on
3006                            anything non-default, so according to the
3007                            protocol rules, we don't reply. */
3008                         break;
3009                     }
3010                     break;
3011                   case TN_DO:
3012                     if (appData.debugMode)
3013                       fprintf(debugFP, "\n<DO ");
3014                     switch (option = (unsigned char) buf[++i]) {
3015                       default:
3016                         /* Whatever this is, we refuse to do it. */
3017                         if (appData.debugMode)
3018                           fprintf(debugFP, "%d ", option);
3019                         TelnetRequest(TN_WONT, option);
3020                         break;
3021                     }
3022                     break;
3023                   case TN_DONT:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<DONT ");
3026                     switch (option = (unsigned char) buf[++i]) {
3027                       default:
3028                         if (appData.debugMode)
3029                           fprintf(debugFP, "%d ", option);
3030                         /* Whatever this is, we are already not doing
3031                            it, because we never agree to do anything
3032                            non-default, so according to the protocol
3033                            rules, we don't reply. */
3034                         break;
3035                     }
3036                     break;
3037                   case TN_IAC:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<IAC ");
3040                     /* Doubled IAC; pass it through */
3041                     i--;
3042                     break;
3043                   default:
3044                     if (appData.debugMode)
3045                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3046                     /* Drop all other telnet commands on the floor */
3047                     break;
3048                 }
3049                 if (oldi > next_out)
3050                   SendToPlayer(&buf[next_out], oldi - next_out);
3051                 if (++i > next_out)
3052                   next_out = i;
3053                 continue;
3054             }
3055
3056             /* OK, this at least will *usually* work */
3057             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3058                 loggedOn = TRUE;
3059             }
3060
3061             if (loggedOn && !intfSet) {
3062                 if (ics_type == ICS_ICC) {
3063                   snprintf(str, MSG_SIZ,
3064                           "/set-quietly interface %s\n/set-quietly style 12\n",
3065                           programVersion);
3066                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3067                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3068                 } else if (ics_type == ICS_CHESSNET) {
3069                   snprintf(str, MSG_SIZ, "/style 12\n");
3070                 } else {
3071                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3072                   strcat(str, programVersion);
3073                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3074                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3075                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3076 #ifdef WIN32
3077                   strcat(str, "$iset nohighlight 1\n");
3078 #endif
3079                   strcat(str, "$iset lock 1\n$style 12\n");
3080                 }
3081                 SendToICS(str);
3082                 NotifyFrontendLogin();
3083                 intfSet = TRUE;
3084             }
3085
3086             if (started == STARTED_COMMENT) {
3087                 /* Accumulate characters in comment */
3088                 parse[parse_pos++] = buf[i];
3089                 if (buf[i] == '\n') {
3090                     parse[parse_pos] = NULLCHAR;
3091                     if(chattingPartner>=0) {
3092                         char mess[MSG_SIZ];
3093                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3094                         OutputChatMessage(chattingPartner, mess);
3095                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3096                             int p;
3097                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3098                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3099                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3100                                 OutputChatMessage(p, mess);
3101                                 break;
3102                             }
3103                         }
3104                         chattingPartner = -1;
3105                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3106                         collective = 0;
3107                     } else
3108                     if(!suppressKibitz) // [HGM] kibitz
3109                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3110                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3111                         int nrDigit = 0, nrAlph = 0, j;
3112                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3113                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3114                         parse[parse_pos] = NULLCHAR;
3115                         // try to be smart: if it does not look like search info, it should go to
3116                         // ICS interaction window after all, not to engine-output window.
3117                         for(j=0; j<parse_pos; j++) { // count letters and digits
3118                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3119                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3120                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3121                         }
3122                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3123                             int depth=0; float score;
3124                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3125                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3126                                 pvInfoList[forwardMostMove-1].depth = depth;
3127                                 pvInfoList[forwardMostMove-1].score = 100*score;
3128                             }
3129                             OutputKibitz(suppressKibitz, parse);
3130                         } else {
3131                             char tmp[MSG_SIZ];
3132                             if(gameMode == IcsObserving) // restore original ICS messages
3133                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3134                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3135                             else
3136                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3138                             SendToPlayer(tmp, strlen(tmp));
3139                         }
3140                         next_out = i+1; // [HGM] suppress printing in ICS window
3141                     }
3142                     started = STARTED_NONE;
3143                 } else {
3144                     /* Don't match patterns against characters in comment */
3145                     i++;
3146                     continue;
3147                 }
3148             }
3149             if (started == STARTED_CHATTER) {
3150                 if (buf[i] != '\n') {
3151                     /* Don't match patterns against characters in chatter */
3152                     i++;
3153                     continue;
3154                 }
3155                 started = STARTED_NONE;
3156                 if(suppressKibitz) next_out = i+1;
3157             }
3158
3159             /* Kludge to deal with rcmd protocol */
3160             if (firstTime && looking_at(buf, &i, "\001*")) {
3161                 DisplayFatalError(&buf[1], 0, 1);
3162                 continue;
3163             } else {
3164                 firstTime = FALSE;
3165             }
3166
3167             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3168                 ics_type = ICS_ICC;
3169                 ics_prefix = "/";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3175                 ics_type = ICS_FICS;
3176                 ics_prefix = "$";
3177                 if (appData.debugMode)
3178                   fprintf(debugFP, "ics_type %d\n", ics_type);
3179                 continue;
3180             }
3181             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3182                 ics_type = ICS_CHESSNET;
3183                 ics_prefix = "/";
3184                 if (appData.debugMode)
3185                   fprintf(debugFP, "ics_type %d\n", ics_type);
3186                 continue;
3187             }
3188
3189             if (!loggedOn &&
3190                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3191                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3192                  looking_at(buf, &i, "will be \"*\""))) {
3193               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3194               continue;
3195             }
3196
3197             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3198               char buf[MSG_SIZ];
3199               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3200               DisplayIcsInteractionTitle(buf);
3201               have_set_title = TRUE;
3202             }
3203
3204             /* skip finger notes */
3205             if (started == STARTED_NONE &&
3206                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3207                  (buf[i] == '1' && buf[i+1] == '0')) &&
3208                 buf[i+2] == ':' && buf[i+3] == ' ') {
3209               started = STARTED_CHATTER;
3210               i += 3;
3211               continue;
3212             }
3213
3214             oldi = i;
3215             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3216             if(appData.seekGraph) {
3217                 if(soughtPending && MatchSoughtLine(buf+i)) {
3218                     i = strstr(buf+i, "rated") - buf;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     next_out = leftover_start = i;
3221                     started = STARTED_CHATTER;
3222                     suppressKibitz = TRUE;
3223                     continue;
3224                 }
3225                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3226                         && looking_at(buf, &i, "* ads displayed")) {
3227                     soughtPending = FALSE;
3228                     seekGraphUp = TRUE;
3229                     DrawSeekGraph();
3230                     continue;
3231                 }
3232                 if(appData.autoRefresh) {
3233                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3234                         int s = (ics_type == ICS_ICC); // ICC format differs
3235                         if(seekGraphUp)
3236                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3237                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3238                         looking_at(buf, &i, "*% "); // eat prompt
3239                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3240                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = i; // suppress
3242                         continue;
3243                     }
3244                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3245                         char *p = star_match[0];
3246                         while(*p) {
3247                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3248                             while(*p && *p++ != ' '); // next
3249                         }
3250                         looking_at(buf, &i, "*% "); // eat prompt
3251                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3252                         next_out = i;
3253                         continue;
3254                     }
3255                 }
3256             }
3257
3258             /* skip formula vars */
3259             if (started == STARTED_NONE &&
3260                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3261               started = STARTED_CHATTER;
3262               i += 3;
3263               continue;
3264             }
3265
3266             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3267             if (appData.autoKibitz && started == STARTED_NONE &&
3268                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3269                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3270                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3271                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3272                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3273                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3274                         suppressKibitz = TRUE;
3275                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3276                         next_out = i;
3277                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3278                                 && (gameMode == IcsPlayingWhite)) ||
3279                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3280                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3281                             started = STARTED_CHATTER; // own kibitz we simply discard
3282                         else {
3283                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3284                             parse_pos = 0; parse[0] = NULLCHAR;
3285                             savingComment = TRUE;
3286                             suppressKibitz = gameMode != IcsObserving ? 2 :
3287                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3288                         }
3289                         continue;
3290                 } else
3291                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3292                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3293                          && atoi(star_match[0])) {
3294                     // suppress the acknowledgements of our own autoKibitz
3295                     char *p;
3296                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3298                     SendToPlayer(star_match[0], strlen(star_match[0]));
3299                     if(looking_at(buf, &i, "*% ")) // eat prompt
3300                         suppressKibitz = FALSE;
3301                     next_out = i;
3302                     continue;
3303                 }
3304             } // [HGM] kibitz: end of patch
3305
3306             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3307
3308             // [HGM] chat: intercept tells by users for which we have an open chat window
3309             channel = -1;
3310             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3311                                            looking_at(buf, &i, "* whispers:") ||
3312                                            looking_at(buf, &i, "* kibitzes:") ||
3313                                            looking_at(buf, &i, "* shouts:") ||
3314                                            looking_at(buf, &i, "* c-shouts:") ||
3315                                            looking_at(buf, &i, "--> * ") ||
3316                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3317                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3320                 int p;
3321                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3322                 chattingPartner = -1; collective = 0;
3323
3324                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3328                     talker[0] = '['; strcat(talker, "] ");
3329                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3330                     chattingPartner = p; break;
3331                     }
3332                 } else
3333                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3334                 for(p=0; p<MAX_CHAT; p++) {
3335                     collective = 1;
3336                     if(!strcmp("kibitzes", chatPartner[p])) {
3337                         talker[0] = '['; strcat(talker, "] ");
3338                         chattingPartner = p; break;
3339                     }
3340                 } else
3341                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3342                 for(p=0; p<MAX_CHAT; p++) {
3343                     collective = 1;
3344                     if(!strcmp("whispers", chatPartner[p])) {
3345                         talker[0] = '['; strcat(talker, "] ");
3346                         chattingPartner = p; break;
3347                     }
3348                 } else
3349                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3350                   if(buf[i-8] == '-' && buf[i-3] == 't')
3351                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3352                     collective = 1;
3353                     if(!strcmp("c-shouts", chatPartner[p])) {
3354                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3355                         chattingPartner = p; break;
3356                     }
3357                   }
3358                   if(chattingPartner < 0)
3359                   for(p=0; p<MAX_CHAT; p++) {
3360                     collective = 1;
3361                     if(!strcmp("shouts", chatPartner[p])) {
3362                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3363                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3364                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3365                         chattingPartner = p; break;
3366                     }
3367                   }
3368                 }
3369                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3370                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3371                     talker[0] = 0;
3372                     Colorize(ColorTell, FALSE);
3373                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3374                     collective |= 2;
3375                     chattingPartner = p; break;
3376                 }
3377                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3378                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3379                     started = STARTED_COMMENT;
3380                     parse_pos = 0; parse[0] = NULLCHAR;
3381                     savingComment = 3 + chattingPartner; // counts as TRUE
3382                     if(collective == 3) i = oldi; else {
3383                         suppressKibitz = TRUE;
3384                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3385                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3386                         continue;
3387                     }
3388                 }
3389             } // [HGM] chat: end of patch
3390
3391           backup = i;
3392             if (appData.zippyTalk || appData.zippyPlay) {
3393                 /* [DM] Backup address for color zippy lines */
3394 #if ZIPPY
3395                if (loggedOn == TRUE)
3396                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3397                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3398 #endif
3399             } // [DM] 'else { ' deleted
3400                 if (
3401                     /* Regular tells and says */
3402                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3403                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3404                     looking_at(buf, &i, "* says: ") ||
3405                     /* Don't color "message" or "messages" output */
3406                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3407                     looking_at(buf, &i, "*. * at *:*: ") ||
3408                     looking_at(buf, &i, "--* (*:*): ") ||
3409                     /* Message notifications (same color as tells) */
3410                     looking_at(buf, &i, "* has left a message ") ||
3411                     looking_at(buf, &i, "* just sent you a message:\n") ||
3412                     /* Whispers and kibitzes */
3413                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3414                     looking_at(buf, &i, "* kibitzes: ") ||
3415                     /* Channel tells */
3416                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3417
3418                   if (tkind == 1 && strchr(star_match[0], ':')) {
3419                       /* Avoid "tells you:" spoofs in channels */
3420                      tkind = 3;
3421                   }
3422                   if (star_match[0][0] == NULLCHAR ||
3423                       strchr(star_match[0], ' ') ||
3424                       (tkind == 3 && strchr(star_match[1], ' '))) {
3425                     /* Reject bogus matches */
3426                     i = oldi;
3427                   } else {
3428                     if (appData.colorize) {
3429                       if (oldi > next_out) {
3430                         SendToPlayer(&buf[next_out], oldi - next_out);
3431                         next_out = oldi;
3432                       }
3433                       switch (tkind) {
3434                       case 1:
3435                         Colorize(ColorTell, FALSE);
3436                         curColor = ColorTell;
3437                         break;
3438                       case 2:
3439                         Colorize(ColorKibitz, FALSE);
3440                         curColor = ColorKibitz;
3441                         break;
3442                       case 3:
3443                         p = strrchr(star_match[1], '(');
3444                         if (p == NULL) {
3445                           p = star_match[1];
3446                         } else {
3447                           p++;
3448                         }
3449                         if (atoi(p) == 1) {
3450                           Colorize(ColorChannel1, FALSE);
3451                           curColor = ColorChannel1;
3452                         } else {
3453                           Colorize(ColorChannel, FALSE);
3454                           curColor = ColorChannel;
3455                         }
3456                         break;
3457                       case 5:
3458                         curColor = ColorNormal;
3459                         break;
3460                       }
3461                     }
3462                     if (started == STARTED_NONE && appData.autoComment &&
3463                         (gameMode == IcsObserving ||
3464                          gameMode == IcsPlayingWhite ||
3465                          gameMode == IcsPlayingBlack)) {
3466                       parse_pos = i - oldi;
3467                       memcpy(parse, &buf[oldi], parse_pos);
3468                       parse[parse_pos] = NULLCHAR;
3469                       started = STARTED_COMMENT;
3470                       savingComment = TRUE;
3471                     } else if(collective != 3) {
3472                       started = STARTED_CHATTER;
3473                       savingComment = FALSE;
3474                     }
3475                     loggedOn = TRUE;
3476                     continue;
3477                   }
3478                 }
3479
3480                 if (looking_at(buf, &i, "* s-shouts: ") ||
3481                     looking_at(buf, &i, "* c-shouts: ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorSShout, FALSE);
3488                         curColor = ColorSShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "--->")) {
3496                     loggedOn = TRUE;
3497                     continue;
3498                 }
3499
3500                 if (looking_at(buf, &i, "* shouts: ") ||
3501                     looking_at(buf, &i, "--> ")) {
3502                     if (appData.colorize) {
3503                         if (oldi > next_out) {
3504                             SendToPlayer(&buf[next_out], oldi - next_out);
3505                             next_out = oldi;
3506                         }
3507                         Colorize(ColorShout, FALSE);
3508                         curColor = ColorShout;
3509                     }
3510                     loggedOn = TRUE;
3511                     started = STARTED_CHATTER;
3512                     continue;
3513                 }
3514
3515                 if (looking_at( buf, &i, "Challenge:")) {
3516                     if (appData.colorize) {
3517                         if (oldi > next_out) {
3518                             SendToPlayer(&buf[next_out], oldi - next_out);
3519                             next_out = oldi;
3520                         }
3521                         Colorize(ColorChallenge, FALSE);
3522                         curColor = ColorChallenge;
3523                     }
3524                     loggedOn = TRUE;
3525                     continue;
3526                 }
3527
3528                 if (looking_at(buf, &i, "* offers you") ||
3529                     looking_at(buf, &i, "* offers to be") ||
3530                     looking_at(buf, &i, "* would like to") ||
3531                     looking_at(buf, &i, "* requests to") ||
3532                     looking_at(buf, &i, "Your opponent offers") ||
3533                     looking_at(buf, &i, "Your opponent requests")) {
3534
3535                     if (appData.colorize) {
3536                         if (oldi > next_out) {
3537                             SendToPlayer(&buf[next_out], oldi - next_out);
3538                             next_out = oldi;
3539                         }
3540                         Colorize(ColorRequest, FALSE);
3541                         curColor = ColorRequest;
3542                     }
3543                     continue;
3544                 }
3545
3546                 if (looking_at(buf, &i, "* (*) seeking")) {
3547                     if (appData.colorize) {
3548                         if (oldi > next_out) {
3549                             SendToPlayer(&buf[next_out], oldi - next_out);
3550                             next_out = oldi;
3551                         }
3552                         Colorize(ColorSeek, FALSE);
3553                         curColor = ColorSeek;
3554                     }
3555                     continue;
3556             }
3557
3558           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3559
3560             if (looking_at(buf, &i, "\\   ")) {
3561                 if (prevColor != ColorNormal) {
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                         next_out = oldi;
3565                     }
3566                     Colorize(prevColor, TRUE);
3567                     curColor = prevColor;
3568                 }
3569                 if (savingComment) {
3570                     parse_pos = i - oldi;
3571                     memcpy(parse, &buf[oldi], parse_pos);
3572                     parse[parse_pos] = NULLCHAR;
3573                     started = STARTED_COMMENT;
3574                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3575                         chattingPartner = savingComment - 3; // kludge to remember the box
3576                 } else {
3577                     started = STARTED_CHATTER;
3578                 }
3579                 continue;
3580             }
3581
3582             if (looking_at(buf, &i, "Black Strength :") ||
3583                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3584                 looking_at(buf, &i, "<10>") ||
3585                 looking_at(buf, &i, "#@#")) {
3586                 /* Wrong board style */
3587                 loggedOn = TRUE;
3588                 SendToICS(ics_prefix);
3589                 SendToICS("set style 12\n");
3590                 SendToICS(ics_prefix);
3591                 SendToICS("refresh\n");
3592                 continue;
3593             }
3594
3595             if (looking_at(buf, &i, "login:")) {
3596               if (!have_sent_ICS_logon) {
3597                 if(ICSInitScript())
3598                   have_sent_ICS_logon = 1;
3599                 else // no init script was found
3600                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3601               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3602                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3603               }
3604                 continue;
3605             }
3606
3607             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3608                 (looking_at(buf, &i, "\n<12> ") ||
3609                  looking_at(buf, &i, "<12> "))) {
3610                 loggedOn = TRUE;
3611                 if (oldi > next_out) {
3612                     SendToPlayer(&buf[next_out], oldi - next_out);
3613                 }
3614                 next_out = i;
3615                 started = STARTED_BOARD;
3616                 parse_pos = 0;
3617                 continue;
3618             }
3619
3620             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3621                 looking_at(buf, &i, "<b1> ")) {
3622                 if (oldi > next_out) {
3623                     SendToPlayer(&buf[next_out], oldi - next_out);
3624                 }
3625                 next_out = i;
3626                 started = STARTED_HOLDINGS;
3627                 parse_pos = 0;
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3632                 loggedOn = TRUE;
3633                 /* Header for a move list -- first line */
3634
3635                 switch (ics_getting_history) {
3636                   case H_FALSE:
3637                     switch (gameMode) {
3638                       case IcsIdle:
3639                       case BeginningOfGame:
3640                         /* User typed "moves" or "oldmoves" while we
3641                            were idle.  Pretend we asked for these
3642                            moves and soak them up so user can step
3643                            through them and/or save them.
3644                            */
3645                         Reset(FALSE, TRUE);
3646                         gameMode = IcsObserving;
3647                         ModeHighlight();
3648                         ics_gamenum = -1;
3649                         ics_getting_history = H_GOT_UNREQ_HEADER;
3650                         break;
3651                       case EditGame: /*?*/
3652                       case EditPosition: /*?*/
3653                         /* Should above feature work in these modes too? */
3654                         /* For now it doesn't */
3655                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3656                         break;
3657                       default:
3658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3659                         break;
3660                     }
3661                     break;
3662                   case H_REQUESTED:
3663                     /* Is this the right one? */
3664                     if (gameInfo.white && gameInfo.black &&
3665                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3666                         strcmp(gameInfo.black, star_match[2]) == 0) {
3667                         /* All is well */
3668                         ics_getting_history = H_GOT_REQ_HEADER;
3669                     }
3670                     break;
3671                   case H_GOT_REQ_HEADER:
3672                   case H_GOT_UNREQ_HEADER:
3673                   case H_GOT_UNWANTED_HEADER:
3674                   case H_GETTING_MOVES:
3675                     /* Should not happen */
3676                     DisplayError(_("Error gathering move list: two headers"), 0);
3677                     ics_getting_history = H_FALSE;
3678                     break;
3679                 }
3680
3681                 /* Save player ratings into gameInfo if needed */
3682                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3683                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3684                     (gameInfo.whiteRating == -1 ||
3685                      gameInfo.blackRating == -1)) {
3686
3687                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3688                     gameInfo.blackRating = string_to_rating(star_match[3]);
3689                     if (appData.debugMode)
3690                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3691                               gameInfo.whiteRating, gameInfo.blackRating);
3692                 }
3693                 continue;
3694             }
3695
3696             if (looking_at(buf, &i,
3697               "* * match, initial time: * minute*, increment: * second")) {
3698                 /* Header for a move list -- second line */
3699                 /* Initial board will follow if this is a wild game */
3700                 if (gameInfo.event != NULL) free(gameInfo.event);
3701                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3702                 gameInfo.event = StrSave(str);
3703                 /* [HGM] we switched variant. Translate boards if needed. */
3704                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3705                 continue;
3706             }
3707
3708             if (looking_at(buf, &i, "Move  ")) {
3709                 /* Beginning of a move list */
3710                 switch (ics_getting_history) {
3711                   case H_FALSE:
3712                     /* Normally should not happen */
3713                     /* Maybe user hit reset while we were parsing */
3714                     break;
3715                   case H_REQUESTED:
3716                     /* Happens if we are ignoring a move list that is not
3717                      * the one we just requested.  Common if the user
3718                      * tries to observe two games without turning off
3719                      * getMoveList */
3720                     break;
3721                   case H_GETTING_MOVES:
3722                     /* Should not happen */
3723                     DisplayError(_("Error gathering move list: nested"), 0);
3724                     ics_getting_history = H_FALSE;
3725                     break;
3726                   case H_GOT_REQ_HEADER:
3727                     ics_getting_history = H_GETTING_MOVES;
3728                     started = STARTED_MOVES;
3729                     parse_pos = 0;
3730                     if (oldi > next_out) {
3731                         SendToPlayer(&buf[next_out], oldi - next_out);
3732                     }
3733                     break;
3734                   case H_GOT_UNREQ_HEADER:
3735                     ics_getting_history = H_GETTING_MOVES;
3736                     started = STARTED_MOVES_NOHIDE;
3737                     parse_pos = 0;
3738                     break;
3739                   case H_GOT_UNWANTED_HEADER:
3740                     ics_getting_history = H_FALSE;
3741                     break;
3742                 }
3743                 continue;
3744             }
3745
3746             if (looking_at(buf, &i, "% ") ||
3747                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3748                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3749                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3750                     soughtPending = FALSE;
3751                     seekGraphUp = TRUE;
3752                     DrawSeekGraph();
3753                 }
3754                 if(suppressKibitz) next_out = i;
3755                 savingComment = FALSE;
3756                 suppressKibitz = 0;
3757                 switch (started) {
3758                   case STARTED_MOVES:
3759                   case STARTED_MOVES_NOHIDE:
3760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3761                     parse[parse_pos + i - oldi] = NULLCHAR;
3762                     ParseGameHistory(parse);
3763 #if ZIPPY
3764                     if (appData.zippyPlay && first.initDone) {
3765                         FeedMovesToProgram(&first, forwardMostMove);
3766                         if (gameMode == IcsPlayingWhite) {
3767                             if (WhiteOnMove(forwardMostMove)) {
3768                                 if (first.sendTime) {
3769                                   if (first.useColors) {
3770                                     SendToProgram("black\n", &first);
3771                                   }
3772                                   SendTimeRemaining(&first, TRUE);
3773                                 }
3774                                 if (first.useColors) {
3775                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3776                                 }
3777                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3778                                 first.maybeThinking = TRUE;
3779                             } else {
3780                                 if (first.usePlayother) {
3781                                   if (first.sendTime) {
3782                                     SendTimeRemaining(&first, TRUE);
3783                                   }
3784                                   SendToProgram("playother\n", &first);
3785                                   firstMove = FALSE;
3786                                 } else {
3787                                   firstMove = TRUE;
3788                                 }
3789                             }
3790                         } else if (gameMode == IcsPlayingBlack) {
3791                             if (!WhiteOnMove(forwardMostMove)) {
3792                                 if (first.sendTime) {
3793                                   if (first.useColors) {
3794                                     SendToProgram("white\n", &first);
3795                                   }
3796                                   SendTimeRemaining(&first, FALSE);
3797                                 }
3798                                 if (first.useColors) {
3799                                   SendToProgram("black\n", &first);
3800                                 }
3801                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3802                                 first.maybeThinking = TRUE;
3803                             } else {
3804                                 if (first.usePlayother) {
3805                                   if (first.sendTime) {
3806                                     SendTimeRemaining(&first, FALSE);
3807                                   }
3808                                   SendToProgram("playother\n", &first);
3809                                   firstMove = FALSE;
3810                                 } else {
3811                                   firstMove = TRUE;
3812                                 }
3813                             }
3814                         }
3815                     }
3816 #endif
3817                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3818                         /* Moves came from oldmoves or moves command
3819                            while we weren't doing anything else.
3820                            */
3821                         currentMove = forwardMostMove;
3822                         ClearHighlights();/*!!could figure this out*/
3823                         flipView = appData.flipView;
3824                         DrawPosition(TRUE, boards[currentMove]);
3825                         DisplayBothClocks();
3826                         snprintf(str, MSG_SIZ, "%s %s %s",
3827                                 gameInfo.white, _("vs."),  gameInfo.black);
3828                         DisplayTitle(str);
3829                         gameMode = IcsIdle;
3830                     } else {
3831                         /* Moves were history of an active game */
3832                         if (gameInfo.resultDetails != NULL) {
3833                             free(gameInfo.resultDetails);
3834                             gameInfo.resultDetails = NULL;
3835                         }
3836                     }
3837                     HistorySet(parseList, backwardMostMove,
3838                                forwardMostMove, currentMove-1);
3839                     DisplayMove(currentMove - 1);
3840                     if (started == STARTED_MOVES) next_out = i;
3841                     started = STARTED_NONE;
3842                     ics_getting_history = H_FALSE;
3843                     break;
3844
3845                   case STARTED_OBSERVE:
3846                     started = STARTED_NONE;
3847                     SendToICS(ics_prefix);
3848                     SendToICS("refresh\n");
3849                     break;
3850
3851                   default:
3852                     break;
3853                 }
3854                 if(bookHit) { // [HGM] book: simulate book reply
3855                     static char bookMove[MSG_SIZ]; // a bit generous?
3856
3857                     programStats.nodes = programStats.depth = programStats.time =
3858                     programStats.score = programStats.got_only_move = 0;
3859                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3860
3861                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3862                     strcat(bookMove, bookHit);
3863                     HandleMachineMove(bookMove, &first);
3864                 }
3865                 continue;
3866             }
3867
3868             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3869                  started == STARTED_HOLDINGS ||
3870                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3871                 /* Accumulate characters in move list or board */
3872                 parse[parse_pos++] = buf[i];
3873             }
3874
3875             /* Start of game messages.  Mostly we detect start of game
3876                when the first board image arrives.  On some versions
3877                of the ICS, though, we need to do a "refresh" after starting
3878                to observe in order to get the current board right away. */
3879             if (looking_at(buf, &i, "Adding game * to observation list")) {
3880                 started = STARTED_OBSERVE;
3881                 continue;
3882             }
3883
3884             /* Handle auto-observe */
3885             if (appData.autoObserve &&
3886                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3887                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3888                 char *player;
3889                 /* Choose the player that was highlighted, if any. */
3890                 if (star_match[0][0] == '\033' ||
3891                     star_match[1][0] != '\033') {
3892                     player = star_match[0];
3893                 } else {
3894                     player = star_match[2];
3895                 }
3896                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3897                         ics_prefix, StripHighlightAndTitle(player));
3898                 SendToICS(str);
3899
3900                 /* Save ratings from notify string */
3901                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3902                 player1Rating = string_to_rating(star_match[1]);
3903                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3904                 player2Rating = string_to_rating(star_match[3]);
3905
3906                 if (appData.debugMode)
3907                   fprintf(debugFP,
3908                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3909                           player1Name, player1Rating,
3910                           player2Name, player2Rating);
3911
3912                 continue;
3913             }
3914
3915             /* Deal with automatic examine mode after a game,
3916                and with IcsObserving -> IcsExamining transition */
3917             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3918                 looking_at(buf, &i, "has made you an examiner of game *")) {
3919
3920                 int gamenum = atoi(star_match[0]);
3921                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3922                     gamenum == ics_gamenum) {
3923                     /* We were already playing or observing this game;
3924                        no need to refetch history */
3925                     gameMode = IcsExamining;
3926                     if (pausing) {
3927                         pauseExamForwardMostMove = forwardMostMove;
3928                     } else if (currentMove < forwardMostMove) {
3929                         ForwardInner(forwardMostMove);
3930                     }
3931                 } else {
3932                     /* I don't think this case really can happen */
3933                     SendToICS(ics_prefix);
3934                     SendToICS("refresh\n");
3935                 }
3936                 continue;
3937             }
3938
3939             /* Error messages */
3940 //          if (ics_user_moved) {
3941             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3942                 if (looking_at(buf, &i, "Illegal move") ||
3943                     looking_at(buf, &i, "Not a legal move") ||
3944                     looking_at(buf, &i, "Your king is in check") ||
3945                     looking_at(buf, &i, "It isn't your turn") ||
3946                     looking_at(buf, &i, "It is not your move")) {
3947                     /* Illegal move */
3948                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3949                         currentMove = forwardMostMove-1;
3950                         DisplayMove(currentMove - 1); /* before DMError */
3951                         DrawPosition(FALSE, boards[currentMove]);
3952                         SwitchClocks(forwardMostMove-1); // [HGM] race
3953                         DisplayBothClocks();
3954                     }
3955                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3956                     ics_user_moved = 0;
3957                     continue;
3958                 }
3959             }
3960
3961             if (looking_at(buf, &i, "still have time") ||
3962                 looking_at(buf, &i, "not out of time") ||
3963                 looking_at(buf, &i, "either player is out of time") ||
3964                 looking_at(buf, &i, "has timeseal; checking")) {
3965                 /* We must have called his flag a little too soon */
3966                 whiteFlag = blackFlag = FALSE;
3967                 continue;
3968             }
3969
3970             if (looking_at(buf, &i, "added * seconds to") ||
3971                 looking_at(buf, &i, "seconds were added to")) {
3972                 /* Update the clocks */
3973                 SendToICS(ics_prefix);
3974                 SendToICS("refresh\n");
3975                 continue;
3976             }
3977
3978             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3979                 ics_clock_paused = TRUE;
3980                 StopClocks();
3981                 continue;
3982             }
3983
3984             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3985                 ics_clock_paused = FALSE;
3986                 StartClocks();
3987                 continue;
3988             }
3989
3990             /* Grab player ratings from the Creating: message.
3991                Note we have to check for the special case when
3992                the ICS inserts things like [white] or [black]. */
3993             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3994                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3995                 /* star_matches:
3996                    0    player 1 name (not necessarily white)
3997                    1    player 1 rating
3998                    2    empty, white, or black (IGNORED)
3999                    3    player 2 name (not necessarily black)
4000                    4    player 2 rating
4001
4002                    The names/ratings are sorted out when the game
4003                    actually starts (below).
4004                 */
4005                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4006                 player1Rating = string_to_rating(star_match[1]);
4007                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4008                 player2Rating = string_to_rating(star_match[4]);
4009
4010                 if (appData.debugMode)
4011                   fprintf(debugFP,
4012                           "Ratings from 'Creating:' %s %d, %s %d\n",
4013                           player1Name, player1Rating,
4014                           player2Name, player2Rating);
4015
4016                 continue;
4017             }
4018
4019             /* Improved generic start/end-of-game messages */
4020             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4021                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4022                 /* If tkind == 0: */
4023                 /* star_match[0] is the game number */
4024                 /*           [1] is the white player's name */
4025                 /*           [2] is the black player's name */
4026                 /* For end-of-game: */
4027                 /*           [3] is the reason for the game end */
4028                 /*           [4] is a PGN end game-token, preceded by " " */
4029                 /* For start-of-game: */
4030                 /*           [3] begins with "Creating" or "Continuing" */
4031                 /*           [4] is " *" or empty (don't care). */
4032                 int gamenum = atoi(star_match[0]);
4033                 char *whitename, *blackname, *why, *endtoken;
4034                 ChessMove endtype = EndOfFile;
4035
4036                 if (tkind == 0) {
4037                   whitename = star_match[1];
4038                   blackname = star_match[2];
4039                   why = star_match[3];
4040                   endtoken = star_match[4];
4041                 } else {
4042                   whitename = star_match[1];
4043                   blackname = star_match[3];
4044                   why = star_match[5];
4045                   endtoken = star_match[6];
4046                 }
4047
4048                 /* Game start messages */
4049                 if (strncmp(why, "Creating ", 9) == 0 ||
4050                     strncmp(why, "Continuing ", 11) == 0) {
4051                     gs_gamenum = gamenum;
4052                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4053                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4054                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4055 #if ZIPPY
4056                     if (appData.zippyPlay) {
4057                         ZippyGameStart(whitename, blackname);
4058                     }
4059 #endif /*ZIPPY*/
4060                     partnerBoardValid = FALSE; // [HGM] bughouse
4061                     continue;
4062                 }
4063
4064                 /* Game end messages */
4065                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4066                     ics_gamenum != gamenum) {
4067                     continue;
4068                 }
4069                 while (endtoken[0] == ' ') endtoken++;
4070                 switch (endtoken[0]) {
4071                   case '*':
4072                   default:
4073                     endtype = GameUnfinished;
4074                     break;
4075                   case '0':
4076                     endtype = BlackWins;
4077                     break;
4078                   case '1':
4079                     if (endtoken[1] == '/')
4080                       endtype = GameIsDrawn;
4081                     else
4082                       endtype = WhiteWins;
4083                     break;
4084                 }
4085                 GameEnds(endtype, why, GE_ICS);
4086 #if ZIPPY
4087                 if (appData.zippyPlay && first.initDone) {
4088                     ZippyGameEnd(endtype, why);
4089                     if (first.pr == NoProc) {
4090                       /* Start the next process early so that we'll
4091                          be ready for the next challenge */
4092                       StartChessProgram(&first);
4093                     }
4094                     /* Send "new" early, in case this command takes
4095                        a long time to finish, so that we'll be ready
4096                        for the next challenge. */
4097                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4098                     Reset(TRUE, TRUE);
4099                 }
4100 #endif /*ZIPPY*/
4101                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4102                 continue;
4103             }
4104
4105             if (looking_at(buf, &i, "Removing game * from observation") ||
4106                 looking_at(buf, &i, "no longer observing game *") ||
4107                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4108                 if (gameMode == IcsObserving &&
4109                     atoi(star_match[0]) == ics_gamenum)
4110                   {
4111                       /* icsEngineAnalyze */
4112                       if (appData.icsEngineAnalyze) {
4113                             ExitAnalyzeMode();
4114                             ModeHighlight();
4115                       }
4116                       StopClocks();
4117                       gameMode = IcsIdle;
4118                       ics_gamenum = -1;
4119                       ics_user_moved = FALSE;
4120                   }
4121                 continue;
4122             }
4123
4124             if (looking_at(buf, &i, "no longer examining game *")) {
4125                 if (gameMode == IcsExamining &&
4126                     atoi(star_match[0]) == ics_gamenum)
4127                   {
4128                       gameMode = IcsIdle;
4129                       ics_gamenum = -1;
4130                       ics_user_moved = FALSE;
4131                   }
4132                 continue;
4133             }
4134
4135             /* Advance leftover_start past any newlines we find,
4136                so only partial lines can get reparsed */
4137             if (looking_at(buf, &i, "\n")) {
4138                 prevColor = curColor;
4139                 if (curColor != ColorNormal) {
4140                     if (oldi > next_out) {
4141                         SendToPlayer(&buf[next_out], oldi - next_out);
4142                         next_out = oldi;
4143                     }
4144                     Colorize(ColorNormal, FALSE);
4145                     curColor = ColorNormal;
4146                 }
4147                 if (started == STARTED_BOARD) {
4148                     started = STARTED_NONE;
4149                     parse[parse_pos] = NULLCHAR;
4150                     ParseBoard12(parse);
4151                     ics_user_moved = 0;
4152
4153                     /* Send premove here */
4154                     if (appData.premove) {
4155                       char str[MSG_SIZ];
4156                       if (currentMove == 0 &&
4157                           gameMode == IcsPlayingWhite &&
4158                           appData.premoveWhite) {
4159                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                         SendToICS(str);
4163                       } else if (currentMove == 1 &&
4164                                  gameMode == IcsPlayingBlack &&
4165                                  appData.premoveBlack) {
4166                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                         SendToICS(str);
4170                       } else if (gotPremove) {
4171                         int oldFMM = forwardMostMove;
4172                         gotPremove = 0;
4173                         ClearPremoveHighlights();
4174                         if (appData.debugMode)
4175                           fprintf(debugFP, "Sending premove:\n");
4176                           UserMoveEvent(premoveFromX, premoveFromY,
4177                                         premoveToX, premoveToY,
4178                                         premovePromoChar);
4179                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4180                           if(moveList[oldFMM-1][1] != '@')
4181                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4182                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4183                           else // (drop)
4184                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4185                         }
4186                       }
4187                     }
4188
4189                     /* Usually suppress following prompt */
4190                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4191                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4192                         if (looking_at(buf, &i, "*% ")) {
4193                             savingComment = FALSE;
4194                             suppressKibitz = 0;
4195                         }
4196                     }
4197                     next_out = i;
4198                 } else if (started == STARTED_HOLDINGS) {
4199                     int gamenum;
4200                     char new_piece[MSG_SIZ];
4201                     started = STARTED_NONE;
4202                     parse[parse_pos] = NULLCHAR;
4203                     if (appData.debugMode)
4204                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4205                                                         parse, currentMove);
4206                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4207                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4208                         if (gameInfo.variant == VariantNormal) {
4209                           /* [HGM] We seem to switch variant during a game!
4210                            * Presumably no holdings were displayed, so we have
4211                            * to move the position two files to the right to
4212                            * create room for them!
4213                            */
4214                           VariantClass newVariant;
4215                           switch(gameInfo.boardWidth) { // base guess on board width
4216                                 case 9:  newVariant = VariantShogi; break;
4217                                 case 10: newVariant = VariantGreat; break;
4218                                 default: newVariant = VariantCrazyhouse; break;
4219                           }
4220                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4221                           /* Get a move list just to see the header, which
4222                              will tell us whether this is really bug or zh */
4223                           if (ics_getting_history == H_FALSE) {
4224                             ics_getting_history = H_REQUESTED;
4225                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4226                             SendToICS(str);
4227                           }
4228                         }
4229                         new_piece[0] = NULLCHAR;
4230                         sscanf(parse, "game %d white [%s black [%s <- %s",
4231                                &gamenum, white_holding, black_holding,
4232                                new_piece);
4233                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4234                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4235                         /* [HGM] copy holdings to board holdings area */
4236                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4237                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4238                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4239 #if ZIPPY
4240                         if (appData.zippyPlay && first.initDone) {
4241                             ZippyHoldings(white_holding, black_holding,
4242                                           new_piece);
4243                         }
4244 #endif /*ZIPPY*/
4245                         if (tinyLayout || smallLayout) {
4246                             char wh[16], bh[16];
4247                             PackHolding(wh, white_holding);
4248                             PackHolding(bh, black_holding);
4249                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4250                                     gameInfo.white, gameInfo.black);
4251                         } else {
4252                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4253                                     gameInfo.white, white_holding, _("vs."),
4254                                     gameInfo.black, black_holding);
4255                         }
4256                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4257                         DrawPosition(FALSE, boards[currentMove]);
4258                         DisplayTitle(str);
4259                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4260                         sscanf(parse, "game %d white [%s black [%s <- %s",
4261                                &gamenum, white_holding, black_holding,
4262                                new_piece);
4263                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4264                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4265                         /* [HGM] copy holdings to partner-board holdings area */
4266                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4267                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4268                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4269                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4270                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4271                       }
4272                     }
4273                     /* Suppress following prompt */
4274                     if (looking_at(buf, &i, "*% ")) {
4275                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4276                         savingComment = FALSE;
4277                         suppressKibitz = 0;
4278                     }
4279                     next_out = i;
4280                 }
4281                 continue;
4282             }
4283
4284             i++;                /* skip unparsed character and loop back */
4285         }
4286
4287         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4288 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4289 //          SendToPlayer(&buf[next_out], i - next_out);
4290             started != STARTED_HOLDINGS && leftover_start > next_out) {
4291             SendToPlayer(&buf[next_out], leftover_start - next_out);
4292             next_out = i;
4293         }
4294
4295         leftover_len = buf_len - leftover_start;
4296         /* if buffer ends with something we couldn't parse,
4297            reparse it after appending the next read */
4298
4299     } else if (count == 0) {
4300         RemoveInputSource(isr);
4301         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4302     } else {
4303         DisplayFatalError(_("Error reading from ICS"), error, 1);
4304     }
4305 }
4306
4307
4308 /* Board style 12 looks like this:
4309
4310    <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
4311
4312  * The "<12> " is stripped before it gets to this routine.  The two
4313  * trailing 0's (flip state and clock ticking) are later addition, and
4314  * some chess servers may not have them, or may have only the first.
4315  * Additional trailing fields may be added in the future.
4316  */
4317
4318 #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"
4319
4320 #define RELATION_OBSERVING_PLAYED    0
4321 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4322 #define RELATION_PLAYING_MYMOVE      1
4323 #define RELATION_PLAYING_NOTMYMOVE  -1
4324 #define RELATION_EXAMINING           2
4325 #define RELATION_ISOLATED_BOARD     -3
4326 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4327
4328 void
4329 ParseBoard12 (char *string)
4330 {
4331 #if ZIPPY
4332     int i, takeback;
4333     char *bookHit = NULL; // [HGM] book
4334 #endif
4335     GameMode newGameMode;
4336     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4337     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4338     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4339     char to_play, board_chars[200];
4340     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4341     char black[32], white[32];
4342     Board board;
4343     int prevMove = currentMove;
4344     int ticking = 2;
4345     ChessMove moveType;
4346     int fromX, fromY, toX, toY;
4347     char promoChar;
4348     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4349     Boolean weird = FALSE, reqFlag = FALSE;
4350
4351     fromX = fromY = toX = toY = -1;
4352
4353     newGame = FALSE;
4354
4355     if (appData.debugMode)
4356       fprintf(debugFP, "Parsing board: %s\n", string);
4357
4358     move_str[0] = NULLCHAR;
4359     elapsed_time[0] = NULLCHAR;
4360     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4361         int  i = 0, j;
4362         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4363             if(string[i] == ' ') { ranks++; files = 0; }
4364             else files++;
4365             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4366             i++;
4367         }
4368         for(j = 0; j <i; j++) board_chars[j] = string[j];
4369         board_chars[i] = '\0';
4370         string += i + 1;
4371     }
4372     n = sscanf(string, PATTERN, &to_play, &double_push,
4373                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4374                &gamenum, white, black, &relation, &basetime, &increment,
4375                &white_stren, &black_stren, &white_time, &black_time,
4376                &moveNum, str, elapsed_time, move_str, &ics_flip,
4377                &ticking);
4378
4379     if (n < 21) {
4380         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4381         DisplayError(str, 0);
4382         return;
4383     }
4384
4385     /* Convert the move number to internal form */
4386     moveNum = (moveNum - 1) * 2;
4387     if (to_play == 'B') moveNum++;
4388     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4389       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4390                         0, 1);
4391       return;
4392     }
4393
4394     switch (relation) {
4395       case RELATION_OBSERVING_PLAYED:
4396       case RELATION_OBSERVING_STATIC:
4397         if (gamenum == -1) {
4398             /* Old ICC buglet */
4399             relation = RELATION_OBSERVING_STATIC;
4400         }
4401         newGameMode = IcsObserving;
4402         break;
4403       case RELATION_PLAYING_MYMOVE:
4404       case RELATION_PLAYING_NOTMYMOVE:
4405         newGameMode =
4406           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4407             IcsPlayingWhite : IcsPlayingBlack;
4408         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4409         break;
4410       case RELATION_EXAMINING:
4411         newGameMode = IcsExamining;
4412         break;
4413       case RELATION_ISOLATED_BOARD:
4414       default:
4415         /* Just display this board.  If user was doing something else,
4416            we will forget about it until the next board comes. */
4417         newGameMode = IcsIdle;
4418         break;
4419       case RELATION_STARTING_POSITION:
4420         newGameMode = gameMode;
4421         break;
4422     }
4423
4424     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4425         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4426          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4427       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4428       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4429       static int lastBgGame = -1;
4430       char *toSqr;
4431       for (k = 0; k < ranks; k++) {
4432         for (j = 0; j < files; j++)
4433           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4434         if(gameInfo.holdingsWidth > 1) {
4435              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4436              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4437         }
4438       }
4439       CopyBoard(partnerBoard, board);
4440       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4441         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4442         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4443       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4444       if(toSqr = strchr(str, '-')) {
4445         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4446         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4447       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4448       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4449       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4450       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4451       if(twoBoards) {
4452           DisplayWhiteClock(white_time*fac, to_play == 'W');
4453           DisplayBlackClock(black_time*fac, to_play != 'W');
4454           activePartner = to_play;
4455           if(gamenum != lastBgGame) {
4456               char buf[MSG_SIZ];
4457               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4458               DisplayTitle(buf);
4459           }
4460           lastBgGame = gamenum;
4461           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4462                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4463       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4464                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4465       if(!twoBoards) DisplayMessage(partnerStatus, "");
4466         partnerBoardValid = TRUE;
4467       return;
4468     }
4469
4470     if(appData.dualBoard && appData.bgObserve) {
4471         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4472             SendToICS(ics_prefix), SendToICS("pobserve\n");
4473         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4474             char buf[MSG_SIZ];
4475             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4476             SendToICS(buf);
4477         }
4478     }
4479
4480     /* Modify behavior for initial board display on move listing
4481        of wild games.
4482        */
4483     switch (ics_getting_history) {
4484       case H_FALSE:
4485       case H_REQUESTED:
4486         break;
4487       case H_GOT_REQ_HEADER:
4488       case H_GOT_UNREQ_HEADER:
4489         /* This is the initial position of the current game */
4490         gamenum = ics_gamenum;
4491         moveNum = 0;            /* old ICS bug workaround */
4492         if (to_play == 'B') {
4493           startedFromSetupPosition = TRUE;
4494           blackPlaysFirst = TRUE;
4495           moveNum = 1;
4496           if (forwardMostMove == 0) forwardMostMove = 1;
4497           if (backwardMostMove == 0) backwardMostMove = 1;
4498           if (currentMove == 0) currentMove = 1;
4499         }
4500         newGameMode = gameMode;
4501         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4502         break;
4503       case H_GOT_UNWANTED_HEADER:
4504         /* This is an initial board that we don't want */
4505         return;
4506       case H_GETTING_MOVES:
4507         /* Should not happen */
4508         DisplayError(_("Error gathering move list: extra board"), 0);
4509         ics_getting_history = H_FALSE;
4510         return;
4511     }
4512
4513    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4514                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4515                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4516      /* [HGM] We seem to have switched variant unexpectedly
4517       * Try to guess new variant from board size
4518       */
4519           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4520           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4521           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4522           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4523           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4524           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4525           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4526           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4527           /* Get a move list just to see the header, which
4528              will tell us whether this is really bug or zh */
4529           if (ics_getting_history == H_FALSE) {
4530             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4531             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4532             SendToICS(str);
4533           }
4534     }
4535
4536     /* Take action if this is the first board of a new game, or of a
4537        different game than is currently being displayed.  */
4538     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4539         relation == RELATION_ISOLATED_BOARD) {
4540
4541         /* Forget the old game and get the history (if any) of the new one */
4542         if (gameMode != BeginningOfGame) {
4543           Reset(TRUE, TRUE);
4544         }
4545         newGame = TRUE;
4546         if (appData.autoRaiseBoard) BoardToTop();
4547         prevMove = -3;
4548         if (gamenum == -1) {
4549             newGameMode = IcsIdle;
4550         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4551                    appData.getMoveList && !reqFlag) {
4552             /* Need to get game history */
4553             ics_getting_history = H_REQUESTED;
4554             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4555             SendToICS(str);
4556         }
4557
4558         /* Initially flip the board to have black on the bottom if playing
4559            black or if the ICS flip flag is set, but let the user change
4560            it with the Flip View button. */
4561         flipView = appData.autoFlipView ?
4562           (newGameMode == IcsPlayingBlack) || ics_flip :
4563           appData.flipView;
4564
4565         /* Done with values from previous mode; copy in new ones */
4566         gameMode = newGameMode;
4567         ModeHighlight();
4568         ics_gamenum = gamenum;
4569         if (gamenum == gs_gamenum) {
4570             int klen = strlen(gs_kind);
4571             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4572             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4573             gameInfo.event = StrSave(str);
4574         } else {
4575             gameInfo.event = StrSave("ICS game");
4576         }
4577         gameInfo.site = StrSave(appData.icsHost);
4578         gameInfo.date = PGNDate();
4579         gameInfo.round = StrSave("-");
4580         gameInfo.white = StrSave(white);
4581         gameInfo.black = StrSave(black);
4582         timeControl = basetime * 60 * 1000;
4583         timeControl_2 = 0;
4584         timeIncrement = increment * 1000;
4585         movesPerSession = 0;
4586         gameInfo.timeControl = TimeControlTagValue();
4587         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4588   if (appData.debugMode) {
4589     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4590     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4591     setbuf(debugFP, NULL);
4592   }
4593
4594         gameInfo.outOfBook = NULL;
4595
4596         /* Do we have the ratings? */
4597         if (strcmp(player1Name, white) == 0 &&
4598             strcmp(player2Name, black) == 0) {
4599             if (appData.debugMode)
4600               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4601                       player1Rating, player2Rating);
4602             gameInfo.whiteRating = player1Rating;
4603             gameInfo.blackRating = player2Rating;
4604         } else if (strcmp(player2Name, white) == 0 &&
4605                    strcmp(player1Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player2Rating, player1Rating);
4609             gameInfo.whiteRating = player2Rating;
4610             gameInfo.blackRating = player1Rating;
4611         }
4612         player1Name[0] = player2Name[0] = NULLCHAR;
4613
4614         /* Silence shouts if requested */
4615         if (appData.quietPlay &&
4616             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4617             SendToICS(ics_prefix);
4618             SendToICS("set shout 0\n");
4619         }
4620     }
4621
4622     /* Deal with midgame name changes */
4623     if (!newGame) {
4624         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4625             if (gameInfo.white) free(gameInfo.white);
4626             gameInfo.white = StrSave(white);
4627         }
4628         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4629             if (gameInfo.black) free(gameInfo.black);
4630             gameInfo.black = StrSave(black);
4631         }
4632     }
4633
4634     /* Throw away game result if anything actually changes in examine mode */
4635     if (gameMode == IcsExamining && !newGame) {
4636         gameInfo.result = GameUnfinished;
4637         if (gameInfo.resultDetails != NULL) {
4638             free(gameInfo.resultDetails);
4639             gameInfo.resultDetails = NULL;
4640         }
4641     }
4642
4643     /* In pausing && IcsExamining mode, we ignore boards coming
4644        in if they are in a different variation than we are. */
4645     if (pauseExamInvalid) return;
4646     if (pausing && gameMode == IcsExamining) {
4647         if (moveNum <= pauseExamForwardMostMove) {
4648             pauseExamInvalid = TRUE;
4649             forwardMostMove = pauseExamForwardMostMove;
4650             return;
4651         }
4652     }
4653
4654   if (appData.debugMode) {
4655     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4656   }
4657     /* Parse the board */
4658     for (k = 0; k < ranks; k++) {
4659       for (j = 0; j < files; j++)
4660         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4661       if(gameInfo.holdingsWidth > 1) {
4662            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4663            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4664       }
4665     }
4666     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4667       board[5][BOARD_RGHT+1] = WhiteAngel;
4668       board[6][BOARD_RGHT+1] = WhiteMarshall;
4669       board[1][0] = BlackMarshall;
4670       board[2][0] = BlackAngel;
4671       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4672     }
4673     CopyBoard(boards[moveNum], board);
4674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4675     if (moveNum == 0) {
4676         startedFromSetupPosition =
4677           !CompareBoards(board, initialPosition);
4678         if(startedFromSetupPosition)
4679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4680     }
4681
4682     /* [HGM] Set castling rights. Take the outermost Rooks,
4683        to make it also work for FRC opening positions. Note that board12
4684        is really defective for later FRC positions, as it has no way to
4685        indicate which Rook can castle if they are on the same side of King.
4686        For the initial position we grant rights to the outermost Rooks,
4687        and remember thos rights, and we then copy them on positions
4688        later in an FRC game. This means WB might not recognize castlings with
4689        Rooks that have moved back to their original position as illegal,
4690        but in ICS mode that is not its job anyway.
4691     */
4692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4694
4695         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4696             if(board[0][i] == WhiteRook) j = i;
4697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4698         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4699             if(board[0][i] == WhiteRook) j = i;
4700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707
4708         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4709         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4710         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4711             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713             if(board[BOARD_HEIGHT-1][k] == bKing)
4714                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4715         if(gameInfo.variant == VariantTwoKings) {
4716             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4717             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4718             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4719         }
4720     } else { int r;
4721         r = boards[moveNum][CASTLING][0] = initialRights[0];
4722         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4723         r = boards[moveNum][CASTLING][1] = initialRights[1];
4724         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4725         r = boards[moveNum][CASTLING][3] = initialRights[3];
4726         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4727         r = boards[moveNum][CASTLING][4] = initialRights[4];
4728         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4729         /* wildcastle kludge: always assume King has rights */
4730         r = boards[moveNum][CASTLING][2] = initialRights[2];
4731         r = boards[moveNum][CASTLING][5] = initialRights[5];
4732     }
4733     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4734     boards[moveNum][EP_STATUS] = EP_NONE;
4735     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4736     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4737     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4738
4739
4740     if (ics_getting_history == H_GOT_REQ_HEADER ||
4741         ics_getting_history == H_GOT_UNREQ_HEADER) {
4742         /* This was an initial position from a move list, not
4743            the current position */
4744         return;
4745     }
4746
4747     /* Update currentMove and known move number limits */
4748     newMove = newGame || moveNum > forwardMostMove;
4749
4750     if (newGame) {
4751         forwardMostMove = backwardMostMove = currentMove = moveNum;
4752         if (gameMode == IcsExamining && moveNum == 0) {
4753           /* Workaround for ICS limitation: we are not told the wild
4754              type when starting to examine a game.  But if we ask for
4755              the move list, the move list header will tell us */
4756             ics_getting_history = H_REQUESTED;
4757             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4758             SendToICS(str);
4759         }
4760     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4761                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4762 #if ZIPPY
4763         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4764         /* [HGM] applied this also to an engine that is silently watching        */
4765         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4766             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4767             gameInfo.variant == currentlyInitializedVariant) {
4768           takeback = forwardMostMove - moveNum;
4769           for (i = 0; i < takeback; i++) {
4770             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4771             SendToProgram("undo\n", &first);
4772           }
4773         }
4774 #endif
4775
4776         forwardMostMove = moveNum;
4777         if (!pausing || currentMove > forwardMostMove)
4778           currentMove = forwardMostMove;
4779     } else {
4780         /* New part of history that is not contiguous with old part */
4781         if (pausing && gameMode == IcsExamining) {
4782             pauseExamInvalid = TRUE;
4783             forwardMostMove = pauseExamForwardMostMove;
4784             return;
4785         }
4786         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4787 #if ZIPPY
4788             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4789                 // [HGM] when we will receive the move list we now request, it will be
4790                 // fed to the engine from the first move on. So if the engine is not
4791                 // in the initial position now, bring it there.
4792                 InitChessProgram(&first, 0);
4793             }
4794 #endif
4795             ics_getting_history = H_REQUESTED;
4796             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4797             SendToICS(str);
4798         }
4799         forwardMostMove = backwardMostMove = currentMove = moveNum;
4800     }
4801
4802     /* Update the clocks */
4803     if (strchr(elapsed_time, '.')) {
4804       /* Time is in ms */
4805       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4806       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4807     } else {
4808       /* Time is in seconds */
4809       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4810       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4811     }
4812
4813
4814 #if ZIPPY
4815     if (appData.zippyPlay && newGame &&
4816         gameMode != IcsObserving && gameMode != IcsIdle &&
4817         gameMode != IcsExamining)
4818       ZippyFirstBoard(moveNum, basetime, increment);
4819 #endif
4820
4821     /* Put the move on the move list, first converting
4822        to canonical algebraic form. */
4823     if (moveNum > 0) {
4824   if (appData.debugMode) {
4825     int f = forwardMostMove;
4826     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4827             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4828             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4829     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4830     fprintf(debugFP, "moveNum = %d\n", moveNum);
4831     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4832     setbuf(debugFP, NULL);
4833   }
4834         if (moveNum <= backwardMostMove) {
4835             /* We don't know what the board looked like before
4836                this move.  Punt. */
4837           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4838             strcat(parseList[moveNum - 1], " ");
4839             strcat(parseList[moveNum - 1], elapsed_time);
4840             moveList[moveNum - 1][0] = NULLCHAR;
4841         } else if (strcmp(move_str, "none") == 0) {
4842             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4843             /* Again, we don't know what the board looked like;
4844                this is really the start of the game. */
4845             parseList[moveNum - 1][0] = NULLCHAR;
4846             moveList[moveNum - 1][0] = NULLCHAR;
4847             backwardMostMove = moveNum;
4848             startedFromSetupPosition = TRUE;
4849             fromX = fromY = toX = toY = -1;
4850         } else {
4851           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4852           //                 So we parse the long-algebraic move string in stead of the SAN move
4853           int valid; char buf[MSG_SIZ], *prom;
4854
4855           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4856                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4857           // str looks something like "Q/a1-a2"; kill the slash
4858           if(str[1] == '/')
4859             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4860           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4861           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4862                 strcat(buf, prom); // long move lacks promo specification!
4863           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4864                 if(appData.debugMode)
4865                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4866                 safeStrCpy(move_str, buf, MSG_SIZ);
4867           }
4868           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4869                                 &fromX, &fromY, &toX, &toY, &promoChar)
4870                || ParseOneMove(buf, moveNum - 1, &moveType,
4871                                 &fromX, &fromY, &toX, &toY, &promoChar);
4872           // end of long SAN patch
4873           if (valid) {
4874             (void) CoordsToAlgebraic(boards[moveNum - 1],
4875                                      PosFlags(moveNum - 1),
4876                                      fromY, fromX, toY, toX, promoChar,
4877                                      parseList[moveNum-1]);
4878             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4879               case MT_NONE:
4880               case MT_STALEMATE:
4881               default:
4882                 break;
4883               case MT_CHECK:
4884                 if(!IS_SHOGI(gameInfo.variant))
4885                     strcat(parseList[moveNum - 1], "+");
4886                 break;
4887               case MT_CHECKMATE:
4888               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4889                 strcat(parseList[moveNum - 1], "#");
4890                 break;
4891             }
4892             strcat(parseList[moveNum - 1], " ");
4893             strcat(parseList[moveNum - 1], elapsed_time);
4894             /* currentMoveString is set as a side-effect of ParseOneMove */
4895             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4896             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4897             strcat(moveList[moveNum - 1], "\n");
4898
4899             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4900                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4901               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4902                 ChessSquare old, new = boards[moveNum][k][j];
4903                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4904                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4905                   if(old == new) continue;
4906                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4907                   else if(new == WhiteWazir || new == BlackWazir) {
4908                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4909                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4910                       else boards[moveNum][k][j] = old; // preserve type of Gold
4911                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4912                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4913               }
4914           } else {
4915             /* Move from ICS was illegal!?  Punt. */
4916             if (appData.debugMode) {
4917               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4918               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4919             }
4920             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4921             strcat(parseList[moveNum - 1], " ");
4922             strcat(parseList[moveNum - 1], elapsed_time);
4923             moveList[moveNum - 1][0] = NULLCHAR;
4924             fromX = fromY = toX = toY = -1;
4925           }
4926         }
4927   if (appData.debugMode) {
4928     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4929     setbuf(debugFP, NULL);
4930   }
4931
4932 #if ZIPPY
4933         /* Send move to chess program (BEFORE animating it). */
4934         if (appData.zippyPlay && !newGame && newMove &&
4935            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4936
4937             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4938                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4939                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4940                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4941                             move_str);
4942                     DisplayError(str, 0);
4943                 } else {
4944                     if (first.sendTime) {
4945                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4946                     }
4947                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4948                     if (firstMove && !bookHit) {
4949                         firstMove = FALSE;
4950                         if (first.useColors) {
4951                           SendToProgram(gameMode == IcsPlayingWhite ?
4952                                         "white\ngo\n" :
4953                                         "black\ngo\n", &first);
4954                         } else {
4955                           SendToProgram("go\n", &first);
4956                         }
4957                         first.maybeThinking = TRUE;
4958                     }
4959                 }
4960             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4961               if (moveList[moveNum - 1][0] == NULLCHAR) {
4962                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4963                 DisplayError(str, 0);
4964               } else {
4965                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4966                 SendMoveToProgram(moveNum - 1, &first);
4967               }
4968             }
4969         }
4970 #endif
4971     }
4972
4973     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4974         /* If move comes from a remote source, animate it.  If it
4975            isn't remote, it will have already been animated. */
4976         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4977             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4978         }
4979         if (!pausing && appData.highlightLastMove) {
4980             SetHighlights(fromX, fromY, toX, toY);
4981         }
4982     }
4983
4984     /* Start the clocks */
4985     whiteFlag = blackFlag = FALSE;
4986     appData.clockMode = !(basetime == 0 && increment == 0);
4987     if (ticking == 0) {
4988       ics_clock_paused = TRUE;
4989       StopClocks();
4990     } else if (ticking == 1) {
4991       ics_clock_paused = FALSE;
4992     }
4993     if (gameMode == IcsIdle ||
4994         relation == RELATION_OBSERVING_STATIC ||
4995         relation == RELATION_EXAMINING ||
4996         ics_clock_paused)
4997       DisplayBothClocks();
4998     else
4999       StartClocks();
5000
5001     /* Display opponents and material strengths */
5002     if (gameInfo.variant != VariantBughouse &&
5003         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5004         if (tinyLayout || smallLayout) {
5005             if(gameInfo.variant == VariantNormal)
5006               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5007                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5008                     basetime, increment);
5009             else
5010               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5011                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5012                     basetime, increment, (int) gameInfo.variant);
5013         } else {
5014             if(gameInfo.variant == VariantNormal)
5015               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5016                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5017                     basetime, increment);
5018             else
5019               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5020                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5021                     basetime, increment, VariantName(gameInfo.variant));
5022         }
5023         DisplayTitle(str);
5024   if (appData.debugMode) {
5025     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5026   }
5027     }
5028
5029
5030     /* Display the board */
5031     if (!pausing && !appData.noGUI) {
5032
5033       if (appData.premove)
5034           if (!gotPremove ||
5035              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5036              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5037               ClearPremoveHighlights();
5038
5039       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5040         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5041       DrawPosition(j, boards[currentMove]);
5042
5043       DisplayMove(moveNum - 1);
5044       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5045             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5046               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5047         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5048       }
5049     }
5050
5051     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5052 #if ZIPPY
5053     if(bookHit) { // [HGM] book: simulate book reply
5054         static char bookMove[MSG_SIZ]; // a bit generous?
5055
5056         programStats.nodes = programStats.depth = programStats.time =
5057         programStats.score = programStats.got_only_move = 0;
5058         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5059
5060         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5061         strcat(bookMove, bookHit);
5062         HandleMachineMove(bookMove, &first);
5063     }
5064 #endif
5065 }
5066
5067 void
5068 GetMoveListEvent ()
5069 {
5070     char buf[MSG_SIZ];
5071     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5072         ics_getting_history = H_REQUESTED;
5073         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5074         SendToICS(buf);
5075     }
5076 }
5077
5078 void
5079 SendToBoth (char *msg)
5080 {   // to make it easy to keep two engines in step in dual analysis
5081     SendToProgram(msg, &first);
5082     if(second.analyzing) SendToProgram(msg, &second);
5083 }
5084
5085 void
5086 AnalysisPeriodicEvent (int force)
5087 {
5088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5089          && !force) || !appData.periodicUpdates)
5090       return;
5091
5092     /* Send . command to Crafty to collect stats */
5093     SendToBoth(".\n");
5094
5095     /* Don't send another until we get a response (this makes
5096        us stop sending to old Crafty's which don't understand
5097        the "." command (sending illegal cmds resets node count & time,
5098        which looks bad)) */
5099     programStats.ok_to_send = 0;
5100 }
5101
5102 void
5103 ics_update_width (int new_width)
5104 {
5105         ics_printf("set width %d\n", new_width);
5106 }
5107
5108 void
5109 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5110 {
5111     char buf[MSG_SIZ];
5112
5113     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5114         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5115             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5116             SendToProgram(buf, cps);
5117             return;
5118         }
5119         // null move in variant where engine does not understand it (for analysis purposes)
5120         SendBoard(cps, moveNum + 1); // send position after move in stead.
5121         return;
5122     }
5123     if (cps->useUsermove) {
5124       SendToProgram("usermove ", cps);
5125     }
5126     if (cps->useSAN) {
5127       char *space;
5128       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5129         int len = space - parseList[moveNum];
5130         memcpy(buf, parseList[moveNum], len);
5131         buf[len++] = '\n';
5132         buf[len] = NULLCHAR;
5133       } else {
5134         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5135       }
5136       SendToProgram(buf, cps);
5137     } else {
5138       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5139         AlphaRank(moveList[moveNum], 4);
5140         SendToProgram(moveList[moveNum], cps);
5141         AlphaRank(moveList[moveNum], 4); // and back
5142       } else
5143       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5144        * the engine. It would be nice to have a better way to identify castle
5145        * moves here. */
5146       if(appData.fischerCastling && cps->useOOCastle) {
5147         int fromX = moveList[moveNum][0] - AAA;
5148         int fromY = moveList[moveNum][1] - ONE;
5149         int toX = moveList[moveNum][2] - AAA;
5150         int toY = moveList[moveNum][3] - ONE;
5151         if((boards[moveNum][fromY][fromX] == WhiteKing
5152             && boards[moveNum][toY][toX] == WhiteRook)
5153            || (boards[moveNum][fromY][fromX] == BlackKing
5154                && boards[moveNum][toY][toX] == BlackRook)) {
5155           if(toX > fromX) SendToProgram("O-O\n", cps);
5156           else SendToProgram("O-O-O\n", cps);
5157         }
5158         else SendToProgram(moveList[moveNum], cps);
5159       } else
5160       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5161         char *m = moveList[moveNum];
5162         static char c[2];
5163         *c = m[7]; // promoChar
5164         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
5165           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5166                                                m[2], m[3] - '0',
5167                                                m[5], m[6] - '0',
5168                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5169         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5170           *c = m[9];
5171           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
5172                                                m[7], m[8] - '0',
5173                                                m[7], m[8] - '0',
5174                                                m[5], m[6] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[2], m[3] - '0', c);
5177         } else
5178           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5179                                                m[5], m[6] - '0',
5180                                                m[5], m[6] - '0',
5181                                                m[2], m[3] - '0', c);
5182           SendToProgram(buf, cps);
5183       } else
5184       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5185         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5186           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5187           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5188                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5189         } else
5190           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5191                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192         SendToProgram(buf, cps);
5193       }
5194       else SendToProgram(moveList[moveNum], cps);
5195       /* End of additions by Tord */
5196     }
5197
5198     /* [HGM] setting up the opening has brought engine in force mode! */
5199     /*       Send 'go' if we are in a mode where machine should play. */
5200     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5201         (gameMode == TwoMachinesPlay   ||
5202 #if ZIPPY
5203          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5204 #endif
5205          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5206         SendToProgram("go\n", cps);
5207   if (appData.debugMode) {
5208     fprintf(debugFP, "(extra)\n");
5209   }
5210     }
5211     setboardSpoiledMachineBlack = 0;
5212 }
5213
5214 void
5215 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5216 {
5217     char user_move[MSG_SIZ];
5218     char suffix[4];
5219
5220     if(gameInfo.variant == VariantSChess && promoChar) {
5221         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5222         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5223     } else suffix[0] = NULLCHAR;
5224
5225     switch (moveType) {
5226       default:
5227         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5228                 (int)moveType, fromX, fromY, toX, toY);
5229         DisplayError(user_move + strlen("say "), 0);
5230         break;
5231       case WhiteKingSideCastle:
5232       case BlackKingSideCastle:
5233       case WhiteQueenSideCastleWild:
5234       case BlackQueenSideCastleWild:
5235       /* PUSH Fabien */
5236       case WhiteHSideCastleFR:
5237       case BlackHSideCastleFR:
5238       /* POP Fabien */
5239         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5240         break;
5241       case WhiteQueenSideCastle:
5242       case BlackQueenSideCastle:
5243       case WhiteKingSideCastleWild:
5244       case BlackKingSideCastleWild:
5245       /* PUSH Fabien */
5246       case WhiteASideCastleFR:
5247       case BlackASideCastleFR:
5248       /* POP Fabien */
5249         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5250         break;
5251       case WhiteNonPromotion:
5252       case BlackNonPromotion:
5253         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5254         break;
5255       case WhitePromotion:
5256       case BlackPromotion:
5257         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5258            gameInfo.variant == VariantMakruk)
5259           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5261                 PieceToChar(WhiteFerz));
5262         else if(gameInfo.variant == VariantGreat)
5263           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5264                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5265                 PieceToChar(WhiteMan));
5266         else
5267           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5268                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5269                 promoChar);
5270         break;
5271       case WhiteDrop:
5272       case BlackDrop:
5273       drop:
5274         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5275                  ToUpper(PieceToChar((ChessSquare) fromX)),
5276                  AAA + toX, ONE + toY);
5277         break;
5278       case IllegalMove:  /* could be a variant we don't quite understand */
5279         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5280       case NormalMove:
5281       case WhiteCapturesEnPassant:
5282       case BlackCapturesEnPassant:
5283         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5284                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5285         break;
5286     }
5287     SendToICS(user_move);
5288     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5289         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5290 }
5291
5292 void
5293 UploadGameEvent ()
5294 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5295     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5296     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5297     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5298       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5299       return;
5300     }
5301     if(gameMode != IcsExamining) { // is this ever not the case?
5302         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5303
5304         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5305           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5306         } else { // on FICS we must first go to general examine mode
5307           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5308         }
5309         if(gameInfo.variant != VariantNormal) {
5310             // try figure out wild number, as xboard names are not always valid on ICS
5311             for(i=1; i<=36; i++) {
5312               snprintf(buf, MSG_SIZ, "wild/%d", i);
5313                 if(StringToVariant(buf) == gameInfo.variant) break;
5314             }
5315             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5316             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5317             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5318         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5319         SendToICS(ics_prefix);
5320         SendToICS(buf);
5321         if(startedFromSetupPosition || backwardMostMove != 0) {
5322           fen = PositionToFEN(backwardMostMove, NULL, 1);
5323           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5324             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5325             SendToICS(buf);
5326           } else { // FICS: everything has to set by separate bsetup commands
5327             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5328             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5329             SendToICS(buf);
5330             if(!WhiteOnMove(backwardMostMove)) {
5331                 SendToICS("bsetup tomove black\n");
5332             }
5333             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5334             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5335             SendToICS(buf);
5336             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5337             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5338             SendToICS(buf);
5339             i = boards[backwardMostMove][EP_STATUS];
5340             if(i >= 0) { // set e.p.
5341               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5342                 SendToICS(buf);
5343             }
5344             bsetup++;
5345           }
5346         }
5347       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5348             SendToICS("bsetup done\n"); // switch to normal examining.
5349     }
5350     for(i = backwardMostMove; i<last; i++) {
5351         char buf[20];
5352         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5353         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5354             int len = strlen(moveList[i]);
5355             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5356             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5357         }
5358         SendToICS(buf);
5359     }
5360     SendToICS(ics_prefix);
5361     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5362 }
5363
5364 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5365 int legNr = 1;
5366
5367 void
5368 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5369 {
5370     if (rf == DROP_RANK) {
5371       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5372       sprintf(move, "%c@%c%c\n",
5373                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5374     } else {
5375         if (promoChar == 'x' || promoChar == NULLCHAR) {
5376           sprintf(move, "%c%c%c%c\n",
5377                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5378           if(killX >= 0 && killY >= 0) {
5379             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5380             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5381           }
5382         } else {
5383             sprintf(move, "%c%c%c%c%c\n",
5384                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5385           if(killX >= 0 && killY >= 0) {
5386             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5388           }
5389         }
5390     }
5391 }
5392
5393 void
5394 ProcessICSInitScript (FILE *f)
5395 {
5396     char buf[MSG_SIZ];
5397
5398     while (fgets(buf, MSG_SIZ, f)) {
5399         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5400     }
5401
5402     fclose(f);
5403 }
5404
5405
5406 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5407 int dragging;
5408 static ClickType lastClickType;
5409
5410 int
5411 PieceInString (char *s, ChessSquare piece)
5412 {
5413   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5414   while((p = strchr(s, ID))) {
5415     if(!suffix || p[1] == suffix) return TRUE;
5416     s = p;
5417   }
5418   return FALSE;
5419 }
5420
5421 int
5422 Partner (ChessSquare *p)
5423 { // change piece into promotion partner if one shogi-promotes to the other
5424   ChessSquare partner = promoPartner[*p];
5425   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5426   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5427   *p = partner;
5428   return 1;
5429 }
5430
5431 void
5432 Sweep (int step)
5433 {
5434     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5435     static int toggleFlag;
5436     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5437     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5438     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5439     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5440     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5441     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5442     do {
5443         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5444         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5445         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5446         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5447         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5448         if(!step) step = -1;
5449     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5450             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5451             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5452             promoSweep == pawn ||
5453             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5454             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5455     if(toX >= 0) {
5456         int victim = boards[currentMove][toY][toX];
5457         boards[currentMove][toY][toX] = promoSweep;
5458         DrawPosition(FALSE, boards[currentMove]);
5459         boards[currentMove][toY][toX] = victim;
5460     } else
5461     ChangeDragPiece(promoSweep);
5462 }
5463
5464 int
5465 PromoScroll (int x, int y)
5466 {
5467   int step = 0;
5468
5469   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5470   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5471   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5472   if(!step) return FALSE;
5473   lastX = x; lastY = y;
5474   if((promoSweep < BlackPawn) == flipView) step = -step;
5475   if(step > 0) selectFlag = 1;
5476   if(!selectFlag) Sweep(step);
5477   return FALSE;
5478 }
5479
5480 void
5481 NextPiece (int step)
5482 {
5483     ChessSquare piece = boards[currentMove][toY][toX];
5484     do {
5485         pieceSweep -= step;
5486         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5487         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5488         if(!step) step = -1;
5489     } while(PieceToChar(pieceSweep) == '.');
5490     boards[currentMove][toY][toX] = pieceSweep;
5491     DrawPosition(FALSE, boards[currentMove]);
5492     boards[currentMove][toY][toX] = piece;
5493 }
5494 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5495 void
5496 AlphaRank (char *move, int n)
5497 {
5498 //    char *p = move, c; int x, y;
5499
5500     if (appData.debugMode) {
5501         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5502     }
5503
5504     if(move[1]=='*' &&
5505        move[2]>='0' && move[2]<='9' &&
5506        move[3]>='a' && move[3]<='x'    ) {
5507         move[1] = '@';
5508         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5509         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5510     } else
5511     if(move[0]>='0' && move[0]<='9' &&
5512        move[1]>='a' && move[1]<='x' &&
5513        move[2]>='0' && move[2]<='9' &&
5514        move[3]>='a' && move[3]<='x'    ) {
5515         /* input move, Shogi -> normal */
5516         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5517         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5518         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5519         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5520     } else
5521     if(move[1]=='@' &&
5522        move[3]>='0' && move[3]<='9' &&
5523        move[2]>='a' && move[2]<='x'    ) {
5524         move[1] = '*';
5525         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5526         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5527     } else
5528     if(
5529        move[0]>='a' && move[0]<='x' &&
5530        move[3]>='0' && move[3]<='9' &&
5531        move[2]>='a' && move[2]<='x'    ) {
5532          /* output move, normal -> Shogi */
5533         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5534         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5535         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5536         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5537         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5538     }
5539     if (appData.debugMode) {
5540         fprintf(debugFP, "   out = '%s'\n", move);
5541     }
5542 }
5543
5544 char yy_textstr[8000];
5545
5546 /* Parser for moves from gnuchess, ICS, or user typein box */
5547 Boolean
5548 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5549 {
5550     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5551
5552     switch (*moveType) {
5553       case WhitePromotion:
5554       case BlackPromotion:
5555       case WhiteNonPromotion:
5556       case BlackNonPromotion:
5557       case NormalMove:
5558       case FirstLeg:
5559       case WhiteCapturesEnPassant:
5560       case BlackCapturesEnPassant:
5561       case WhiteKingSideCastle:
5562       case WhiteQueenSideCastle:
5563       case BlackKingSideCastle:
5564       case BlackQueenSideCastle:
5565       case WhiteKingSideCastleWild:
5566       case WhiteQueenSideCastleWild:
5567       case BlackKingSideCastleWild:
5568       case BlackQueenSideCastleWild:
5569       /* Code added by Tord: */
5570       case WhiteHSideCastleFR:
5571       case WhiteASideCastleFR:
5572       case BlackHSideCastleFR:
5573       case BlackASideCastleFR:
5574       /* End of code added by Tord */
5575       case IllegalMove:         /* bug or odd chess variant */
5576         if(currentMoveString[1] == '@') { // illegal drop
5577           *fromX = WhiteOnMove(moveNum) ?
5578             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579             (int) CharToPiece(ToLower(currentMoveString[0]));
5580           goto drop;
5581         }
5582         *fromX = currentMoveString[0] - AAA;
5583         *fromY = currentMoveString[1] - ONE;
5584         *toX = currentMoveString[2] - AAA;
5585         *toY = currentMoveString[3] - ONE;
5586         *promoChar = currentMoveString[4];
5587         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5588         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5589             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5590     if (appData.debugMode) {
5591         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5592     }
5593             *fromX = *fromY = *toX = *toY = 0;
5594             return FALSE;
5595         }
5596         if (appData.testLegality) {
5597           return (*moveType != IllegalMove);
5598         } else {
5599           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5600                          // [HGM] lion: if this is a double move we are less critical
5601                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5602         }
5603
5604       case WhiteDrop:
5605       case BlackDrop:
5606         *fromX = *moveType == WhiteDrop ?
5607           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5608           (int) CharToPiece(ToLower(currentMoveString[0]));
5609       drop:
5610         *fromY = DROP_RANK;
5611         *toX = currentMoveString[2] - AAA;
5612         *toY = currentMoveString[3] - ONE;
5613         *promoChar = NULLCHAR;
5614         return TRUE;
5615
5616       case AmbiguousMove:
5617       case ImpossibleMove:
5618       case EndOfFile:
5619       case ElapsedTime:
5620       case Comment:
5621       case PGNTag:
5622       case NAG:
5623       case WhiteWins:
5624       case BlackWins:
5625       case GameIsDrawn:
5626       default:
5627     if (appData.debugMode) {
5628         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5629     }
5630         /* bug? */
5631         *fromX = *fromY = *toX = *toY = 0;
5632         *promoChar = NULLCHAR;
5633         return FALSE;
5634     }
5635 }
5636
5637 Boolean pushed = FALSE;
5638 char *lastParseAttempt;
5639
5640 void
5641 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5642 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5643   int fromX, fromY, toX, toY; char promoChar;
5644   ChessMove moveType;
5645   Boolean valid;
5646   int nr = 0;
5647
5648   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5649   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5650     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5651     pushed = TRUE;
5652   }
5653   endPV = forwardMostMove;
5654   do {
5655     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5656     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5657     lastParseAttempt = pv;
5658     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5659     if(!valid && nr == 0 &&
5660        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5661         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5662         // Hande case where played move is different from leading PV move
5663         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5664         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5665         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5666         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5667           endPV += 2; // if position different, keep this
5668           moveList[endPV-1][0] = fromX + AAA;
5669           moveList[endPV-1][1] = fromY + ONE;
5670           moveList[endPV-1][2] = toX + AAA;
5671           moveList[endPV-1][3] = toY + ONE;
5672           parseList[endPV-1][0] = NULLCHAR;
5673           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5674         }
5675       }
5676     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5677     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5678     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5679     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5680         valid++; // allow comments in PV
5681         continue;
5682     }
5683     nr++;
5684     if(endPV+1 > framePtr) break; // no space, truncate
5685     if(!valid) break;
5686     endPV++;
5687     CopyBoard(boards[endPV], boards[endPV-1]);
5688     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5689     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5690     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5691     CoordsToAlgebraic(boards[endPV - 1],
5692                              PosFlags(endPV - 1),
5693                              fromY, fromX, toY, toX, promoChar,
5694                              parseList[endPV - 1]);
5695   } while(valid);
5696   if(atEnd == 2) return; // used hidden, for PV conversion
5697   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5698   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5699   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5700                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5701   DrawPosition(TRUE, boards[currentMove]);
5702 }
5703
5704 int
5705 MultiPV (ChessProgramState *cps, int kind)
5706 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5707         int i;
5708         for(i=0; i<cps->nrOptions; i++) {
5709             char *s = cps->option[i].name;
5710             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5711             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5712                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5713         }
5714         return -1;
5715 }
5716
5717 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5718 static int multi, pv_margin;
5719 static ChessProgramState *activeCps;
5720
5721 Boolean
5722 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5723 {
5724         int startPV, lineStart, origIndex = index;
5725         char *p, buf2[MSG_SIZ];
5726         ChessProgramState *cps = (pane ? &second : &first);
5727
5728         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5729         lastX = x; lastY = y;
5730         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5731         lineStart = startPV = index;
5732         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5733         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5734         index = startPV;
5735         do{ while(buf[index] && buf[index] != '\n') index++;
5736         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5737         buf[index] = 0;
5738         if(lineStart == 0 && gameMode == AnalyzeMode) {
5739             int n = 0;
5740             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5741             if(n == 0) { // click not on "fewer" or "more"
5742                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5743                     pv_margin = cps->option[multi].value;
5744                     activeCps = cps; // non-null signals margin adjustment
5745                 }
5746             } else if((multi = MultiPV(cps, 1)) >= 0) {
5747                 n += cps->option[multi].value; if(n < 1) n = 1;
5748                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5749                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5750                 cps->option[multi].value = n;
5751                 *start = *end = 0;
5752                 return FALSE;
5753             }
5754         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5755                 ExcludeClick(origIndex - lineStart);
5756                 return FALSE;
5757         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5758                 Collapse(origIndex - lineStart);
5759                 return FALSE;
5760         }
5761         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5762         *start = startPV; *end = index-1;
5763         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5764         return TRUE;
5765 }
5766
5767 char *
5768 PvToSAN (char *pv)
5769 {
5770         static char buf[10*MSG_SIZ];
5771         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5772         *buf = NULLCHAR;
5773         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5774         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5775         for(i = forwardMostMove; i<endPV; i++){
5776             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5777             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5778             k += strlen(buf+k);
5779         }
5780         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5781         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5782         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5783         endPV = savedEnd;
5784         return buf;
5785 }
5786
5787 Boolean
5788 LoadPV (int x, int y)
5789 { // called on right mouse click to load PV
5790   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5791   lastX = x; lastY = y;
5792   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5793   extendGame = FALSE;
5794   return TRUE;
5795 }
5796
5797 void
5798 UnLoadPV ()
5799 {
5800   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5801   if(activeCps) {
5802     if(pv_margin != activeCps->option[multi].value) {
5803       char buf[MSG_SIZ];
5804       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5805       SendToProgram(buf, activeCps);
5806       activeCps->option[multi].value = pv_margin;
5807     }
5808     activeCps = NULL;
5809     return;
5810   }
5811   if(endPV < 0) return;
5812   if(appData.autoCopyPV) CopyFENToClipboard();
5813   endPV = -1;
5814   if(extendGame && currentMove > forwardMostMove) {
5815         Boolean saveAnimate = appData.animate;
5816         if(pushed) {
5817             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5818                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5819             } else storedGames--; // abandon shelved tail of original game
5820         }
5821         pushed = FALSE;
5822         forwardMostMove = currentMove;
5823         currentMove = oldFMM;
5824         appData.animate = FALSE;
5825         ToNrEvent(forwardMostMove);
5826         appData.animate = saveAnimate;
5827   }
5828   currentMove = forwardMostMove;
5829   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5830   ClearPremoveHighlights();
5831   DrawPosition(TRUE, boards[currentMove]);
5832 }
5833
5834 void
5835 MovePV (int x, int y, int h)
5836 { // step through PV based on mouse coordinates (called on mouse move)
5837   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5838
5839   if(activeCps) { // adjusting engine's multi-pv margin
5840     if(x > lastX) pv_margin++; else
5841     if(x < lastX) pv_margin -= (pv_margin > 0);
5842     if(x != lastX) {
5843       char buf[MSG_SIZ];
5844       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5845       DisplayMessage(buf, "");
5846     }
5847     lastX = x;
5848     return;
5849   }
5850   // we must somehow check if right button is still down (might be released off board!)
5851   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5852   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5853   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5854   if(!step) return;
5855   lastX = x; lastY = y;
5856
5857   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5858   if(endPV < 0) return;
5859   if(y < margin) step = 1; else
5860   if(y > h - margin) step = -1;
5861   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5862   currentMove += step;
5863   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5864   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5865                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5866   DrawPosition(FALSE, boards[currentMove]);
5867 }
5868
5869
5870 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5871 // All positions will have equal probability, but the current method will not provide a unique
5872 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5873 #define DARK 1
5874 #define LITE 2
5875 #define ANY 3
5876
5877 int squaresLeft[4];
5878 int piecesLeft[(int)BlackPawn];
5879 int seed, nrOfShuffles;
5880
5881 void
5882 GetPositionNumber ()
5883 {       // sets global variable seed
5884         int i;
5885
5886         seed = appData.defaultFrcPosition;
5887         if(seed < 0) { // randomize based on time for negative FRC position numbers
5888                 for(i=0; i<50; i++) seed += random();
5889                 seed = random() ^ random() >> 8 ^ random() << 8;
5890                 if(seed<0) seed = -seed;
5891         }
5892 }
5893
5894 int
5895 put (Board board, int pieceType, int rank, int n, int shade)
5896 // put the piece on the (n-1)-th empty squares of the given shade
5897 {
5898         int i;
5899
5900         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5901                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5902                         board[rank][i] = (ChessSquare) pieceType;
5903                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5904                         squaresLeft[ANY]--;
5905                         piecesLeft[pieceType]--;
5906                         return i;
5907                 }
5908         }
5909         return -1;
5910 }
5911
5912
5913 void
5914 AddOnePiece (Board board, int pieceType, int rank, int shade)
5915 // calculate where the next piece goes, (any empty square), and put it there
5916 {
5917         int i;
5918
5919         i = seed % squaresLeft[shade];
5920         nrOfShuffles *= squaresLeft[shade];
5921         seed /= squaresLeft[shade];
5922         put(board, pieceType, rank, i, shade);
5923 }
5924
5925 void
5926 AddTwoPieces (Board board, int pieceType, int rank)
5927 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5928 {
5929         int i, n=squaresLeft[ANY], j=n-1, k;
5930
5931         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5932         i = seed % k;  // pick one
5933         nrOfShuffles *= k;
5934         seed /= k;
5935         while(i >= j) i -= j--;
5936         j = n - 1 - j; i += j;
5937         put(board, pieceType, rank, j, ANY);
5938         put(board, pieceType, rank, i, ANY);
5939 }
5940
5941 void
5942 SetUpShuffle (Board board, int number)
5943 {
5944         int i, p, first=1;
5945
5946         GetPositionNumber(); nrOfShuffles = 1;
5947
5948         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5949         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5950         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5951
5952         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5953
5954         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5955             p = (int) board[0][i];
5956             if(p < (int) BlackPawn) piecesLeft[p] ++;
5957             board[0][i] = EmptySquare;
5958         }
5959
5960         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5961             // shuffles restricted to allow normal castling put KRR first
5962             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5963                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5964             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5965                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5966             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5967                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5968             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5969                 put(board, WhiteRook, 0, 0, ANY);
5970             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5971         }
5972
5973         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5974             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5975             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5976                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5977                 while(piecesLeft[p] >= 2) {
5978                     AddOnePiece(board, p, 0, LITE);
5979                     AddOnePiece(board, p, 0, DARK);
5980                 }
5981                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5982             }
5983
5984         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5985             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5986             // but we leave King and Rooks for last, to possibly obey FRC restriction
5987             if(p == (int)WhiteRook) continue;
5988             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5989             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5990         }
5991
5992         // now everything is placed, except perhaps King (Unicorn) and Rooks
5993
5994         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5995             // Last King gets castling rights
5996             while(piecesLeft[(int)WhiteUnicorn]) {
5997                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5999             }
6000
6001             while(piecesLeft[(int)WhiteKing]) {
6002                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6003                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6004             }
6005
6006
6007         } else {
6008             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6009             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6010         }
6011
6012         // Only Rooks can be left; simply place them all
6013         while(piecesLeft[(int)WhiteRook]) {
6014                 i = put(board, WhiteRook, 0, 0, ANY);
6015                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6016                         if(first) {
6017                                 first=0;
6018                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6019                         }
6020                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6021                 }
6022         }
6023         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6024             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6025         }
6026
6027         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6028 }
6029
6030 int
6031 ptclen (const char *s, char *escapes)
6032 {
6033     int n = 0;
6034     if(!*escapes) return strlen(s);
6035     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6036     return n;
6037 }
6038
6039 int
6040 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6041 /* [HGM] moved here from winboard.c because of its general usefulness */
6042 /*       Basically a safe strcpy that uses the last character as King */
6043 {
6044     int result = FALSE; int NrPieces;
6045     unsigned char partner[EmptySquare];
6046
6047     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6048                     && NrPieces >= 12 && !(NrPieces&1)) {
6049         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6050
6051         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6052         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6053             char *p, c=0;
6054             if(map[j] == '/') offs = WhitePBishop - i, j++;
6055             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6056             table[i+offs] = map[j++];
6057             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6058             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6059             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6060         }
6061         table[(int) WhiteKing]  = map[j++];
6062         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6063             char *p, c=0;
6064             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6065             i = WHITE_TO_BLACK ii;
6066             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6067             table[i+offs] = map[j++];
6068             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6069             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6070             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6071         }
6072         table[(int) BlackKing]  = map[j++];
6073
6074
6075         if(*escapes) { // set up promotion pairing
6076             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6077             // pieceToChar entirely filled, so we can look up specified partners
6078             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6079                 int c = table[i];
6080                 if(c == '^' || c == '-') { // has specified partner
6081                     int p;
6082                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6083                     if(c == '^') table[i] = '+';
6084                     if(p < EmptySquare) {
6085                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6086                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6087                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6088                     }
6089                 } else if(c == '*') {
6090                     table[i] = partner[i];
6091                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6092                 }
6093             }
6094         }
6095
6096         result = TRUE;
6097     }
6098
6099     return result;
6100 }
6101
6102 int
6103 SetCharTable (unsigned char *table, const char * map)
6104 {
6105     return SetCharTableEsc(table, map, "");
6106 }
6107
6108 void
6109 Prelude (Board board)
6110 {       // [HGM] superchess: random selection of exo-pieces
6111         int i, j, k; ChessSquare p;
6112         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6113
6114         GetPositionNumber(); // use FRC position number
6115
6116         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6117             SetCharTable(pieceToChar, appData.pieceToCharTable);
6118             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6119                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6120         }
6121
6122         j = seed%4;                 seed /= 4;
6123         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6124         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6125         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6126         j = seed%3 + (seed%3 >= j); seed /= 3;
6127         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6128         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6129         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6130         j = seed%3;                 seed /= 3;
6131         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6132         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6133         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6134         j = seed%2 + (seed%2 >= j); seed /= 2;
6135         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6136         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6137         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6138         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6139         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6140         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6141         put(board, exoPieces[0],    0, 0, ANY);
6142         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6143 }
6144
6145 void
6146 InitPosition (int redraw)
6147 {
6148     ChessSquare (* pieces)[BOARD_FILES];
6149     int i, j, pawnRow=1, pieceRows=1, overrule,
6150     oldx = gameInfo.boardWidth,
6151     oldy = gameInfo.boardHeight,
6152     oldh = gameInfo.holdingsWidth;
6153     static int oldv;
6154
6155     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6156
6157     /* [AS] Initialize pv info list [HGM] and game status */
6158     {
6159         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6160             pvInfoList[i].depth = 0;
6161             boards[i][EP_STATUS] = EP_NONE;
6162             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6163         }
6164
6165         initialRulePlies = 0; /* 50-move counter start */
6166
6167         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6168         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6169     }
6170
6171
6172     /* [HGM] logic here is completely changed. In stead of full positions */
6173     /* the initialized data only consist of the two backranks. The switch */
6174     /* selects which one we will use, which is than copied to the Board   */
6175     /* initialPosition, which for the rest is initialized by Pawns and    */
6176     /* empty squares. This initial position is then copied to boards[0],  */
6177     /* possibly after shuffling, so that it remains available.            */
6178
6179     gameInfo.holdingsWidth = 0; /* default board sizes */
6180     gameInfo.boardWidth    = 8;
6181     gameInfo.boardHeight   = 8;
6182     gameInfo.holdingsSize  = 0;
6183     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6184     for(i=0; i<BOARD_FILES-6; i++)
6185       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6186     initialPosition[EP_STATUS] = EP_NONE;
6187     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6188     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6189     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6190          SetCharTable(pieceNickName, appData.pieceNickNames);
6191     else SetCharTable(pieceNickName, "............");
6192     pieces = FIDEArray;
6193
6194     switch (gameInfo.variant) {
6195     case VariantFischeRandom:
6196       shuffleOpenings = TRUE;
6197       appData.fischerCastling = TRUE;
6198     default:
6199       break;
6200     case VariantShatranj:
6201       pieces = ShatranjArray;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6204       break;
6205     case VariantMakruk:
6206       pieces = makrukArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6209       break;
6210     case VariantASEAN:
6211       pieces = aseanArray;
6212       nrCastlingRights = 0;
6213       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6214       break;
6215     case VariantTwoKings:
6216       pieces = twoKingsArray;
6217       break;
6218     case VariantGrand:
6219       pieces = GrandArray;
6220       nrCastlingRights = 0;
6221       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6222       gameInfo.boardWidth = 10;
6223       gameInfo.boardHeight = 10;
6224       gameInfo.holdingsSize = 7;
6225       break;
6226     case VariantCapaRandom:
6227       shuffleOpenings = TRUE;
6228       appData.fischerCastling = TRUE;
6229     case VariantCapablanca:
6230       pieces = CapablancaArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6233       break;
6234     case VariantGothic:
6235       pieces = GothicArray;
6236       gameInfo.boardWidth = 10;
6237       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6238       break;
6239     case VariantSChess:
6240       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6241       gameInfo.holdingsSize = 7;
6242       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6243       break;
6244     case VariantJanus:
6245       pieces = JanusArray;
6246       gameInfo.boardWidth = 10;
6247       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6248       nrCastlingRights = 6;
6249         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6250         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6251         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6252         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6253         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6254         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6255       break;
6256     case VariantFalcon:
6257       pieces = FalconArray;
6258       gameInfo.boardWidth = 10;
6259       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6260       break;
6261     case VariantXiangqi:
6262       pieces = XiangqiArray;
6263       gameInfo.boardWidth  = 9;
6264       gameInfo.boardHeight = 10;
6265       nrCastlingRights = 0;
6266       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6267       break;
6268     case VariantShogi:
6269       pieces = ShogiArray;
6270       gameInfo.boardWidth  = 9;
6271       gameInfo.boardHeight = 9;
6272       gameInfo.holdingsSize = 7;
6273       nrCastlingRights = 0;
6274       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6275       break;
6276     case VariantChu:
6277       pieces = ChuArray; pieceRows = 3;
6278       gameInfo.boardWidth  = 12;
6279       gameInfo.boardHeight = 12;
6280       nrCastlingRights = 0;
6281       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6282                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6283       break;
6284     case VariantCourier:
6285       pieces = CourierArray;
6286       gameInfo.boardWidth  = 12;
6287       nrCastlingRights = 0;
6288       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6289       break;
6290     case VariantKnightmate:
6291       pieces = KnightmateArray;
6292       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6293       break;
6294     case VariantSpartan:
6295       pieces = SpartanArray;
6296       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6297       break;
6298     case VariantLion:
6299       pieces = lionArray;
6300       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6301       break;
6302     case VariantChuChess:
6303       pieces = ChuChessArray;
6304       gameInfo.boardWidth = 10;
6305       gameInfo.boardHeight = 10;
6306       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6307       break;
6308     case VariantFairy:
6309       pieces = fairyArray;
6310       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6311       break;
6312     case VariantGreat:
6313       pieces = GreatArray;
6314       gameInfo.boardWidth = 10;
6315       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6316       gameInfo.holdingsSize = 8;
6317       break;
6318     case VariantSuper:
6319       pieces = FIDEArray;
6320       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6321       gameInfo.holdingsSize = 8;
6322       startedFromSetupPosition = TRUE;
6323       break;
6324     case VariantCrazyhouse:
6325     case VariantBughouse:
6326       pieces = FIDEArray;
6327       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6328       gameInfo.holdingsSize = 5;
6329       break;
6330     case VariantWildCastle:
6331       pieces = FIDEArray;
6332       /* !!?shuffle with kings guaranteed to be on d or e file */
6333       shuffleOpenings = 1;
6334       break;
6335     case VariantNoCastle:
6336       pieces = FIDEArray;
6337       nrCastlingRights = 0;
6338       /* !!?unconstrained back-rank shuffle */
6339       shuffleOpenings = 1;
6340       break;
6341     }
6342
6343     overrule = 0;
6344     if(appData.NrFiles >= 0) {
6345         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6346         gameInfo.boardWidth = appData.NrFiles;
6347     }
6348     if(appData.NrRanks >= 0) {
6349         gameInfo.boardHeight = appData.NrRanks;
6350     }
6351     if(appData.holdingsSize >= 0) {
6352         i = appData.holdingsSize;
6353         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6354         gameInfo.holdingsSize = i;
6355     }
6356     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6357     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6358         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6359
6360     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6361     if(pawnRow < 1) pawnRow = 1;
6362     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6363        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6364     if(gameInfo.variant == VariantChu) pawnRow = 3;
6365
6366     /* User pieceToChar list overrules defaults */
6367     if(appData.pieceToCharTable != NULL)
6368         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6369
6370     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6371
6372         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6373             s = (ChessSquare) 0; /* account holding counts in guard band */
6374         for( i=0; i<BOARD_HEIGHT; i++ )
6375             initialPosition[i][j] = s;
6376
6377         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6378         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6379         initialPosition[pawnRow][j] = WhitePawn;
6380         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6381         if(gameInfo.variant == VariantXiangqi) {
6382             if(j&1) {
6383                 initialPosition[pawnRow][j] =
6384                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6385                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6386                    initialPosition[2][j] = WhiteCannon;
6387                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6388                 }
6389             }
6390         }
6391         if(gameInfo.variant == VariantChu) {
6392              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6393                initialPosition[pawnRow+1][j] = WhiteCobra,
6394                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6395              for(i=1; i<pieceRows; i++) {
6396                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6397                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6398              }
6399         }
6400         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6401             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6402                initialPosition[0][j] = WhiteRook;
6403                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6404             }
6405         }
6406         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6407     }
6408     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6409     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6410
6411             j=BOARD_LEFT+1;
6412             initialPosition[1][j] = WhiteBishop;
6413             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6414             j=BOARD_RGHT-2;
6415             initialPosition[1][j] = WhiteRook;
6416             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6417     }
6418
6419     if( nrCastlingRights == -1) {
6420         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6421         /*       This sets default castling rights from none to normal corners   */
6422         /* Variants with other castling rights must set them themselves above    */
6423         nrCastlingRights = 6;
6424
6425         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6426         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6427         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6428         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6429         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6430         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6431      }
6432
6433      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6434      if(gameInfo.variant == VariantGreat) { // promotion commoners
6435         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6437         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6439      }
6440      if( gameInfo.variant == VariantSChess ) {
6441       initialPosition[1][0] = BlackMarshall;
6442       initialPosition[2][0] = BlackAngel;
6443       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6444       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6445       initialPosition[1][1] = initialPosition[2][1] =
6446       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6447      }
6448   if (appData.debugMode) {
6449     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6450   }
6451     if(shuffleOpenings) {
6452         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6453         startedFromSetupPosition = TRUE;
6454     }
6455     if(startedFromPositionFile) {
6456       /* [HGM] loadPos: use PositionFile for every new game */
6457       CopyBoard(initialPosition, filePosition);
6458       for(i=0; i<nrCastlingRights; i++)
6459           initialRights[i] = filePosition[CASTLING][i];
6460       startedFromSetupPosition = TRUE;
6461     }
6462
6463     CopyBoard(boards[0], initialPosition);
6464
6465     if(oldx != gameInfo.boardWidth ||
6466        oldy != gameInfo.boardHeight ||
6467        oldv != gameInfo.variant ||
6468        oldh != gameInfo.holdingsWidth
6469                                          )
6470             InitDrawingSizes(-2 ,0);
6471
6472     oldv = gameInfo.variant;
6473     if (redraw)
6474       DrawPosition(TRUE, boards[currentMove]);
6475 }
6476
6477 void
6478 SendBoard (ChessProgramState *cps, int moveNum)
6479 {
6480     char message[MSG_SIZ];
6481
6482     if (cps->useSetboard) {
6483       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6484       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6485       SendToProgram(message, cps);
6486       free(fen);
6487
6488     } else {
6489       ChessSquare *bp;
6490       int i, j, left=0, right=BOARD_WIDTH;
6491       /* Kludge to set black to move, avoiding the troublesome and now
6492        * deprecated "black" command.
6493        */
6494       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6495         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6496
6497       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6498
6499       SendToProgram("edit\n", cps);
6500       SendToProgram("#\n", cps);
6501       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6502         bp = &boards[moveNum][i][left];
6503         for (j = left; j < right; j++, bp++) {
6504           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6505           if ((int) *bp < (int) BlackPawn) {
6506             if(j == BOARD_RGHT+1)
6507                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6508             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6509             if(message[0] == '+' || message[0] == '~') {
6510               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6511                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6512                         AAA + j, ONE + i - '0');
6513             }
6514             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6515                 message[1] = BOARD_RGHT   - 1 - j + '1';
6516                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6517             }
6518             SendToProgram(message, cps);
6519           }
6520         }
6521       }
6522
6523       SendToProgram("c\n", cps);
6524       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6525         bp = &boards[moveNum][i][left];
6526         for (j = left; j < right; j++, bp++) {
6527           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6528           if (((int) *bp != (int) EmptySquare)
6529               && ((int) *bp >= (int) BlackPawn)) {
6530             if(j == BOARD_LEFT-2)
6531                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6532             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6533                     AAA + j, ONE + i - '0');
6534             if(message[0] == '+' || message[0] == '~') {
6535               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6536                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6537                         AAA + j, ONE + i - '0');
6538             }
6539             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6540                 message[1] = BOARD_RGHT   - 1 - j + '1';
6541                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6542             }
6543             SendToProgram(message, cps);
6544           }
6545         }
6546       }
6547
6548       SendToProgram(".\n", cps);
6549     }
6550     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6551 }
6552
6553 char exclusionHeader[MSG_SIZ];
6554 int exCnt, excludePtr;
6555 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6556 static Exclusion excluTab[200];
6557 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6558
6559 static void
6560 WriteMap (int s)
6561 {
6562     int j;
6563     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6564     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6565 }
6566
6567 static void
6568 ClearMap ()
6569 {
6570     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6571     excludePtr = 24; exCnt = 0;
6572     WriteMap(0);
6573 }
6574
6575 static void
6576 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6577 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6578     char buf[2*MOVE_LEN], *p;
6579     Exclusion *e = excluTab;
6580     int i;
6581     for(i=0; i<exCnt; i++)
6582         if(e[i].ff == fromX && e[i].fr == fromY &&
6583            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6584     if(i == exCnt) { // was not in exclude list; add it
6585         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6586         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6587             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6588             return; // abort
6589         }
6590         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6591         excludePtr++; e[i].mark = excludePtr++;
6592         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6593         exCnt++;
6594     }
6595     exclusionHeader[e[i].mark] = state;
6596 }
6597
6598 static int
6599 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6600 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6601     char buf[MSG_SIZ];
6602     int j, k;
6603     ChessMove moveType;
6604     if((signed char)promoChar == -1) { // kludge to indicate best move
6605         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6606             return 1; // if unparsable, abort
6607     }
6608     // update exclusion map (resolving toggle by consulting existing state)
6609     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6610     j = k%8; k >>= 3;
6611     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6612     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6613          excludeMap[k] |=   1<<j;
6614     else excludeMap[k] &= ~(1<<j);
6615     // update header
6616     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6617     // inform engine
6618     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6619     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6620     SendToBoth(buf);
6621     return (state == '+');
6622 }
6623
6624 static void
6625 ExcludeClick (int index)
6626 {
6627     int i, j;
6628     Exclusion *e = excluTab;
6629     if(index < 25) { // none, best or tail clicked
6630         if(index < 13) { // none: include all
6631             WriteMap(0); // clear map
6632             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6633             SendToBoth("include all\n"); // and inform engine
6634         } else if(index > 18) { // tail
6635             if(exclusionHeader[19] == '-') { // tail was excluded
6636                 SendToBoth("include all\n");
6637                 WriteMap(0); // clear map completely
6638                 // now re-exclude selected moves
6639                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6640                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6641             } else { // tail was included or in mixed state
6642                 SendToBoth("exclude all\n");
6643                 WriteMap(0xFF); // fill map completely
6644                 // now re-include selected moves
6645                 j = 0; // count them
6646                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6647                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6648                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6649             }
6650         } else { // best
6651             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6652         }
6653     } else {
6654         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6655             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6656             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6657             break;
6658         }
6659     }
6660 }
6661
6662 ChessSquare
6663 DefaultPromoChoice (int white)
6664 {
6665     ChessSquare result;
6666     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6667        gameInfo.variant == VariantMakruk)
6668         result = WhiteFerz; // no choice
6669     else if(gameInfo.variant == VariantASEAN)
6670         result = WhiteRook; // no choice
6671     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6672         result= WhiteKing; // in Suicide Q is the last thing we want
6673     else if(gameInfo.variant == VariantSpartan)
6674         result = white ? WhiteQueen : WhiteAngel;
6675     else result = WhiteQueen;
6676     if(!white) result = WHITE_TO_BLACK result;
6677     return result;
6678 }
6679
6680 static int autoQueen; // [HGM] oneclick
6681
6682 int
6683 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6684 {
6685     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6686     /* [HGM] add Shogi promotions */
6687     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6688     ChessSquare piece, partner;
6689     ChessMove moveType;
6690     Boolean premove;
6691
6692     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6693     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6694
6695     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6696       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6697         return FALSE;
6698
6699     piece = boards[currentMove][fromY][fromX];
6700     if(gameInfo.variant == VariantChu) {
6701         promotionZoneSize = BOARD_HEIGHT/3;
6702         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6703     } else if(gameInfo.variant == VariantShogi) {
6704         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6705         highestPromotingPiece = (int)WhiteAlfil;
6706     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6707         promotionZoneSize = 3;
6708     }
6709
6710     // Treat Lance as Pawn when it is not representing Amazon or Lance
6711     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6712         if(piece == WhiteLance) piece = WhitePawn; else
6713         if(piece == BlackLance) piece = BlackPawn;
6714     }
6715
6716     // next weed out all moves that do not touch the promotion zone at all
6717     if((int)piece >= BlackPawn) {
6718         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6719              return FALSE;
6720         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6721         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6722     } else {
6723         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6724            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6725         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6726              return FALSE;
6727     }
6728
6729     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6730
6731     // weed out mandatory Shogi promotions
6732     if(gameInfo.variant == VariantShogi) {
6733         if(piece >= BlackPawn) {
6734             if(toY == 0 && piece == BlackPawn ||
6735                toY == 0 && piece == BlackQueen ||
6736                toY <= 1 && piece == BlackKnight) {
6737                 *promoChoice = '+';
6738                 return FALSE;
6739             }
6740         } else {
6741             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6742                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6743                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6744                 *promoChoice = '+';
6745                 return FALSE;
6746             }
6747         }
6748     }
6749
6750     // weed out obviously illegal Pawn moves
6751     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6752         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6753         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6754         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6755         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6756         // note we are not allowed to test for valid (non-)capture, due to premove
6757     }
6758
6759     // we either have a choice what to promote to, or (in Shogi) whether to promote
6760     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6761        gameInfo.variant == VariantMakruk) {
6762         ChessSquare p=BlackFerz;  // no choice
6763         while(p < EmptySquare) {  //but make sure we use piece that exists
6764             *promoChoice = PieceToChar(p++);
6765             if(*promoChoice != '.') break;
6766         }
6767         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6768     }
6769     // no sense asking what we must promote to if it is going to explode...
6770     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6771         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6772         return FALSE;
6773     }
6774     // give caller the default choice even if we will not make it
6775     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6776     partner = piece; // pieces can promote if the pieceToCharTable says so
6777     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6778     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6779     if(        sweepSelect && gameInfo.variant != VariantGreat
6780                            && gameInfo.variant != VariantGrand
6781                            && gameInfo.variant != VariantSuper) return FALSE;
6782     if(autoQueen) return FALSE; // predetermined
6783
6784     // suppress promotion popup on illegal moves that are not premoves
6785     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6786               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6787     if(appData.testLegality && !premove) {
6788         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6789                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6790         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6791         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6792             return FALSE;
6793     }
6794
6795     return TRUE;
6796 }
6797
6798 int
6799 InPalace (int row, int column)
6800 {   /* [HGM] for Xiangqi */
6801     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6802          column < (BOARD_WIDTH + 4)/2 &&
6803          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6804     return FALSE;
6805 }
6806
6807 int
6808 PieceForSquare (int x, int y)
6809 {
6810   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6811      return -1;
6812   else
6813      return boards[currentMove][y][x];
6814 }
6815
6816 int
6817 OKToStartUserMove (int x, int y)
6818 {
6819     ChessSquare from_piece;
6820     int white_piece;
6821
6822     if (matchMode) return FALSE;
6823     if (gameMode == EditPosition) return TRUE;
6824
6825     if (x >= 0 && y >= 0)
6826       from_piece = boards[currentMove][y][x];
6827     else
6828       from_piece = EmptySquare;
6829
6830     if (from_piece == EmptySquare) return FALSE;
6831
6832     white_piece = (int)from_piece >= (int)WhitePawn &&
6833       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6834
6835     switch (gameMode) {
6836       case AnalyzeFile:
6837       case TwoMachinesPlay:
6838       case EndOfGame:
6839         return FALSE;
6840
6841       case IcsObserving:
6842       case IcsIdle:
6843         return FALSE;
6844
6845       case MachinePlaysWhite:
6846       case IcsPlayingBlack:
6847         if (appData.zippyPlay) return FALSE;
6848         if (white_piece) {
6849             DisplayMoveError(_("You are playing Black"));
6850             return FALSE;
6851         }
6852         break;
6853
6854       case MachinePlaysBlack:
6855       case IcsPlayingWhite:
6856         if (appData.zippyPlay) return FALSE;
6857         if (!white_piece) {
6858             DisplayMoveError(_("You are playing White"));
6859             return FALSE;
6860         }
6861         break;
6862
6863       case PlayFromGameFile:
6864             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6865       case EditGame:
6866       case AnalyzeMode:
6867         if (!white_piece && WhiteOnMove(currentMove)) {
6868             DisplayMoveError(_("It is White's turn"));
6869             return FALSE;
6870         }
6871         if (white_piece && !WhiteOnMove(currentMove)) {
6872             DisplayMoveError(_("It is Black's turn"));
6873             return FALSE;
6874         }
6875         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6876             /* Editing correspondence game history */
6877             /* Could disallow this or prompt for confirmation */
6878             cmailOldMove = -1;
6879         }
6880         break;
6881
6882       case BeginningOfGame:
6883         if (appData.icsActive) return FALSE;
6884         if (!appData.noChessProgram) {
6885             if (!white_piece) {
6886                 DisplayMoveError(_("You are playing White"));
6887                 return FALSE;
6888             }
6889         }
6890         break;
6891
6892       case Training:
6893         if (!white_piece && WhiteOnMove(currentMove)) {
6894             DisplayMoveError(_("It is White's turn"));
6895             return FALSE;
6896         }
6897         if (white_piece && !WhiteOnMove(currentMove)) {
6898             DisplayMoveError(_("It is Black's turn"));
6899             return FALSE;
6900         }
6901         break;
6902
6903       default:
6904       case IcsExamining:
6905         break;
6906     }
6907     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6908         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6909         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6910         && gameMode != AnalyzeFile && gameMode != Training) {
6911         DisplayMoveError(_("Displayed position is not current"));
6912         return FALSE;
6913     }
6914     return TRUE;
6915 }
6916
6917 Boolean
6918 OnlyMove (int *x, int *y, Boolean captures)
6919 {
6920     DisambiguateClosure cl;
6921     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6922     switch(gameMode) {
6923       case MachinePlaysBlack:
6924       case IcsPlayingWhite:
6925       case BeginningOfGame:
6926         if(!WhiteOnMove(currentMove)) return FALSE;
6927         break;
6928       case MachinePlaysWhite:
6929       case IcsPlayingBlack:
6930         if(WhiteOnMove(currentMove)) return FALSE;
6931         break;
6932       case EditGame:
6933         break;
6934       default:
6935         return FALSE;
6936     }
6937     cl.pieceIn = EmptySquare;
6938     cl.rfIn = *y;
6939     cl.ffIn = *x;
6940     cl.rtIn = -1;
6941     cl.ftIn = -1;
6942     cl.promoCharIn = NULLCHAR;
6943     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6944     if( cl.kind == NormalMove ||
6945         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6946         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6947         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6948       fromX = cl.ff;
6949       fromY = cl.rf;
6950       *x = cl.ft;
6951       *y = cl.rt;
6952       return TRUE;
6953     }
6954     if(cl.kind != ImpossibleMove) return FALSE;
6955     cl.pieceIn = EmptySquare;
6956     cl.rfIn = -1;
6957     cl.ffIn = -1;
6958     cl.rtIn = *y;
6959     cl.ftIn = *x;
6960     cl.promoCharIn = NULLCHAR;
6961     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6962     if( cl.kind == NormalMove ||
6963         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6964         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6965         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6966       fromX = cl.ff;
6967       fromY = cl.rf;
6968       *x = cl.ft;
6969       *y = cl.rt;
6970       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6971       return TRUE;
6972     }
6973     return FALSE;
6974 }
6975
6976 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6977 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6978 int lastLoadGameUseList = FALSE;
6979 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6980 ChessMove lastLoadGameStart = EndOfFile;
6981 int doubleClick;
6982 Boolean addToBookFlag;
6983
6984 void
6985 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6986 {
6987     ChessMove moveType;
6988     ChessSquare pup;
6989     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6990
6991     /* Check if the user is playing in turn.  This is complicated because we
6992        let the user "pick up" a piece before it is his turn.  So the piece he
6993        tried to pick up may have been captured by the time he puts it down!
6994        Therefore we use the color the user is supposed to be playing in this
6995        test, not the color of the piece that is currently on the starting
6996        square---except in EditGame mode, where the user is playing both
6997        sides; fortunately there the capture race can't happen.  (It can
6998        now happen in IcsExamining mode, but that's just too bad.  The user
6999        will get a somewhat confusing message in that case.)
7000        */
7001
7002     switch (gameMode) {
7003       case AnalyzeFile:
7004       case TwoMachinesPlay:
7005       case EndOfGame:
7006       case IcsObserving:
7007       case IcsIdle:
7008         /* We switched into a game mode where moves are not accepted,
7009            perhaps while the mouse button was down. */
7010         return;
7011
7012       case MachinePlaysWhite:
7013         /* User is moving for Black */
7014         if (WhiteOnMove(currentMove)) {
7015             DisplayMoveError(_("It is White's turn"));
7016             return;
7017         }
7018         break;
7019
7020       case MachinePlaysBlack:
7021         /* User is moving for White */
7022         if (!WhiteOnMove(currentMove)) {
7023             DisplayMoveError(_("It is Black's turn"));
7024             return;
7025         }
7026         break;
7027
7028       case PlayFromGameFile:
7029             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7030       case EditGame:
7031       case IcsExamining:
7032       case BeginningOfGame:
7033       case AnalyzeMode:
7034       case Training:
7035         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7036         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7037             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7038             /* User is moving for Black */
7039             if (WhiteOnMove(currentMove)) {
7040                 DisplayMoveError(_("It is White's turn"));
7041                 return;
7042             }
7043         } else {
7044             /* User is moving for White */
7045             if (!WhiteOnMove(currentMove)) {
7046                 DisplayMoveError(_("It is Black's turn"));
7047                 return;
7048             }
7049         }
7050         break;
7051
7052       case IcsPlayingBlack:
7053         /* User is moving for Black */
7054         if (WhiteOnMove(currentMove)) {
7055             if (!appData.premove) {
7056                 DisplayMoveError(_("It is White's turn"));
7057             } else if (toX >= 0 && toY >= 0) {
7058                 premoveToX = toX;
7059                 premoveToY = toY;
7060                 premoveFromX = fromX;
7061                 premoveFromY = fromY;
7062                 premovePromoChar = promoChar;
7063                 gotPremove = 1;
7064                 if (appData.debugMode)
7065                     fprintf(debugFP, "Got premove: fromX %d,"
7066                             "fromY %d, toX %d, toY %d\n",
7067                             fromX, fromY, toX, toY);
7068             }
7069             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7070             return;
7071         }
7072         break;
7073
7074       case IcsPlayingWhite:
7075         /* User is moving for White */
7076         if (!WhiteOnMove(currentMove)) {
7077             if (!appData.premove) {
7078                 DisplayMoveError(_("It is Black's turn"));
7079             } else if (toX >= 0 && toY >= 0) {
7080                 premoveToX = toX;
7081                 premoveToY = toY;
7082                 premoveFromX = fromX;
7083                 premoveFromY = fromY;
7084                 premovePromoChar = promoChar;
7085                 gotPremove = 1;
7086                 if (appData.debugMode)
7087                     fprintf(debugFP, "Got premove: fromX %d,"
7088                             "fromY %d, toX %d, toY %d\n",
7089                             fromX, fromY, toX, toY);
7090             }
7091             DrawPosition(TRUE, boards[currentMove]);
7092             return;
7093         }
7094         break;
7095
7096       default:
7097         break;
7098
7099       case EditPosition:
7100         /* EditPosition, empty square, or different color piece;
7101            click-click move is possible */
7102         if (toX == -2 || toY == -2) {
7103             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7104             DrawPosition(FALSE, boards[currentMove]);
7105             return;
7106         } else if (toX >= 0 && toY >= 0) {
7107             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7108                 ChessSquare p = boards[0][rf][ff];
7109                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7110                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7111             }
7112             boards[0][toY][toX] = boards[0][fromY][fromX];
7113             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7114                 if(boards[0][fromY][0] != EmptySquare) {
7115                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7116                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7117                 }
7118             } else
7119             if(fromX == BOARD_RGHT+1) {
7120                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7121                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7122                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7123                 }
7124             } else
7125             boards[0][fromY][fromX] = gatingPiece;
7126             ClearHighlights();
7127             DrawPosition(FALSE, boards[currentMove]);
7128             return;
7129         }
7130         return;
7131     }
7132
7133     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7134     pup = boards[currentMove][toY][toX];
7135
7136     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7137     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7138          if( pup != EmptySquare ) return;
7139          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7140            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7141                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7142            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7143            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7144            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7145            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7146          fromY = DROP_RANK;
7147     }
7148
7149     /* [HGM] always test for legality, to get promotion info */
7150     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7151                                          fromY, fromX, toY, toX, promoChar);
7152
7153     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7154
7155     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7156
7157     /* [HGM] but possibly ignore an IllegalMove result */
7158     if (appData.testLegality) {
7159         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7160             DisplayMoveError(_("Illegal move"));
7161             return;
7162         }
7163     }
7164
7165     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7166         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7167              ClearPremoveHighlights(); // was included
7168         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7169         return;
7170     }
7171
7172     if(addToBookFlag) { // adding moves to book
7173         char buf[MSG_SIZ], move[MSG_SIZ];
7174         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7175         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7176                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7177         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7178         AddBookMove(buf);
7179         addToBookFlag = FALSE;
7180         ClearHighlights();
7181         return;
7182     }
7183
7184     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7185 }
7186
7187 /* Common tail of UserMoveEvent and DropMenuEvent */
7188 int
7189 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7190 {
7191     char *bookHit = 0;
7192
7193     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7194         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7195         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7196         if(WhiteOnMove(currentMove)) {
7197             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7198         } else {
7199             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7200         }
7201     }
7202
7203     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7204        move type in caller when we know the move is a legal promotion */
7205     if(moveType == NormalMove && promoChar)
7206         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7207
7208     /* [HGM] <popupFix> The following if has been moved here from
7209        UserMoveEvent(). Because it seemed to belong here (why not allow
7210        piece drops in training games?), and because it can only be
7211        performed after it is known to what we promote. */
7212     if (gameMode == Training) {
7213       /* compare the move played on the board to the next move in the
7214        * game. If they match, display the move and the opponent's response.
7215        * If they don't match, display an error message.
7216        */
7217       int saveAnimate;
7218       Board testBoard;
7219       CopyBoard(testBoard, boards[currentMove]);
7220       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7221
7222       if (CompareBoards(testBoard, boards[currentMove+1])) {
7223         ForwardInner(currentMove+1);
7224
7225         /* Autoplay the opponent's response.
7226          * if appData.animate was TRUE when Training mode was entered,
7227          * the response will be animated.
7228          */
7229         saveAnimate = appData.animate;
7230         appData.animate = animateTraining;
7231         ForwardInner(currentMove+1);
7232         appData.animate = saveAnimate;
7233
7234         /* check for the end of the game */
7235         if (currentMove >= forwardMostMove) {
7236           gameMode = PlayFromGameFile;
7237           ModeHighlight();
7238           SetTrainingModeOff();
7239           DisplayInformation(_("End of game"));
7240         }
7241       } else {
7242         DisplayError(_("Incorrect move"), 0);
7243       }
7244       return 1;
7245     }
7246
7247   /* Ok, now we know that the move is good, so we can kill
7248      the previous line in Analysis Mode */
7249   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7250                                 && currentMove < forwardMostMove) {
7251     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7252     else forwardMostMove = currentMove;
7253   }
7254
7255   ClearMap();
7256
7257   /* If we need the chess program but it's dead, restart it */
7258   ResurrectChessProgram();
7259
7260   /* A user move restarts a paused game*/
7261   if (pausing)
7262     PauseEvent();
7263
7264   thinkOutput[0] = NULLCHAR;
7265
7266   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7267
7268   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7269     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7270     return 1;
7271   }
7272
7273   if (gameMode == BeginningOfGame) {
7274     if (appData.noChessProgram) {
7275       gameMode = EditGame;
7276       SetGameInfo();
7277     } else {
7278       char buf[MSG_SIZ];
7279       gameMode = MachinePlaysBlack;
7280       StartClocks();
7281       SetGameInfo();
7282       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7283       DisplayTitle(buf);
7284       if (first.sendName) {
7285         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7286         SendToProgram(buf, &first);
7287       }
7288       StartClocks();
7289     }
7290     ModeHighlight();
7291   }
7292
7293   /* Relay move to ICS or chess engine */
7294   if (appData.icsActive) {
7295     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7296         gameMode == IcsExamining) {
7297       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7298         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7299         SendToICS("draw ");
7300         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7301       }
7302       // also send plain move, in case ICS does not understand atomic claims
7303       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7304       ics_user_moved = 1;
7305     }
7306   } else {
7307     if (first.sendTime && (gameMode == BeginningOfGame ||
7308                            gameMode == MachinePlaysWhite ||
7309                            gameMode == MachinePlaysBlack)) {
7310       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7311     }
7312     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7313          // [HGM] book: if program might be playing, let it use book
7314         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7315         first.maybeThinking = TRUE;
7316     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7317         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7318         SendBoard(&first, currentMove+1);
7319         if(second.analyzing) {
7320             if(!second.useSetboard) SendToProgram("undo\n", &second);
7321             SendBoard(&second, currentMove+1);
7322         }
7323     } else {
7324         SendMoveToProgram(forwardMostMove-1, &first);
7325         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7326     }
7327     if (currentMove == cmailOldMove + 1) {
7328       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7329     }
7330   }
7331
7332   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7333
7334   switch (gameMode) {
7335   case EditGame:
7336     if(appData.testLegality)
7337     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7338     case MT_NONE:
7339     case MT_CHECK:
7340       break;
7341     case MT_CHECKMATE:
7342     case MT_STAINMATE:
7343       if (WhiteOnMove(currentMove)) {
7344         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7345       } else {
7346         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7347       }
7348       break;
7349     case MT_STALEMATE:
7350       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7351       break;
7352     }
7353     break;
7354
7355   case MachinePlaysBlack:
7356   case MachinePlaysWhite:
7357     /* disable certain menu options while machine is thinking */
7358     SetMachineThinkingEnables();
7359     break;
7360
7361   default:
7362     break;
7363   }
7364
7365   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7366   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7367
7368   if(bookHit) { // [HGM] book: simulate book reply
7369         static char bookMove[MSG_SIZ]; // a bit generous?
7370
7371         programStats.nodes = programStats.depth = programStats.time =
7372         programStats.score = programStats.got_only_move = 0;
7373         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7374
7375         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7376         strcat(bookMove, bookHit);
7377         HandleMachineMove(bookMove, &first);
7378   }
7379   return 1;
7380 }
7381
7382 void
7383 MarkByFEN(char *fen)
7384 {
7385         int r, f;
7386         if(!appData.markers || !appData.highlightDragging) return;
7387         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7388         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7389         while(*fen) {
7390             int s = 0;
7391             marker[r][f] = 0;
7392             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7393             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7394             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7395             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7396             if(*fen == 'T') marker[r][f++] = 0; else
7397             if(*fen == 'Y') marker[r][f++] = 1; else
7398             if(*fen == 'G') marker[r][f++] = 3; else
7399             if(*fen == 'B') marker[r][f++] = 4; else
7400             if(*fen == 'C') marker[r][f++] = 5; else
7401             if(*fen == 'M') marker[r][f++] = 6; else
7402             if(*fen == 'W') marker[r][f++] = 7; else
7403             if(*fen == 'D') marker[r][f++] = 8; else
7404             if(*fen == 'R') marker[r][f++] = 2; else {
7405                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7406               f += s; fen -= s>0;
7407             }
7408             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7409             if(r < 0) break;
7410             fen++;
7411         }
7412         DrawPosition(TRUE, NULL);
7413 }
7414
7415 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7416
7417 void
7418 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7419 {
7420     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7421     Markers *m = (Markers *) closure;
7422     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7423                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7424         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7425                          || kind == WhiteCapturesEnPassant
7426                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7427     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7428 }
7429
7430 static int hoverSavedValid;
7431
7432 void
7433 MarkTargetSquares (int clear)
7434 {
7435   int x, y, sum=0;
7436   if(clear) { // no reason to ever suppress clearing
7437     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7438     hoverSavedValid = 0;
7439     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7440   } else {
7441     int capt = 0;
7442     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7443        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7444     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7445     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7446       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7447       if(capt)
7448       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7449     }
7450   }
7451   DrawPosition(FALSE, NULL);
7452 }
7453
7454 int
7455 Explode (Board board, int fromX, int fromY, int toX, int toY)
7456 {
7457     if(gameInfo.variant == VariantAtomic &&
7458        (board[toY][toX] != EmptySquare ||                     // capture?
7459         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7460                          board[fromY][fromX] == BlackPawn   )
7461       )) {
7462         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7463         return TRUE;
7464     }
7465     return FALSE;
7466 }
7467
7468 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7469
7470 int
7471 CanPromote (ChessSquare piece, int y)
7472 {
7473         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7474         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7475         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7476         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7477            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7478           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7479            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7480         return (piece == BlackPawn && y <= zone ||
7481                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7482                 piece == BlackLance && y <= zone ||
7483                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7484 }
7485
7486 void
7487 HoverEvent (int xPix, int yPix, int x, int y)
7488 {
7489         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7490         int r, f;
7491         if(!first.highlight) return;
7492         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7493         if(x == oldX && y == oldY) return; // only do something if we enter new square
7494         oldFromX = fromX; oldFromY = fromY;
7495         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7496           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7497             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7498           hoverSavedValid = 1;
7499         } else if(oldX != x || oldY != y) {
7500           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7501           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7502           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7503             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7504           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7505             char buf[MSG_SIZ];
7506             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7507             SendToProgram(buf, &first);
7508           }
7509           oldX = x; oldY = y;
7510 //        SetHighlights(fromX, fromY, x, y);
7511         }
7512 }
7513
7514 void ReportClick(char *action, int x, int y)
7515 {
7516         char buf[MSG_SIZ]; // Inform engine of what user does
7517         int r, f;
7518         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7519           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7520             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7521         if(!first.highlight || gameMode == EditPosition) return;
7522         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7523         SendToProgram(buf, &first);
7524 }
7525
7526 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7527
7528 void
7529 LeftClick (ClickType clickType, int xPix, int yPix)
7530 {
7531     int x, y;
7532     Boolean saveAnimate;
7533     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7534     char promoChoice = NULLCHAR;
7535     ChessSquare piece;
7536     static TimeMark lastClickTime, prevClickTime;
7537
7538     if(flashing) return;
7539
7540     x = EventToSquare(xPix, BOARD_WIDTH);
7541     y = EventToSquare(yPix, BOARD_HEIGHT);
7542     if (!flipView && y >= 0) {
7543         y = BOARD_HEIGHT - 1 - y;
7544     }
7545     if (flipView && x >= 0) {
7546         x = BOARD_WIDTH - 1 - x;
7547     }
7548
7549     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7550         static int dummy;
7551         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7552         right = TRUE;
7553         return;
7554     }
7555
7556     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7557
7558     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7559
7560     if (clickType == Press) ErrorPopDown();
7561     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7562
7563     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7564         defaultPromoChoice = promoSweep;
7565         promoSweep = EmptySquare;   // terminate sweep
7566         promoDefaultAltered = TRUE;
7567         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7568     }
7569
7570     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7571         if(clickType == Release) return; // ignore upclick of click-click destination
7572         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7573         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7574         if(gameInfo.holdingsWidth &&
7575                 (WhiteOnMove(currentMove)
7576                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7577                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7578             // click in right holdings, for determining promotion piece
7579             ChessSquare p = boards[currentMove][y][x];
7580             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7581             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7582             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7583                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7584                 fromX = fromY = -1;
7585                 return;
7586             }
7587         }
7588         DrawPosition(FALSE, boards[currentMove]);
7589         return;
7590     }
7591
7592     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7593     if(clickType == Press
7594             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7595               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7596               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7597         return;
7598
7599     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7600         // could be static click on premove from-square: abort premove
7601         gotPremove = 0;
7602         ClearPremoveHighlights();
7603     }
7604
7605     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7606         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7607
7608     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7609         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7610                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7611         defaultPromoChoice = DefaultPromoChoice(side);
7612     }
7613
7614     autoQueen = appData.alwaysPromoteToQueen;
7615
7616     if (fromX == -1) {
7617       int originalY = y;
7618       gatingPiece = EmptySquare;
7619       if (clickType != Press) {
7620         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7621             DragPieceEnd(xPix, yPix); dragging = 0;
7622             DrawPosition(FALSE, NULL);
7623         }
7624         return;
7625       }
7626       doubleClick = FALSE;
7627       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7628         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7629       }
7630       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7631       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7632          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7633          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7634             /* First square */
7635             if (OKToStartUserMove(fromX, fromY)) {
7636                 second = 0;
7637                 ReportClick("lift", x, y);
7638                 MarkTargetSquares(0);
7639                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7640                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7641                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7642                     promoSweep = defaultPromoChoice;
7643                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7644                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7645                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7646                 }
7647                 if (appData.highlightDragging) {
7648                     SetHighlights(fromX, fromY, -1, -1);
7649                 } else {
7650                     ClearHighlights();
7651                 }
7652             } else fromX = fromY = -1;
7653             return;
7654         }
7655     }
7656
7657     /* fromX != -1 */
7658     if (clickType == Press && gameMode != EditPosition) {
7659         ChessSquare fromP;
7660         ChessSquare toP;
7661         int frc;
7662
7663         // ignore off-board to clicks
7664         if(y < 0 || x < 0) return;
7665
7666         /* Check if clicking again on the same color piece */
7667         fromP = boards[currentMove][fromY][fromX];
7668         toP = boards[currentMove][y][x];
7669         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7670         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7671             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7672            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7673              WhitePawn <= toP && toP <= WhiteKing &&
7674              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7675              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7676             (BlackPawn <= fromP && fromP <= BlackKing &&
7677              BlackPawn <= toP && toP <= BlackKing &&
7678              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7679              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7680             /* Clicked again on same color piece -- changed his mind */
7681             second = (x == fromX && y == fromY);
7682             killX = killY = kill2X = kill2Y = -1;
7683             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7684                 second = FALSE; // first double-click rather than scond click
7685                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7686             }
7687             promoDefaultAltered = FALSE;
7688            if(!second) MarkTargetSquares(1);
7689            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7690             if (appData.highlightDragging) {
7691                 SetHighlights(x, y, -1, -1);
7692             } else {
7693                 ClearHighlights();
7694             }
7695             if (OKToStartUserMove(x, y)) {
7696                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7697                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7698                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7699                  gatingPiece = boards[currentMove][fromY][fromX];
7700                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7701                 fromX = x;
7702                 fromY = y; dragging = 1;
7703                 if(!second) ReportClick("lift", x, y);
7704                 MarkTargetSquares(0);
7705                 DragPieceBegin(xPix, yPix, FALSE);
7706                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7707                     promoSweep = defaultPromoChoice;
7708                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7709                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7710                 }
7711             }
7712            }
7713            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7714            second = FALSE;
7715         }
7716         // ignore clicks on holdings
7717         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7718     }
7719
7720     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7721         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7722         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7723         return;
7724     }
7725
7726     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7727         DragPieceEnd(xPix, yPix); dragging = 0;
7728         if(clearFlag) {
7729             // a deferred attempt to click-click move an empty square on top of a piece
7730             boards[currentMove][y][x] = EmptySquare;
7731             ClearHighlights();
7732             DrawPosition(FALSE, boards[currentMove]);
7733             fromX = fromY = -1; clearFlag = 0;
7734             return;
7735         }
7736         if (appData.animateDragging) {
7737             /* Undo animation damage if any */
7738             DrawPosition(FALSE, NULL);
7739         }
7740         if (second) {
7741             /* Second up/down in same square; just abort move */
7742             second = 0;
7743             fromX = fromY = -1;
7744             gatingPiece = EmptySquare;
7745             ClearHighlights();
7746             gotPremove = 0;
7747             ClearPremoveHighlights();
7748             MarkTargetSquares(-1);
7749             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7750         } else {
7751             /* First upclick in same square; start click-click mode */
7752             SetHighlights(x, y, -1, -1);
7753         }
7754         return;
7755     }
7756
7757     clearFlag = 0;
7758
7759     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7760        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7761         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7762         DisplayMessage(_("only marked squares are legal"),"");
7763         DrawPosition(TRUE, NULL);
7764         return; // ignore to-click
7765     }
7766
7767     /* we now have a different from- and (possibly off-board) to-square */
7768     /* Completed move */
7769     if(!sweepSelecting) {
7770         toX = x;
7771         toY = y;
7772     }
7773
7774     piece = boards[currentMove][fromY][fromX];
7775
7776     saveAnimate = appData.animate;
7777     if (clickType == Press) {
7778         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7779         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7780             // must be Edit Position mode with empty-square selected
7781             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7782             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7783             return;
7784         }
7785         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7786             return;
7787         }
7788         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7789             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7790         } else
7791         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7792         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7793           if(appData.sweepSelect) {
7794             promoSweep = defaultPromoChoice;
7795             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7796             selectFlag = 0; lastX = xPix; lastY = yPix;
7797             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7798             saveFlash = appData.flashCount; appData.flashCount = 0;
7799             Sweep(0); // Pawn that is going to promote: preview promotion piece
7800             sweepSelecting = 1;
7801             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7802             MarkTargetSquares(1);
7803           }
7804           return; // promo popup appears on up-click
7805         }
7806         /* Finish clickclick move */
7807         if (appData.animate || appData.highlightLastMove) {
7808             SetHighlights(fromX, fromY, toX, toY);
7809         } else {
7810             ClearHighlights();
7811         }
7812         MarkTargetSquares(1);
7813     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7814         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7815         *promoRestrict = 0; appData.flashCount = saveFlash;
7816         if (appData.animate || appData.highlightLastMove) {
7817             SetHighlights(fromX, fromY, toX, toY);
7818         } else {
7819             ClearHighlights();
7820         }
7821         MarkTargetSquares(1);
7822     } else {
7823 #if 0
7824 // [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
7825         /* Finish drag move */
7826         if (appData.highlightLastMove) {
7827             SetHighlights(fromX, fromY, toX, toY);
7828         } else {
7829             ClearHighlights();
7830         }
7831 #endif
7832         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7833           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7834         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7835         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7836           dragging *= 2;            // flag button-less dragging if we are dragging
7837           MarkTargetSquares(1);
7838           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7839           else {
7840             kill2X = killX; kill2Y = killY;
7841             killX = x; killY = y;     // remember this square as intermediate
7842             ReportClick("put", x, y); // and inform engine
7843             ReportClick("lift", x, y);
7844             MarkTargetSquares(0);
7845             return;
7846           }
7847         }
7848         DragPieceEnd(xPix, yPix); dragging = 0;
7849         /* Don't animate move and drag both */
7850         appData.animate = FALSE;
7851         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7852     }
7853
7854     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7855     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7856         ChessSquare piece = boards[currentMove][fromY][fromX];
7857         if(gameMode == EditPosition && piece != EmptySquare &&
7858            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7859             int n;
7860
7861             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7862                 n = PieceToNumber(piece - (int)BlackPawn);
7863                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7864                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7865                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7866             } else
7867             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7868                 n = PieceToNumber(piece);
7869                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7870                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7871                 boards[currentMove][n][BOARD_WIDTH-2]++;
7872             }
7873             boards[currentMove][fromY][fromX] = EmptySquare;
7874         }
7875         ClearHighlights();
7876         fromX = fromY = -1;
7877         MarkTargetSquares(1);
7878         DrawPosition(TRUE, boards[currentMove]);
7879         return;
7880     }
7881
7882     // off-board moves should not be highlighted
7883     if(x < 0 || y < 0) ClearHighlights();
7884     else ReportClick("put", x, y);
7885
7886     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7887
7888     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7889
7890     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7891         SetHighlights(fromX, fromY, toX, toY);
7892         MarkTargetSquares(1);
7893         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7894             // [HGM] super: promotion to captured piece selected from holdings
7895             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7896             promotionChoice = TRUE;
7897             // kludge follows to temporarily execute move on display, without promoting yet
7898             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7899             boards[currentMove][toY][toX] = p;
7900             DrawPosition(FALSE, boards[currentMove]);
7901             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7902             boards[currentMove][toY][toX] = q;
7903             DisplayMessage("Click in holdings to choose piece", "");
7904             return;
7905         }
7906         PromotionPopUp(promoChoice);
7907     } else {
7908         int oldMove = currentMove;
7909         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7910         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7911         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7912         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7913         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7914            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7915             DrawPosition(TRUE, boards[currentMove]);
7916         fromX = fromY = -1;
7917         flashing = 0;
7918     }
7919     appData.animate = saveAnimate;
7920     if (appData.animate || appData.animateDragging) {
7921         /* Undo animation damage if needed */
7922 //      DrawPosition(FALSE, NULL);
7923     }
7924 }
7925
7926 int
7927 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7928 {   // front-end-free part taken out of PieceMenuPopup
7929     int whichMenu; int xSqr, ySqr;
7930
7931     if(seekGraphUp) { // [HGM] seekgraph
7932         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7933         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7934         return -2;
7935     }
7936
7937     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7938          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7939         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7940         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7941         if(action == Press)   {
7942             originalFlip = flipView;
7943             flipView = !flipView; // temporarily flip board to see game from partners perspective
7944             DrawPosition(TRUE, partnerBoard);
7945             DisplayMessage(partnerStatus, "");
7946             partnerUp = TRUE;
7947         } else if(action == Release) {
7948             flipView = originalFlip;
7949             DrawPosition(TRUE, boards[currentMove]);
7950             partnerUp = FALSE;
7951         }
7952         return -2;
7953     }
7954
7955     xSqr = EventToSquare(x, BOARD_WIDTH);
7956     ySqr = EventToSquare(y, BOARD_HEIGHT);
7957     if (action == Release) {
7958         if(pieceSweep != EmptySquare) {
7959             EditPositionMenuEvent(pieceSweep, toX, toY);
7960             pieceSweep = EmptySquare;
7961         } else UnLoadPV(); // [HGM] pv
7962     }
7963     if (action != Press) return -2; // return code to be ignored
7964     switch (gameMode) {
7965       case IcsExamining:
7966         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7967       case EditPosition:
7968         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7969         if (xSqr < 0 || ySqr < 0) return -1;
7970         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7971         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7972         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7973         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7974         NextPiece(0);
7975         return 2; // grab
7976       case IcsObserving:
7977         if(!appData.icsEngineAnalyze) return -1;
7978       case IcsPlayingWhite:
7979       case IcsPlayingBlack:
7980         if(!appData.zippyPlay) goto noZip;
7981       case AnalyzeMode:
7982       case AnalyzeFile:
7983       case MachinePlaysWhite:
7984       case MachinePlaysBlack:
7985       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7986         if (!appData.dropMenu) {
7987           LoadPV(x, y);
7988           return 2; // flag front-end to grab mouse events
7989         }
7990         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7991            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7992       case EditGame:
7993       noZip:
7994         if (xSqr < 0 || ySqr < 0) return -1;
7995         if (!appData.dropMenu || appData.testLegality &&
7996             gameInfo.variant != VariantBughouse &&
7997             gameInfo.variant != VariantCrazyhouse) return -1;
7998         whichMenu = 1; // drop menu
7999         break;
8000       default:
8001         return -1;
8002     }
8003
8004     if (((*fromX = xSqr) < 0) ||
8005         ((*fromY = ySqr) < 0)) {
8006         *fromX = *fromY = -1;
8007         return -1;
8008     }
8009     if (flipView)
8010       *fromX = BOARD_WIDTH - 1 - *fromX;
8011     else
8012       *fromY = BOARD_HEIGHT - 1 - *fromY;
8013
8014     return whichMenu;
8015 }
8016
8017 void
8018 Wheel (int dir, int x, int y)
8019 {
8020     if(gameMode == EditPosition) {
8021         int xSqr = EventToSquare(x, BOARD_WIDTH);
8022         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8023         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8024         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8025         do {
8026             boards[currentMove][ySqr][xSqr] += dir;
8027             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8028             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8029         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8030         DrawPosition(FALSE, boards[currentMove]);
8031     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8032 }
8033
8034 void
8035 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8036 {
8037 //    char * hint = lastHint;
8038     FrontEndProgramStats stats;
8039
8040     stats.which = cps == &first ? 0 : 1;
8041     stats.depth = cpstats->depth;
8042     stats.nodes = cpstats->nodes;
8043     stats.score = cpstats->score;
8044     stats.time = cpstats->time;
8045     stats.pv = cpstats->movelist;
8046     stats.hint = lastHint;
8047     stats.an_move_index = 0;
8048     stats.an_move_count = 0;
8049
8050     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8051         stats.hint = cpstats->move_name;
8052         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8053         stats.an_move_count = cpstats->nr_moves;
8054     }
8055
8056     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
8057
8058     SetProgramStats( &stats );
8059 }
8060
8061 void
8062 ClearEngineOutputPane (int which)
8063 {
8064     static FrontEndProgramStats dummyStats;
8065     dummyStats.which = which;
8066     dummyStats.pv = "#";
8067     SetProgramStats( &dummyStats );
8068 }
8069
8070 #define MAXPLAYERS 500
8071
8072 char *
8073 TourneyStandings (int display)
8074 {
8075     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8076     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8077     char result, *p, *names[MAXPLAYERS];
8078
8079     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8080         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8081     names[0] = p = strdup(appData.participants);
8082     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8083
8084     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8085
8086     while(result = appData.results[nr]) {
8087         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8088         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8089         wScore = bScore = 0;
8090         switch(result) {
8091           case '+': wScore = 2; break;
8092           case '-': bScore = 2; break;
8093           case '=': wScore = bScore = 1; break;
8094           case ' ':
8095           case '*': return strdup("busy"); // tourney not finished
8096         }
8097         score[w] += wScore;
8098         score[b] += bScore;
8099         games[w]++;
8100         games[b]++;
8101         nr++;
8102     }
8103     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8104     for(w=0; w<nPlayers; w++) {
8105         bScore = -1;
8106         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8107         ranking[w] = b; points[w] = bScore; score[b] = -2;
8108     }
8109     p = malloc(nPlayers*34+1);
8110     for(w=0; w<nPlayers && w<display; w++)
8111         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8112     free(names[0]);
8113     return p;
8114 }
8115
8116 void
8117 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8118 {       // count all piece types
8119         int p, f, r;
8120         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8121         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8122         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8123                 p = board[r][f];
8124                 pCnt[p]++;
8125                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8126                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8127                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8128                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8129                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8130                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8131         }
8132 }
8133
8134 int
8135 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8136 {
8137         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8138         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8139
8140         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8141         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8142         if(myPawns == 2 && nMine == 3) // KPP
8143             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8144         if(myPawns == 1 && nMine == 2) // KP
8145             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8146         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8147             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8148         if(myPawns) return FALSE;
8149         if(pCnt[WhiteRook+side])
8150             return pCnt[BlackRook-side] ||
8151                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8152                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8153                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8154         if(pCnt[WhiteCannon+side]) {
8155             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8156             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8157         }
8158         if(pCnt[WhiteKnight+side])
8159             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8160         return FALSE;
8161 }
8162
8163 int
8164 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8165 {
8166         VariantClass v = gameInfo.variant;
8167
8168         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8169         if(v == VariantShatranj) return TRUE; // always winnable through baring
8170         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8171         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8172
8173         if(v == VariantXiangqi) {
8174                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8175
8176                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8177                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8178                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8179                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8180                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8181                 if(stale) // we have at least one last-rank P plus perhaps C
8182                     return majors // KPKX
8183                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8184                 else // KCA*E*
8185                     return pCnt[WhiteFerz+side] // KCAK
8186                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8187                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8188                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8189
8190         } else if(v == VariantKnightmate) {
8191                 if(nMine == 1) return FALSE;
8192                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8193         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8194                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8195
8196                 if(nMine == 1) return FALSE; // bare King
8197                 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
8198                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8199                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8200                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8201                 if(pCnt[WhiteKnight+side])
8202                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8203                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8204                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8205                 if(nBishops)
8206                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8207                 if(pCnt[WhiteAlfil+side])
8208                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8209                 if(pCnt[WhiteWazir+side])
8210                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8211         }
8212
8213         return TRUE;
8214 }
8215
8216 int
8217 CompareWithRights (Board b1, Board b2)
8218 {
8219     int rights = 0;
8220     if(!CompareBoards(b1, b2)) return FALSE;
8221     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8222     /* compare castling rights */
8223     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8224            rights++; /* King lost rights, while rook still had them */
8225     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8226         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8227            rights++; /* but at least one rook lost them */
8228     }
8229     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8230            rights++;
8231     if( b1[CASTLING][5] != NoRights ) {
8232         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8233            rights++;
8234     }
8235     return rights == 0;
8236 }
8237
8238 int
8239 Adjudicate (ChessProgramState *cps)
8240 {       // [HGM] some adjudications useful with buggy engines
8241         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8242         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8243         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8244         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8245         int k, drop, count = 0; static int bare = 1;
8246         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8247         Boolean canAdjudicate = !appData.icsActive;
8248
8249         // most tests only when we understand the game, i.e. legality-checking on
8250             if( appData.testLegality )
8251             {   /* [HGM] Some more adjudications for obstinate engines */
8252                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8253                 static int moveCount = 6;
8254                 ChessMove result;
8255                 char *reason = NULL;
8256
8257                 /* Count what is on board. */
8258                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8259
8260                 /* Some material-based adjudications that have to be made before stalemate test */
8261                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8262                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8263                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8264                      if(canAdjudicate && appData.checkMates) {
8265                          if(engineOpponent)
8266                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8267                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8268                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8269                          return 1;
8270                      }
8271                 }
8272
8273                 /* Bare King in Shatranj (loses) or Losers (wins) */
8274                 if( nrW == 1 || nrB == 1) {
8275                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8276                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8277                      if(canAdjudicate && appData.checkMates) {
8278                          if(engineOpponent)
8279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8280                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8281                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8282                          return 1;
8283                      }
8284                   } else
8285                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8286                   {    /* bare King */
8287                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8288                         if(canAdjudicate && appData.checkMates) {
8289                             /* but only adjudicate if adjudication enabled */
8290                             if(engineOpponent)
8291                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8292                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8293                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8294                             return 1;
8295                         }
8296                   }
8297                 } else bare = 1;
8298
8299
8300             // don't wait for engine to announce game end if we can judge ourselves
8301             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8302               case MT_CHECK:
8303                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8304                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8305                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8306                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8307                             checkCnt++;
8308                         if(checkCnt >= 2) {
8309                             reason = "Xboard adjudication: 3rd check";
8310                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8311                             break;
8312                         }
8313                     }
8314                 }
8315               case MT_NONE:
8316               default:
8317                 break;
8318               case MT_STEALMATE:
8319               case MT_STALEMATE:
8320               case MT_STAINMATE:
8321                 reason = "Xboard adjudication: Stalemate";
8322                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8323                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8324                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8325                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8326                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8327                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8328                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8329                                                                         EP_CHECKMATE : EP_WINS);
8330                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8331                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8332                 }
8333                 break;
8334               case MT_CHECKMATE:
8335                 reason = "Xboard adjudication: Checkmate";
8336                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8337                 if(gameInfo.variant == VariantShogi) {
8338                     if(forwardMostMove > backwardMostMove
8339                        && moveList[forwardMostMove-1][1] == '@'
8340                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8341                         reason = "XBoard adjudication: pawn-drop mate";
8342                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8343                     }
8344                 }
8345                 break;
8346             }
8347
8348                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8349                     case EP_STALEMATE:
8350                         result = GameIsDrawn; break;
8351                     case EP_CHECKMATE:
8352                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8353                     case EP_WINS:
8354                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8355                     default:
8356                         result = EndOfFile;
8357                 }
8358                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8359                     if(engineOpponent)
8360                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8361                     GameEnds( result, reason, GE_XBOARD );
8362                     return 1;
8363                 }
8364
8365                 /* Next absolutely insufficient mating material. */
8366                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8367                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8368                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8369
8370                      /* always flag draws, for judging claims */
8371                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8372
8373                      if(canAdjudicate && appData.materialDraws) {
8374                          /* but only adjudicate them if adjudication enabled */
8375                          if(engineOpponent) {
8376                            SendToProgram("force\n", engineOpponent); // suppress reply
8377                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8378                          }
8379                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8380                          return 1;
8381                      }
8382                 }
8383
8384                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8385                 if(gameInfo.variant == VariantXiangqi ?
8386                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8387                  : nrW + nrB == 4 &&
8388                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8389                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8390                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8391                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8392                    ) ) {
8393                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8394                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8395                           if(engineOpponent) {
8396                             SendToProgram("force\n", engineOpponent); // suppress reply
8397                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8398                           }
8399                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8400                           return 1;
8401                      }
8402                 } else moveCount = 6;
8403             }
8404
8405         // Repetition draws and 50-move rule can be applied independently of legality testing
8406
8407                 /* Check for rep-draws */
8408                 count = 0;
8409                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8410                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8411                 for(k = forwardMostMove-2;
8412                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8413                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8414                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8415                     k-=2)
8416                 {   int rights=0;
8417                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8418                         /* compare castling rights */
8419                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8420                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8421                                 rights++; /* King lost rights, while rook still had them */
8422                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8423                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8424                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8425                                    rights++; /* but at least one rook lost them */
8426                         }
8427                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8428                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8429                                 rights++;
8430                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8431                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8432                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8433                                    rights++;
8434                         }
8435                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8436                             && appData.drawRepeats > 1) {
8437                              /* adjudicate after user-specified nr of repeats */
8438                              int result = GameIsDrawn;
8439                              char *details = "XBoard adjudication: repetition draw";
8440                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8441                                 // [HGM] xiangqi: check for forbidden perpetuals
8442                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8443                                 for(m=forwardMostMove; m>k; m-=2) {
8444                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8445                                         ourPerpetual = 0; // the current mover did not always check
8446                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8447                                         hisPerpetual = 0; // the opponent did not always check
8448                                 }
8449                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8450                                                                         ourPerpetual, hisPerpetual);
8451                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8452                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8453                                     details = "Xboard adjudication: perpetual checking";
8454                                 } else
8455                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8456                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8457                                 } else
8458                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8459                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8460                                         result = BlackWins;
8461                                         details = "Xboard adjudication: repetition";
8462                                     }
8463                                 } else // it must be XQ
8464                                 // Now check for perpetual chases
8465                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8466                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8467                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8468                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8469                                         static char resdet[MSG_SIZ];
8470                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8471                                         details = resdet;
8472                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8473                                     } else
8474                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8475                                         break; // Abort repetition-checking loop.
8476                                 }
8477                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8478                              }
8479                              if(engineOpponent) {
8480                                SendToProgram("force\n", engineOpponent); // suppress reply
8481                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8482                              }
8483                              GameEnds( result, details, GE_XBOARD );
8484                              return 1;
8485                         }
8486                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8487                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8488                     }
8489                 }
8490
8491                 /* Now we test for 50-move draws. Determine ply count */
8492                 count = forwardMostMove;
8493                 /* look for last irreversble move */
8494                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8495                     count--;
8496                 /* if we hit starting position, add initial plies */
8497                 if( count == backwardMostMove )
8498                     count -= initialRulePlies;
8499                 count = forwardMostMove - count;
8500                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8501                         // adjust reversible move counter for checks in Xiangqi
8502                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8503                         if(i < backwardMostMove) i = backwardMostMove;
8504                         while(i <= forwardMostMove) {
8505                                 lastCheck = inCheck; // check evasion does not count
8506                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8507                                 if(inCheck || lastCheck) count--; // check does not count
8508                                 i++;
8509                         }
8510                 }
8511                 if( count >= 100)
8512                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8513                          /* this is used to judge if draw claims are legal */
8514                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8515                          if(engineOpponent) {
8516                            SendToProgram("force\n", engineOpponent); // suppress reply
8517                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8518                          }
8519                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8520                          return 1;
8521                 }
8522
8523                 /* if draw offer is pending, treat it as a draw claim
8524                  * when draw condition present, to allow engines a way to
8525                  * claim draws before making their move to avoid a race
8526                  * condition occurring after their move
8527                  */
8528                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8529                          char *p = NULL;
8530                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8531                              p = "Draw claim: 50-move rule";
8532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8533                              p = "Draw claim: 3-fold repetition";
8534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8535                              p = "Draw claim: insufficient mating material";
8536                          if( p != NULL && canAdjudicate) {
8537                              if(engineOpponent) {
8538                                SendToProgram("force\n", engineOpponent); // suppress reply
8539                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8540                              }
8541                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8542                              return 1;
8543                          }
8544                 }
8545
8546                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8547                     if(engineOpponent) {
8548                       SendToProgram("force\n", engineOpponent); // suppress reply
8549                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8550                     }
8551                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8552                     return 1;
8553                 }
8554         return 0;
8555 }
8556
8557 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8558 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8559 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8560
8561 static int
8562 BitbaseProbe ()
8563 {
8564     int pieces[10], squares[10], cnt=0, r, f, res;
8565     static int loaded;
8566     static PPROBE_EGBB probeBB;
8567     if(!appData.testLegality) return 10;
8568     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8569     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8570     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8571     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8572         ChessSquare piece = boards[forwardMostMove][r][f];
8573         int black = (piece >= BlackPawn);
8574         int type = piece - black*BlackPawn;
8575         if(piece == EmptySquare) continue;
8576         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8577         if(type == WhiteKing) type = WhiteQueen + 1;
8578         type = egbbCode[type];
8579         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8580         pieces[cnt] = type + black*6;
8581         if(++cnt > 5) return 11;
8582     }
8583     pieces[cnt] = squares[cnt] = 0;
8584     // probe EGBB
8585     if(loaded == 2) return 13; // loading failed before
8586     if(loaded == 0) {
8587         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8588         HMODULE lib;
8589         PLOAD_EGBB loadBB;
8590         loaded = 2; // prepare for failure
8591         if(!path) return 13; // no egbb installed
8592         strncpy(buf, path + 8, MSG_SIZ);
8593         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8594         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8595         lib = LoadLibrary(buf);
8596         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8597         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8598         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8599         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8600         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8601         loaded = 1; // success!
8602     }
8603     res = probeBB(forwardMostMove & 1, pieces, squares);
8604     return res > 0 ? 1 : res < 0 ? -1 : 0;
8605 }
8606
8607 char *
8608 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8609 {   // [HGM] book: this routine intercepts moves to simulate book replies
8610     char *bookHit = NULL;
8611
8612     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8613         char buf[MSG_SIZ];
8614         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8615         SendToProgram(buf, cps);
8616     }
8617     //first determine if the incoming move brings opponent into his book
8618     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8619         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8620     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8621     if(bookHit != NULL && !cps->bookSuspend) {
8622         // make sure opponent is not going to reply after receiving move to book position
8623         SendToProgram("force\n", cps);
8624         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8625     }
8626     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8627     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8628     // now arrange restart after book miss
8629     if(bookHit) {
8630         // after a book hit we never send 'go', and the code after the call to this routine
8631         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8632         char buf[MSG_SIZ], *move = bookHit;
8633         if(cps->useSAN) {
8634             int fromX, fromY, toX, toY;
8635             char promoChar;
8636             ChessMove moveType;
8637             move = buf + 30;
8638             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8639                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8640                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8641                                     PosFlags(forwardMostMove),
8642                                     fromY, fromX, toY, toX, promoChar, move);
8643             } else {
8644                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8645                 bookHit = NULL;
8646             }
8647         }
8648         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8649         SendToProgram(buf, cps);
8650         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8651     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8652         SendToProgram("go\n", cps);
8653         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8654     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8655         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8656             SendToProgram("go\n", cps);
8657         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8658     }
8659     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8660 }
8661
8662 int
8663 LoadError (char *errmess, ChessProgramState *cps)
8664 {   // unloads engine and switches back to -ncp mode if it was first
8665     if(cps->initDone) return FALSE;
8666     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8667     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8668     cps->pr = NoProc;
8669     if(cps == &first) {
8670         appData.noChessProgram = TRUE;
8671         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8672         gameMode = BeginningOfGame; ModeHighlight();
8673         SetNCPMode();
8674     }
8675     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8676     DisplayMessage("", ""); // erase waiting message
8677     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8678     return TRUE;
8679 }
8680
8681 char *savedMessage;
8682 ChessProgramState *savedState;
8683 void
8684 DeferredBookMove (void)
8685 {
8686         if(savedState->lastPing != savedState->lastPong)
8687                     ScheduleDelayedEvent(DeferredBookMove, 10);
8688         else
8689         HandleMachineMove(savedMessage, savedState);
8690 }
8691
8692 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8693 static ChessProgramState *stalledEngine;
8694 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8695
8696 void
8697 HandleMachineMove (char *message, ChessProgramState *cps)
8698 {
8699     static char firstLeg[20], legs;
8700     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8701     char realname[MSG_SIZ];
8702     int fromX, fromY, toX, toY;
8703     ChessMove moveType;
8704     char promoChar, roar;
8705     char *p, *pv=buf1;
8706     int oldError;
8707     char *bookHit;
8708
8709     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8710         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8711         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8712             DisplayError(_("Invalid pairing from pairing engine"), 0);
8713             return;
8714         }
8715         pairingReceived = 1;
8716         NextMatchGame();
8717         return; // Skim the pairing messages here.
8718     }
8719
8720     oldError = cps->userError; cps->userError = 0;
8721
8722 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8723     /*
8724      * Kludge to ignore BEL characters
8725      */
8726     while (*message == '\007') message++;
8727
8728     /*
8729      * [HGM] engine debug message: ignore lines starting with '#' character
8730      */
8731     if(cps->debug && *message == '#') return;
8732
8733     /*
8734      * Look for book output
8735      */
8736     if (cps == &first && bookRequested) {
8737         if (message[0] == '\t' || message[0] == ' ') {
8738             /* Part of the book output is here; append it */
8739             strcat(bookOutput, message);
8740             strcat(bookOutput, "  \n");
8741             return;
8742         } else if (bookOutput[0] != NULLCHAR) {
8743             /* All of book output has arrived; display it */
8744             char *p = bookOutput;
8745             while (*p != NULLCHAR) {
8746                 if (*p == '\t') *p = ' ';
8747                 p++;
8748             }
8749             DisplayInformation(bookOutput);
8750             bookRequested = FALSE;
8751             /* Fall through to parse the current output */
8752         }
8753     }
8754
8755     /*
8756      * Look for machine move.
8757      */
8758     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8759         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8760     {
8761         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8762             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8763             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8764             stalledEngine = cps;
8765             if(appData.ponderNextMove) { // bring opponent out of ponder
8766                 if(gameMode == TwoMachinesPlay) {
8767                     if(cps->other->pause)
8768                         PauseEngine(cps->other);
8769                     else
8770                         SendToProgram("easy\n", cps->other);
8771                 }
8772             }
8773             StopClocks();
8774             return;
8775         }
8776
8777       if(cps->usePing) {
8778
8779         /* This method is only useful on engines that support ping */
8780         if(abortEngineThink) {
8781             if (appData.debugMode) {
8782                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8783             }
8784             SendToProgram("undo\n", cps);
8785             return;
8786         }
8787
8788         if (cps->lastPing != cps->lastPong) {
8789             /* Extra move from before last new; ignore */
8790             if (appData.debugMode) {
8791                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8792             }
8793           return;
8794         }
8795
8796       } else {
8797
8798         int machineWhite = FALSE;
8799
8800         switch (gameMode) {
8801           case BeginningOfGame:
8802             /* Extra move from before last reset; ignore */
8803             if (appData.debugMode) {
8804                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8805             }
8806             return;
8807
8808           case EndOfGame:
8809           case IcsIdle:
8810           default:
8811             /* Extra move after we tried to stop.  The mode test is
8812                not a reliable way of detecting this problem, but it's
8813                the best we can do on engines that don't support ping.
8814             */
8815             if (appData.debugMode) {
8816                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8817                         cps->which, gameMode);
8818             }
8819             SendToProgram("undo\n", cps);
8820             return;
8821
8822           case MachinePlaysWhite:
8823           case IcsPlayingWhite:
8824             machineWhite = TRUE;
8825             break;
8826
8827           case MachinePlaysBlack:
8828           case IcsPlayingBlack:
8829             machineWhite = FALSE;
8830             break;
8831
8832           case TwoMachinesPlay:
8833             machineWhite = (cps->twoMachinesColor[0] == 'w');
8834             break;
8835         }
8836         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8837             if (appData.debugMode) {
8838                 fprintf(debugFP,
8839                         "Ignoring move out of turn by %s, gameMode %d"
8840                         ", forwardMost %d\n",
8841                         cps->which, gameMode, forwardMostMove);
8842             }
8843             return;
8844         }
8845       }
8846
8847         if(cps->alphaRank) AlphaRank(machineMove, 4);
8848
8849         // [HGM] lion: (some very limited) support for Alien protocol
8850         killX = killY = kill2X = kill2Y = -1;
8851         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8852             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8853             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8854             return;
8855         }
8856         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8857             char *q = strchr(p+1, ',');            // second comma?
8858             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8859             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8860             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8861         }
8862         if(firstLeg[0]) { // there was a previous leg;
8863             // only support case where same piece makes two step
8864             char buf[20], *p = machineMove+1, *q = buf+1, f;
8865             safeStrCpy(buf, machineMove, 20);
8866             while(isdigit(*q)) q++; // find start of to-square
8867             safeStrCpy(machineMove, firstLeg, 20);
8868             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8869             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
8870             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)
8871             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8872             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8873             firstLeg[0] = NULLCHAR; legs = 0;
8874         }
8875
8876         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8877                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8878             /* Machine move could not be parsed; ignore it. */
8879           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8880                     machineMove, _(cps->which));
8881             DisplayMoveError(buf1);
8882             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8883                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8884             if (gameMode == TwoMachinesPlay) {
8885               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8886                        buf1, GE_XBOARD);
8887             }
8888             return;
8889         }
8890
8891         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8892         /* So we have to redo legality test with true e.p. status here,  */
8893         /* to make sure an illegal e.p. capture does not slip through,   */
8894         /* to cause a forfeit on a justified illegal-move complaint      */
8895         /* of the opponent.                                              */
8896         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8897            ChessMove moveType;
8898            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8899                              fromY, fromX, toY, toX, promoChar);
8900             if(moveType == IllegalMove) {
8901               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8902                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8903                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8904                            buf1, GE_XBOARD);
8905                 return;
8906            } else if(!appData.fischerCastling)
8907            /* [HGM] Kludge to handle engines that send FRC-style castling
8908               when they shouldn't (like TSCP-Gothic) */
8909            switch(moveType) {
8910              case WhiteASideCastleFR:
8911              case BlackASideCastleFR:
8912                toX+=2;
8913                currentMoveString[2]++;
8914                break;
8915              case WhiteHSideCastleFR:
8916              case BlackHSideCastleFR:
8917                toX--;
8918                currentMoveString[2]--;
8919                break;
8920              default: ; // nothing to do, but suppresses warning of pedantic compilers
8921            }
8922         }
8923         hintRequested = FALSE;
8924         lastHint[0] = NULLCHAR;
8925         bookRequested = FALSE;
8926         /* Program may be pondering now */
8927         cps->maybeThinking = TRUE;
8928         if (cps->sendTime == 2) cps->sendTime = 1;
8929         if (cps->offeredDraw) cps->offeredDraw--;
8930
8931         /* [AS] Save move info*/
8932         pvInfoList[ forwardMostMove ].score = programStats.score;
8933         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8934         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8935
8936         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8937
8938         /* Test suites abort the 'game' after one move */
8939         if(*appData.finger) {
8940            static FILE *f;
8941            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8942            if(!f) f = fopen(appData.finger, "w");
8943            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8944            else { DisplayFatalError("Bad output file", errno, 0); return; }
8945            free(fen);
8946            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8947         }
8948         if(appData.epd) {
8949            if(solvingTime >= 0) {
8950               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8951               totalTime += solvingTime; first.matchWins++;
8952            } else {
8953               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8954               second.matchWins++;
8955            }
8956            OutputKibitz(2, buf1);
8957            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8958         }
8959
8960         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8961         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8962             int count = 0;
8963
8964             while( count < adjudicateLossPlies ) {
8965                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8966
8967                 if( count & 1 ) {
8968                     score = -score; /* Flip score for winning side */
8969                 }
8970
8971                 if( score > appData.adjudicateLossThreshold ) {
8972                     break;
8973                 }
8974
8975                 count++;
8976             }
8977
8978             if( count >= adjudicateLossPlies ) {
8979                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8980
8981                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8982                     "Xboard adjudication",
8983                     GE_XBOARD );
8984
8985                 return;
8986             }
8987         }
8988
8989         if(Adjudicate(cps)) {
8990             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8991             return; // [HGM] adjudicate: for all automatic game ends
8992         }
8993
8994 #if ZIPPY
8995         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8996             first.initDone) {
8997           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8998                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8999                 SendToICS("draw ");
9000                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9001           }
9002           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9003           ics_user_moved = 1;
9004           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9005                 char buf[3*MSG_SIZ];
9006
9007                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9008                         programStats.score / 100.,
9009                         programStats.depth,
9010                         programStats.time / 100.,
9011                         (unsigned int)programStats.nodes,
9012                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9013                         programStats.movelist);
9014                 SendToICS(buf);
9015           }
9016         }
9017 #endif
9018
9019         /* [AS] Clear stats for next move */
9020         ClearProgramStats();
9021         thinkOutput[0] = NULLCHAR;
9022         hiddenThinkOutputState = 0;
9023
9024         bookHit = NULL;
9025         if (gameMode == TwoMachinesPlay) {
9026             /* [HGM] relaying draw offers moved to after reception of move */
9027             /* and interpreting offer as claim if it brings draw condition */
9028             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9029                 SendToProgram("draw\n", cps->other);
9030             }
9031             if (cps->other->sendTime) {
9032                 SendTimeRemaining(cps->other,
9033                                   cps->other->twoMachinesColor[0] == 'w');
9034             }
9035             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9036             if (firstMove && !bookHit) {
9037                 firstMove = FALSE;
9038                 if (cps->other->useColors) {
9039                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9040                 }
9041                 SendToProgram("go\n", cps->other);
9042             }
9043             cps->other->maybeThinking = TRUE;
9044         }
9045
9046         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9047
9048         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9049
9050         if (!pausing && appData.ringBellAfterMoves) {
9051             if(!roar) RingBell();
9052         }
9053
9054         /*
9055          * Reenable menu items that were disabled while
9056          * machine was thinking
9057          */
9058         if (gameMode != TwoMachinesPlay)
9059             SetUserThinkingEnables();
9060
9061         // [HGM] book: after book hit opponent has received move and is now in force mode
9062         // force the book reply into it, and then fake that it outputted this move by jumping
9063         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9064         if(bookHit) {
9065                 static char bookMove[MSG_SIZ]; // a bit generous?
9066
9067                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9068                 strcat(bookMove, bookHit);
9069                 message = bookMove;
9070                 cps = cps->other;
9071                 programStats.nodes = programStats.depth = programStats.time =
9072                 programStats.score = programStats.got_only_move = 0;
9073                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9074
9075                 if(cps->lastPing != cps->lastPong) {
9076                     savedMessage = message; // args for deferred call
9077                     savedState = cps;
9078                     ScheduleDelayedEvent(DeferredBookMove, 10);
9079                     return;
9080                 }
9081                 goto FakeBookMove;
9082         }
9083
9084         return;
9085     }
9086
9087     /* Set special modes for chess engines.  Later something general
9088      *  could be added here; for now there is just one kludge feature,
9089      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9090      *  when "xboard" is given as an interactive command.
9091      */
9092     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9093         cps->useSigint = FALSE;
9094         cps->useSigterm = FALSE;
9095     }
9096     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9097       ParseFeatures(message+8, cps);
9098       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9099     }
9100
9101     if (!strncmp(message, "setup ", 6) && 
9102         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9103           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9104                                         ) { // [HGM] allow first engine to define opening position
9105       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9106       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9107       *buf = NULLCHAR;
9108       if(sscanf(message, "setup (%s", buf) == 1) {
9109         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9110         ASSIGN(appData.pieceToCharTable, buf);
9111       }
9112       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9113       if(dummy >= 3) {
9114         while(message[s] && message[s++] != ' ');
9115         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9116            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9117             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9118             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9119           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9120           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9121           startedFromSetupPosition = FALSE;
9122         }
9123       }
9124       if(startedFromSetupPosition) return;
9125       ParseFEN(boards[0], &dummy, message+s, FALSE);
9126       DrawPosition(TRUE, boards[0]);
9127       CopyBoard(initialPosition, boards[0]);
9128       startedFromSetupPosition = TRUE;
9129       return;
9130     }
9131     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9132       ChessSquare piece = WhitePawn;
9133       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9134       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9135       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9136       piece += CharToPiece(ID & 255) - WhitePawn;
9137       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9138       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9139       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9140       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9141       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9142       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9143                                                && gameInfo.variant != VariantGreat
9144                                                && gameInfo.variant != VariantFairy    ) return;
9145       if(piece < EmptySquare) {
9146         pieceDefs = TRUE;
9147         ASSIGN(pieceDesc[piece], buf1);
9148         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9149       }
9150       return;
9151     }
9152     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9153       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9154       Sweep(0);
9155       return;
9156     }
9157     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9158      * want this, I was asked to put it in, and obliged.
9159      */
9160     if (!strncmp(message, "setboard ", 9)) {
9161         Board initial_position;
9162
9163         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9164
9165         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9166             DisplayError(_("Bad FEN received from engine"), 0);
9167             return ;
9168         } else {
9169            Reset(TRUE, FALSE);
9170            CopyBoard(boards[0], initial_position);
9171            initialRulePlies = FENrulePlies;
9172            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9173            else gameMode = MachinePlaysBlack;
9174            DrawPosition(FALSE, boards[currentMove]);
9175         }
9176         return;
9177     }
9178
9179     /*
9180      * Look for communication commands
9181      */
9182     if (!strncmp(message, "telluser ", 9)) {
9183         if(message[9] == '\\' && message[10] == '\\')
9184             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9185         PlayTellSound();
9186         DisplayNote(message + 9);
9187         return;
9188     }
9189     if (!strncmp(message, "tellusererror ", 14)) {
9190         cps->userError = 1;
9191         if(message[14] == '\\' && message[15] == '\\')
9192             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9193         PlayTellSound();
9194         DisplayError(message + 14, 0);
9195         return;
9196     }
9197     if (!strncmp(message, "tellopponent ", 13)) {
9198       if (appData.icsActive) {
9199         if (loggedOn) {
9200           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9201           SendToICS(buf1);
9202         }
9203       } else {
9204         DisplayNote(message + 13);
9205       }
9206       return;
9207     }
9208     if (!strncmp(message, "tellothers ", 11)) {
9209       if (appData.icsActive) {
9210         if (loggedOn) {
9211           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9212           SendToICS(buf1);
9213         }
9214       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9215       return;
9216     }
9217     if (!strncmp(message, "tellall ", 8)) {
9218       if (appData.icsActive) {
9219         if (loggedOn) {
9220           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9221           SendToICS(buf1);
9222         }
9223       } else {
9224         DisplayNote(message + 8);
9225       }
9226       return;
9227     }
9228     if (strncmp(message, "warning", 7) == 0) {
9229         /* Undocumented feature, use tellusererror in new code */
9230         DisplayError(message, 0);
9231         return;
9232     }
9233     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9234         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9235         strcat(realname, " query");
9236         AskQuestion(realname, buf2, buf1, cps->pr);
9237         return;
9238     }
9239     /* Commands from the engine directly to ICS.  We don't allow these to be
9240      *  sent until we are logged on. Crafty kibitzes have been known to
9241      *  interfere with the login process.
9242      */
9243     if (loggedOn) {
9244         if (!strncmp(message, "tellics ", 8)) {
9245             SendToICS(message + 8);
9246             SendToICS("\n");
9247             return;
9248         }
9249         if (!strncmp(message, "tellicsnoalias ", 15)) {
9250             SendToICS(ics_prefix);
9251             SendToICS(message + 15);
9252             SendToICS("\n");
9253             return;
9254         }
9255         /* The following are for backward compatibility only */
9256         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9257             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9258             SendToICS(ics_prefix);
9259             SendToICS(message);
9260             SendToICS("\n");
9261             return;
9262         }
9263     }
9264     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9265         if(initPing == cps->lastPong) {
9266             if(gameInfo.variant == VariantUnknown) {
9267                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9268                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9269                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9270             }
9271             initPing = -1;
9272         }
9273         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9274             abortEngineThink = FALSE;
9275             DisplayMessage("", "");
9276             ThawUI();
9277         }
9278         return;
9279     }
9280     if(!strncmp(message, "highlight ", 10)) {
9281         if(appData.testLegality && !*engineVariant && appData.markers) return;
9282         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9283         return;
9284     }
9285     if(!strncmp(message, "click ", 6)) {
9286         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9287         if(appData.testLegality || !appData.oneClick) return;
9288         sscanf(message+6, "%c%d%c", &f, &y, &c);
9289         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9290         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9291         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9292         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9293         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9294         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9295             LeftClick(Release, lastLeftX, lastLeftY);
9296         controlKey  = (c == ',');
9297         LeftClick(Press, x, y);
9298         LeftClick(Release, x, y);
9299         first.highlight = f;
9300         return;
9301     }
9302     /*
9303      * If the move is illegal, cancel it and redraw the board.
9304      * Also deal with other error cases.  Matching is rather loose
9305      * here to accommodate engines written before the spec.
9306      */
9307     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9308         strncmp(message, "Error", 5) == 0) {
9309         if (StrStr(message, "name") ||
9310             StrStr(message, "rating") || StrStr(message, "?") ||
9311             StrStr(message, "result") || StrStr(message, "board") ||
9312             StrStr(message, "bk") || StrStr(message, "computer") ||
9313             StrStr(message, "variant") || StrStr(message, "hint") ||
9314             StrStr(message, "random") || StrStr(message, "depth") ||
9315             StrStr(message, "accepted")) {
9316             return;
9317         }
9318         if (StrStr(message, "protover")) {
9319           /* Program is responding to input, so it's apparently done
9320              initializing, and this error message indicates it is
9321              protocol version 1.  So we don't need to wait any longer
9322              for it to initialize and send feature commands. */
9323           FeatureDone(cps, 1);
9324           cps->protocolVersion = 1;
9325           return;
9326         }
9327         cps->maybeThinking = FALSE;
9328
9329         if (StrStr(message, "draw")) {
9330             /* Program doesn't have "draw" command */
9331             cps->sendDrawOffers = 0;
9332             return;
9333         }
9334         if (cps->sendTime != 1 &&
9335             (StrStr(message, "time") || StrStr(message, "otim"))) {
9336           /* Program apparently doesn't have "time" or "otim" command */
9337           cps->sendTime = 0;
9338           return;
9339         }
9340         if (StrStr(message, "analyze")) {
9341             cps->analysisSupport = FALSE;
9342             cps->analyzing = FALSE;
9343 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9344             EditGameEvent(); // [HGM] try to preserve loaded game
9345             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9346             DisplayError(buf2, 0);
9347             return;
9348         }
9349         if (StrStr(message, "(no matching move)st")) {
9350           /* Special kludge for GNU Chess 4 only */
9351           cps->stKludge = TRUE;
9352           SendTimeControl(cps, movesPerSession, timeControl,
9353                           timeIncrement, appData.searchDepth,
9354                           searchTime);
9355           return;
9356         }
9357         if (StrStr(message, "(no matching move)sd")) {
9358           /* Special kludge for GNU Chess 4 only */
9359           cps->sdKludge = TRUE;
9360           SendTimeControl(cps, movesPerSession, timeControl,
9361                           timeIncrement, appData.searchDepth,
9362                           searchTime);
9363           return;
9364         }
9365         if (!StrStr(message, "llegal")) {
9366             return;
9367         }
9368         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9369             gameMode == IcsIdle) return;
9370         if (forwardMostMove <= backwardMostMove) return;
9371         if (pausing) PauseEvent();
9372       if(appData.forceIllegal) {
9373             // [HGM] illegal: machine refused move; force position after move into it
9374           SendToProgram("force\n", cps);
9375           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9376                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9377                 // when black is to move, while there might be nothing on a2 or black
9378                 // might already have the move. So send the board as if white has the move.
9379                 // But first we must change the stm of the engine, as it refused the last move
9380                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9381                 if(WhiteOnMove(forwardMostMove)) {
9382                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9383                     SendBoard(cps, forwardMostMove); // kludgeless board
9384                 } else {
9385                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9386                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9387                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9388                 }
9389           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9390             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9391                  gameMode == TwoMachinesPlay)
9392               SendToProgram("go\n", cps);
9393             return;
9394       } else
9395         if (gameMode == PlayFromGameFile) {
9396             /* Stop reading this game file */
9397             gameMode = EditGame;
9398             ModeHighlight();
9399         }
9400         /* [HGM] illegal-move claim should forfeit game when Xboard */
9401         /* only passes fully legal moves                            */
9402         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9403             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9404                                 "False illegal-move claim", GE_XBOARD );
9405             return; // do not take back move we tested as valid
9406         }
9407         currentMove = forwardMostMove-1;
9408         DisplayMove(currentMove-1); /* before DisplayMoveError */
9409         SwitchClocks(forwardMostMove-1); // [HGM] race
9410         DisplayBothClocks();
9411         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9412                 parseList[currentMove], _(cps->which));
9413         DisplayMoveError(buf1);
9414         DrawPosition(FALSE, boards[currentMove]);
9415
9416         SetUserThinkingEnables();
9417         return;
9418     }
9419     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9420         /* Program has a broken "time" command that
9421            outputs a string not ending in newline.
9422            Don't use it. */
9423         cps->sendTime = 0;
9424     }
9425     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9426         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9427             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9428     }
9429
9430     /*
9431      * If chess program startup fails, exit with an error message.
9432      * Attempts to recover here are futile. [HGM] Well, we try anyway
9433      */
9434     if ((StrStr(message, "unknown host") != NULL)
9435         || (StrStr(message, "No remote directory") != NULL)
9436         || (StrStr(message, "not found") != NULL)
9437         || (StrStr(message, "No such file") != NULL)
9438         || (StrStr(message, "can't alloc") != NULL)
9439         || (StrStr(message, "Permission denied") != NULL)) {
9440
9441         cps->maybeThinking = FALSE;
9442         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9443                 _(cps->which), cps->program, cps->host, message);
9444         RemoveInputSource(cps->isr);
9445         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9446             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9447             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9448         }
9449         return;
9450     }
9451
9452     /*
9453      * Look for hint output
9454      */
9455     if (sscanf(message, "Hint: %s", buf1) == 1) {
9456         if (cps == &first && hintRequested) {
9457             hintRequested = FALSE;
9458             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9459                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9460                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9461                                     PosFlags(forwardMostMove),
9462                                     fromY, fromX, toY, toX, promoChar, buf1);
9463                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9464                 DisplayInformation(buf2);
9465             } else {
9466                 /* Hint move could not be parsed!? */
9467               snprintf(buf2, sizeof(buf2),
9468                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9469                         buf1, _(cps->which));
9470                 DisplayError(buf2, 0);
9471             }
9472         } else {
9473           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9474         }
9475         return;
9476     }
9477
9478     /*
9479      * Ignore other messages if game is not in progress
9480      */
9481     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9482         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9483
9484     /*
9485      * look for win, lose, draw, or draw offer
9486      */
9487     if (strncmp(message, "1-0", 3) == 0) {
9488         char *p, *q, *r = "";
9489         p = strchr(message, '{');
9490         if (p) {
9491             q = strchr(p, '}');
9492             if (q) {
9493                 *q = NULLCHAR;
9494                 r = p + 1;
9495             }
9496         }
9497         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9498         return;
9499     } else if (strncmp(message, "0-1", 3) == 0) {
9500         char *p, *q, *r = "";
9501         p = strchr(message, '{');
9502         if (p) {
9503             q = strchr(p, '}');
9504             if (q) {
9505                 *q = NULLCHAR;
9506                 r = p + 1;
9507             }
9508         }
9509         /* Kludge for Arasan 4.1 bug */
9510         if (strcmp(r, "Black resigns") == 0) {
9511             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9512             return;
9513         }
9514         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9515         return;
9516     } else if (strncmp(message, "1/2", 3) == 0) {
9517         char *p, *q, *r = "";
9518         p = strchr(message, '{');
9519         if (p) {
9520             q = strchr(p, '}');
9521             if (q) {
9522                 *q = NULLCHAR;
9523                 r = p + 1;
9524             }
9525         }
9526
9527         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9528         return;
9529
9530     } else if (strncmp(message, "White resign", 12) == 0) {
9531         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9532         return;
9533     } else if (strncmp(message, "Black resign", 12) == 0) {
9534         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9535         return;
9536     } else if (strncmp(message, "White matches", 13) == 0 ||
9537                strncmp(message, "Black matches", 13) == 0   ) {
9538         /* [HGM] ignore GNUShogi noises */
9539         return;
9540     } else if (strncmp(message, "White", 5) == 0 &&
9541                message[5] != '(' &&
9542                StrStr(message, "Black") == NULL) {
9543         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9544         return;
9545     } else if (strncmp(message, "Black", 5) == 0 &&
9546                message[5] != '(') {
9547         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9548         return;
9549     } else if (strcmp(message, "resign") == 0 ||
9550                strcmp(message, "computer resigns") == 0) {
9551         switch (gameMode) {
9552           case MachinePlaysBlack:
9553           case IcsPlayingBlack:
9554             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9555             break;
9556           case MachinePlaysWhite:
9557           case IcsPlayingWhite:
9558             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9559             break;
9560           case TwoMachinesPlay:
9561             if (cps->twoMachinesColor[0] == 'w')
9562               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9563             else
9564               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9565             break;
9566           default:
9567             /* can't happen */
9568             break;
9569         }
9570         return;
9571     } else if (strncmp(message, "opponent mates", 14) == 0) {
9572         switch (gameMode) {
9573           case MachinePlaysBlack:
9574           case IcsPlayingBlack:
9575             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9576             break;
9577           case MachinePlaysWhite:
9578           case IcsPlayingWhite:
9579             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9580             break;
9581           case TwoMachinesPlay:
9582             if (cps->twoMachinesColor[0] == 'w')
9583               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9584             else
9585               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9586             break;
9587           default:
9588             /* can't happen */
9589             break;
9590         }
9591         return;
9592     } else if (strncmp(message, "computer mates", 14) == 0) {
9593         switch (gameMode) {
9594           case MachinePlaysBlack:
9595           case IcsPlayingBlack:
9596             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9597             break;
9598           case MachinePlaysWhite:
9599           case IcsPlayingWhite:
9600             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9601             break;
9602           case TwoMachinesPlay:
9603             if (cps->twoMachinesColor[0] == 'w')
9604               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9605             else
9606               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9607             break;
9608           default:
9609             /* can't happen */
9610             break;
9611         }
9612         return;
9613     } else if (strncmp(message, "checkmate", 9) == 0) {
9614         if (WhiteOnMove(forwardMostMove)) {
9615             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9616         } else {
9617             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9618         }
9619         return;
9620     } else if (strstr(message, "Draw") != NULL ||
9621                strstr(message, "game is a draw") != NULL) {
9622         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9623         return;
9624     } else if (strstr(message, "offer") != NULL &&
9625                strstr(message, "draw") != NULL) {
9626 #if ZIPPY
9627         if (appData.zippyPlay && first.initDone) {
9628             /* Relay offer to ICS */
9629             SendToICS(ics_prefix);
9630             SendToICS("draw\n");
9631         }
9632 #endif
9633         cps->offeredDraw = 2; /* valid until this engine moves twice */
9634         if (gameMode == TwoMachinesPlay) {
9635             if (cps->other->offeredDraw) {
9636                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9637             /* [HGM] in two-machine mode we delay relaying draw offer      */
9638             /* until after we also have move, to see if it is really claim */
9639             }
9640         } else if (gameMode == MachinePlaysWhite ||
9641                    gameMode == MachinePlaysBlack) {
9642           if (userOfferedDraw) {
9643             DisplayInformation(_("Machine accepts your draw offer"));
9644             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9645           } else {
9646             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9647           }
9648         }
9649     }
9650
9651
9652     /*
9653      * Look for thinking output
9654      */
9655     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9656           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9657                                 ) {
9658         int plylev, mvleft, mvtot, curscore, time;
9659         char mvname[MOVE_LEN];
9660         u64 nodes; // [DM]
9661         char plyext;
9662         int ignore = FALSE;
9663         int prefixHint = FALSE;
9664         mvname[0] = NULLCHAR;
9665
9666         switch (gameMode) {
9667           case MachinePlaysBlack:
9668           case IcsPlayingBlack:
9669             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9670             break;
9671           case MachinePlaysWhite:
9672           case IcsPlayingWhite:
9673             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9674             break;
9675           case AnalyzeMode:
9676           case AnalyzeFile:
9677             break;
9678           case IcsObserving: /* [DM] icsEngineAnalyze */
9679             if (!appData.icsEngineAnalyze) ignore = TRUE;
9680             break;
9681           case TwoMachinesPlay:
9682             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9683                 ignore = TRUE;
9684             }
9685             break;
9686           default:
9687             ignore = TRUE;
9688             break;
9689         }
9690
9691         if (!ignore) {
9692             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9693             buf1[0] = NULLCHAR;
9694             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9695                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9696                 char score_buf[MSG_SIZ];
9697
9698                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9699                     nodes += u64Const(0x100000000);
9700
9701                 if (plyext != ' ' && plyext != '\t') {
9702                     time *= 100;
9703                 }
9704
9705                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9706                 if( cps->scoreIsAbsolute &&
9707                     ( gameMode == MachinePlaysBlack ||
9708                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9709                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9710                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9711                      !WhiteOnMove(currentMove)
9712                     ) )
9713                 {
9714                     curscore = -curscore;
9715                 }
9716
9717                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9718
9719                 if(*bestMove) { // rememer time best EPD move was first found
9720                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9721                     ChessMove mt;
9722                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9723                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9724                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9725                 }
9726
9727                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9728                         char buf[MSG_SIZ];
9729                         FILE *f;
9730                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9731                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9732                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9733                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9734                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9735                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9736                                 fclose(f);
9737                         }
9738                         else
9739                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9740                           DisplayError(_("failed writing PV"), 0);
9741                 }
9742
9743                 tempStats.depth = plylev;
9744                 tempStats.nodes = nodes;
9745                 tempStats.time = time;
9746                 tempStats.score = curscore;
9747                 tempStats.got_only_move = 0;
9748
9749                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9750                         int ticklen;
9751
9752                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9753                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9754                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9755                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9756                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9757                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9758                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9759                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9760                 }
9761
9762                 /* Buffer overflow protection */
9763                 if (pv[0] != NULLCHAR) {
9764                     if (strlen(pv) >= sizeof(tempStats.movelist)
9765                         && appData.debugMode) {
9766                         fprintf(debugFP,
9767                                 "PV is too long; using the first %u bytes.\n",
9768                                 (unsigned) sizeof(tempStats.movelist) - 1);
9769                     }
9770
9771                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9772                 } else {
9773                     sprintf(tempStats.movelist, " no PV\n");
9774                 }
9775
9776                 if (tempStats.seen_stat) {
9777                     tempStats.ok_to_send = 1;
9778                 }
9779
9780                 if (strchr(tempStats.movelist, '(') != NULL) {
9781                     tempStats.line_is_book = 1;
9782                     tempStats.nr_moves = 0;
9783                     tempStats.moves_left = 0;
9784                 } else {
9785                     tempStats.line_is_book = 0;
9786                 }
9787
9788                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9789                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9790
9791                 SendProgramStatsToFrontend( cps, &tempStats );
9792
9793                 /*
9794                     [AS] Protect the thinkOutput buffer from overflow... this
9795                     is only useful if buf1 hasn't overflowed first!
9796                 */
9797                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9798                 if(curscore >= MATE_SCORE) 
9799                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9800                 else if(curscore <= -MATE_SCORE) 
9801                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9802                 else
9803                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9804                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9805                          plylev,
9806                          (gameMode == TwoMachinesPlay ?
9807                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9808                          score_buf,
9809                          prefixHint ? lastHint : "",
9810                          prefixHint ? " " : "" );
9811
9812                 if( buf1[0] != NULLCHAR ) {
9813                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9814
9815                     if( strlen(pv) > max_len ) {
9816                         if( appData.debugMode) {
9817                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9818                         }
9819                         pv[max_len+1] = '\0';
9820                     }
9821
9822                     strcat( thinkOutput, pv);
9823                 }
9824
9825                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9826                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9827                     DisplayMove(currentMove - 1);
9828                 }
9829                 return;
9830
9831             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9832                 /* crafty (9.25+) says "(only move) <move>"
9833                  * if there is only 1 legal move
9834                  */
9835                 sscanf(p, "(only move) %s", buf1);
9836                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9837                 sprintf(programStats.movelist, "%s (only move)", buf1);
9838                 programStats.depth = 1;
9839                 programStats.nr_moves = 1;
9840                 programStats.moves_left = 1;
9841                 programStats.nodes = 1;
9842                 programStats.time = 1;
9843                 programStats.got_only_move = 1;
9844
9845                 /* Not really, but we also use this member to
9846                    mean "line isn't going to change" (Crafty
9847                    isn't searching, so stats won't change) */
9848                 programStats.line_is_book = 1;
9849
9850                 SendProgramStatsToFrontend( cps, &programStats );
9851
9852                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9853                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9854                     DisplayMove(currentMove - 1);
9855                 }
9856                 return;
9857             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9858                               &time, &nodes, &plylev, &mvleft,
9859                               &mvtot, mvname) >= 5) {
9860                 /* The stat01: line is from Crafty (9.29+) in response
9861                    to the "." command */
9862                 programStats.seen_stat = 1;
9863                 cps->maybeThinking = TRUE;
9864
9865                 if (programStats.got_only_move || !appData.periodicUpdates)
9866                   return;
9867
9868                 programStats.depth = plylev;
9869                 programStats.time = time;
9870                 programStats.nodes = nodes;
9871                 programStats.moves_left = mvleft;
9872                 programStats.nr_moves = mvtot;
9873                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9874                 programStats.ok_to_send = 1;
9875                 programStats.movelist[0] = '\0';
9876
9877                 SendProgramStatsToFrontend( cps, &programStats );
9878
9879                 return;
9880
9881             } else if (strncmp(message,"++",2) == 0) {
9882                 /* Crafty 9.29+ outputs this */
9883                 programStats.got_fail = 2;
9884                 return;
9885
9886             } else if (strncmp(message,"--",2) == 0) {
9887                 /* Crafty 9.29+ outputs this */
9888                 programStats.got_fail = 1;
9889                 return;
9890
9891             } else if (thinkOutput[0] != NULLCHAR &&
9892                        strncmp(message, "    ", 4) == 0) {
9893                 unsigned message_len;
9894
9895                 p = message;
9896                 while (*p && *p == ' ') p++;
9897
9898                 message_len = strlen( p );
9899
9900                 /* [AS] Avoid buffer overflow */
9901                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9902                     strcat(thinkOutput, " ");
9903                     strcat(thinkOutput, p);
9904                 }
9905
9906                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9907                     strcat(programStats.movelist, " ");
9908                     strcat(programStats.movelist, p);
9909                 }
9910
9911                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9912                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9913                     DisplayMove(currentMove - 1);
9914                 }
9915                 return;
9916             }
9917         }
9918         else {
9919             buf1[0] = NULLCHAR;
9920
9921             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9922                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9923             {
9924                 ChessProgramStats cpstats;
9925
9926                 if (plyext != ' ' && plyext != '\t') {
9927                     time *= 100;
9928                 }
9929
9930                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9931                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9932                     curscore = -curscore;
9933                 }
9934
9935                 cpstats.depth = plylev;
9936                 cpstats.nodes = nodes;
9937                 cpstats.time = time;
9938                 cpstats.score = curscore;
9939                 cpstats.got_only_move = 0;
9940                 cpstats.movelist[0] = '\0';
9941
9942                 if (buf1[0] != NULLCHAR) {
9943                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9944                 }
9945
9946                 cpstats.ok_to_send = 0;
9947                 cpstats.line_is_book = 0;
9948                 cpstats.nr_moves = 0;
9949                 cpstats.moves_left = 0;
9950
9951                 SendProgramStatsToFrontend( cps, &cpstats );
9952             }
9953         }
9954     }
9955 }
9956
9957
9958 /* Parse a game score from the character string "game", and
9959    record it as the history of the current game.  The game
9960    score is NOT assumed to start from the standard position.
9961    The display is not updated in any way.
9962    */
9963 void
9964 ParseGameHistory (char *game)
9965 {
9966     ChessMove moveType;
9967     int fromX, fromY, toX, toY, boardIndex;
9968     char promoChar;
9969     char *p, *q;
9970     char buf[MSG_SIZ];
9971
9972     if (appData.debugMode)
9973       fprintf(debugFP, "Parsing game history: %s\n", game);
9974
9975     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9976     gameInfo.site = StrSave(appData.icsHost);
9977     gameInfo.date = PGNDate();
9978     gameInfo.round = StrSave("-");
9979
9980     /* Parse out names of players */
9981     while (*game == ' ') game++;
9982     p = buf;
9983     while (*game != ' ') *p++ = *game++;
9984     *p = NULLCHAR;
9985     gameInfo.white = StrSave(buf);
9986     while (*game == ' ') game++;
9987     p = buf;
9988     while (*game != ' ' && *game != '\n') *p++ = *game++;
9989     *p = NULLCHAR;
9990     gameInfo.black = StrSave(buf);
9991
9992     /* Parse moves */
9993     boardIndex = blackPlaysFirst ? 1 : 0;
9994     yynewstr(game);
9995     for (;;) {
9996         yyboardindex = boardIndex;
9997         moveType = (ChessMove) Myylex();
9998         switch (moveType) {
9999           case IllegalMove:             /* maybe suicide chess, etc. */
10000   if (appData.debugMode) {
10001     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10002     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10003     setbuf(debugFP, NULL);
10004   }
10005           case WhitePromotion:
10006           case BlackPromotion:
10007           case WhiteNonPromotion:
10008           case BlackNonPromotion:
10009           case NormalMove:
10010           case FirstLeg:
10011           case WhiteCapturesEnPassant:
10012           case BlackCapturesEnPassant:
10013           case WhiteKingSideCastle:
10014           case WhiteQueenSideCastle:
10015           case BlackKingSideCastle:
10016           case BlackQueenSideCastle:
10017           case WhiteKingSideCastleWild:
10018           case WhiteQueenSideCastleWild:
10019           case BlackKingSideCastleWild:
10020           case BlackQueenSideCastleWild:
10021           /* PUSH Fabien */
10022           case WhiteHSideCastleFR:
10023           case WhiteASideCastleFR:
10024           case BlackHSideCastleFR:
10025           case BlackASideCastleFR:
10026           /* POP Fabien */
10027             fromX = currentMoveString[0] - AAA;
10028             fromY = currentMoveString[1] - ONE;
10029             toX = currentMoveString[2] - AAA;
10030             toY = currentMoveString[3] - ONE;
10031             promoChar = currentMoveString[4];
10032             break;
10033           case WhiteDrop:
10034           case BlackDrop:
10035             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10036             fromX = moveType == WhiteDrop ?
10037               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10038             (int) CharToPiece(ToLower(currentMoveString[0]));
10039             fromY = DROP_RANK;
10040             toX = currentMoveString[2] - AAA;
10041             toY = currentMoveString[3] - ONE;
10042             promoChar = NULLCHAR;
10043             break;
10044           case AmbiguousMove:
10045             /* bug? */
10046             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10047   if (appData.debugMode) {
10048     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10049     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10050     setbuf(debugFP, NULL);
10051   }
10052             DisplayError(buf, 0);
10053             return;
10054           case ImpossibleMove:
10055             /* bug? */
10056             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10057   if (appData.debugMode) {
10058     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10059     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10060     setbuf(debugFP, NULL);
10061   }
10062             DisplayError(buf, 0);
10063             return;
10064           case EndOfFile:
10065             if (boardIndex < backwardMostMove) {
10066                 /* Oops, gap.  How did that happen? */
10067                 DisplayError(_("Gap in move list"), 0);
10068                 return;
10069             }
10070             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10071             if (boardIndex > forwardMostMove) {
10072                 forwardMostMove = boardIndex;
10073             }
10074             return;
10075           case ElapsedTime:
10076             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10077                 strcat(parseList[boardIndex-1], " ");
10078                 strcat(parseList[boardIndex-1], yy_text);
10079             }
10080             continue;
10081           case Comment:
10082           case PGNTag:
10083           case NAG:
10084           default:
10085             /* ignore */
10086             continue;
10087           case WhiteWins:
10088           case BlackWins:
10089           case GameIsDrawn:
10090           case GameUnfinished:
10091             if (gameMode == IcsExamining) {
10092                 if (boardIndex < backwardMostMove) {
10093                     /* Oops, gap.  How did that happen? */
10094                     return;
10095                 }
10096                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10097                 return;
10098             }
10099             gameInfo.result = moveType;
10100             p = strchr(yy_text, '{');
10101             if (p == NULL) p = strchr(yy_text, '(');
10102             if (p == NULL) {
10103                 p = yy_text;
10104                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10105             } else {
10106                 q = strchr(p, *p == '{' ? '}' : ')');
10107                 if (q != NULL) *q = NULLCHAR;
10108                 p++;
10109             }
10110             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10111             gameInfo.resultDetails = StrSave(p);
10112             continue;
10113         }
10114         if (boardIndex >= forwardMostMove &&
10115             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10116             backwardMostMove = blackPlaysFirst ? 1 : 0;
10117             return;
10118         }
10119         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10120                                  fromY, fromX, toY, toX, promoChar,
10121                                  parseList[boardIndex]);
10122         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10123         /* currentMoveString is set as a side-effect of yylex */
10124         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10125         strcat(moveList[boardIndex], "\n");
10126         boardIndex++;
10127         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10128         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10129           case MT_NONE:
10130           case MT_STALEMATE:
10131           default:
10132             break;
10133           case MT_CHECK:
10134             if(!IS_SHOGI(gameInfo.variant))
10135                 strcat(parseList[boardIndex - 1], "+");
10136             break;
10137           case MT_CHECKMATE:
10138           case MT_STAINMATE:
10139             strcat(parseList[boardIndex - 1], "#");
10140             break;
10141         }
10142     }
10143 }
10144
10145
10146 /* Apply a move to the given board  */
10147 void
10148 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10149 {
10150   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10151   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10152
10153     /* [HGM] compute & store e.p. status and castling rights for new position */
10154     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10155
10156       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10157       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10158       board[EP_STATUS] = EP_NONE;
10159       board[EP_FILE] = board[EP_RANK] = 100;
10160
10161   if (fromY == DROP_RANK) {
10162         /* must be first */
10163         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10164             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10165             return;
10166         }
10167         piece = board[toY][toX] = (ChessSquare) fromX;
10168   } else {
10169 //      ChessSquare victim;
10170       int i;
10171
10172       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10173 //           victim = board[killY][killX],
10174            killed = board[killY][killX],
10175            board[killY][killX] = EmptySquare,
10176            board[EP_STATUS] = EP_CAPTURE;
10177            if( kill2X >= 0 && kill2Y >= 0)
10178              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10179       }
10180
10181       if( board[toY][toX] != EmptySquare ) {
10182            board[EP_STATUS] = EP_CAPTURE;
10183            if( (fromX != toX || fromY != toY) && // not igui!
10184                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10185                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10186                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10187            }
10188       }
10189
10190       pawn = board[fromY][fromX];
10191       if( pawn == WhiteLance || pawn == BlackLance ) {
10192            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10193                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10194                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10195            }
10196       }
10197       if( pawn == WhitePawn ) {
10198            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10199                board[EP_STATUS] = EP_PAWN_MOVE;
10200            if( toY-fromY>=2) {
10201                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10202                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10203                         gameInfo.variant != VariantBerolina || toX < fromX)
10204                       board[EP_STATUS] = toX | berolina;
10205                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10206                         gameInfo.variant != VariantBerolina || toX > fromX)
10207                       board[EP_STATUS] = toX;
10208            }
10209       } else
10210       if( pawn == BlackPawn ) {
10211            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10212                board[EP_STATUS] = EP_PAWN_MOVE;
10213            if( toY-fromY<= -2) {
10214                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10215                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10216                         gameInfo.variant != VariantBerolina || toX < fromX)
10217                       board[EP_STATUS] = toX | berolina;
10218                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10219                         gameInfo.variant != VariantBerolina || toX > fromX)
10220                       board[EP_STATUS] = toX;
10221            }
10222        }
10223
10224        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10225        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10226        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10227        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10228
10229        for(i=0; i<nrCastlingRights; i++) {
10230            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10231               board[CASTLING][i] == toX   && castlingRank[i] == toY
10232              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10233        }
10234
10235        if(gameInfo.variant == VariantSChess) { // update virginity
10236            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10237            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10238            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10239            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10240        }
10241
10242      if (fromX == toX && fromY == toY && killX < 0) return;
10243
10244      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10245      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10246      if(gameInfo.variant == VariantKnightmate)
10247          king += (int) WhiteUnicorn - (int) WhiteKing;
10248
10249     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10250        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10251         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10252         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10253         board[EP_STATUS] = EP_NONE; // capture was fake!
10254     } else
10255     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10256         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10257         board[toY][toX] = piece;
10258         board[EP_STATUS] = EP_NONE; // capture was fake!
10259     } else
10260     /* Code added by Tord: */
10261     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10262     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10263         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10264       board[EP_STATUS] = EP_NONE; // capture was fake!
10265       board[fromY][fromX] = EmptySquare;
10266       board[toY][toX] = EmptySquare;
10267       if((toX > fromX) != (piece == WhiteRook)) {
10268         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10269       } else {
10270         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10271       }
10272     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10273                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10274       board[EP_STATUS] = EP_NONE;
10275       board[fromY][fromX] = EmptySquare;
10276       board[toY][toX] = EmptySquare;
10277       if((toX > fromX) != (piece == BlackRook)) {
10278         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10279       } else {
10280         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10281       }
10282     /* End of code added by Tord */
10283
10284     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10285         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10286         board[toY][toX] = piece;
10287     } else if (board[fromY][fromX] == king
10288         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10289         && toY == fromY && toX > fromX+1) {
10290         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10291         board[fromY][toX-1] = board[fromY][rookX];
10292         board[fromY][rookX] = EmptySquare;
10293         board[fromY][fromX] = EmptySquare;
10294         board[toY][toX] = king;
10295     } else if (board[fromY][fromX] == king
10296         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10297                && toY == fromY && toX < fromX-1) {
10298         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10299         board[fromY][toX+1] = board[fromY][rookX];
10300         board[fromY][rookX] = EmptySquare;
10301         board[fromY][fromX] = EmptySquare;
10302         board[toY][toX] = king;
10303     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10304                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10305                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10306                ) {
10307         /* white pawn promotion */
10308         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10309         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10310             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10311         board[fromY][fromX] = EmptySquare;
10312     } else if ((fromY >= BOARD_HEIGHT>>1)
10313                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10314                && (toX != fromX)
10315                && gameInfo.variant != VariantXiangqi
10316                && gameInfo.variant != VariantBerolina
10317                && (pawn == WhitePawn)
10318                && (board[toY][toX] == EmptySquare)) {
10319         board[fromY][fromX] = EmptySquare;
10320         board[toY][toX] = piece;
10321         if(toY == epRank - 128 + 1)
10322             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10323         else
10324             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10325     } else if ((fromY == BOARD_HEIGHT-4)
10326                && (toX == fromX)
10327                && gameInfo.variant == VariantBerolina
10328                && (board[fromY][fromX] == WhitePawn)
10329                && (board[toY][toX] == EmptySquare)) {
10330         board[fromY][fromX] = EmptySquare;
10331         board[toY][toX] = WhitePawn;
10332         if(oldEP & EP_BEROLIN_A) {
10333                 captured = board[fromY][fromX-1];
10334                 board[fromY][fromX-1] = EmptySquare;
10335         }else{  captured = board[fromY][fromX+1];
10336                 board[fromY][fromX+1] = EmptySquare;
10337         }
10338     } else if (board[fromY][fromX] == king
10339         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10340                && toY == fromY && toX > fromX+1) {
10341         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10342         board[fromY][toX-1] = board[fromY][rookX];
10343         board[fromY][rookX] = EmptySquare;
10344         board[fromY][fromX] = EmptySquare;
10345         board[toY][toX] = king;
10346     } else if (board[fromY][fromX] == king
10347         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10348                && toY == fromY && toX < fromX-1) {
10349         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10350         board[fromY][toX+1] = board[fromY][rookX];
10351         board[fromY][rookX] = EmptySquare;
10352         board[fromY][fromX] = EmptySquare;
10353         board[toY][toX] = king;
10354     } else if (fromY == 7 && fromX == 3
10355                && board[fromY][fromX] == BlackKing
10356                && toY == 7 && toX == 5) {
10357         board[fromY][fromX] = EmptySquare;
10358         board[toY][toX] = BlackKing;
10359         board[fromY][7] = EmptySquare;
10360         board[toY][4] = BlackRook;
10361     } else if (fromY == 7 && fromX == 3
10362                && board[fromY][fromX] == BlackKing
10363                && toY == 7 && toX == 1) {
10364         board[fromY][fromX] = EmptySquare;
10365         board[toY][toX] = BlackKing;
10366         board[fromY][0] = EmptySquare;
10367         board[toY][2] = BlackRook;
10368     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10369                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10370                && toY < promoRank && promoChar
10371                ) {
10372         /* black pawn promotion */
10373         board[toY][toX] = CharToPiece(ToLower(promoChar));
10374         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10375             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10376         board[fromY][fromX] = EmptySquare;
10377     } else if ((fromY < BOARD_HEIGHT>>1)
10378                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10379                && (toX != fromX)
10380                && gameInfo.variant != VariantXiangqi
10381                && gameInfo.variant != VariantBerolina
10382                && (pawn == BlackPawn)
10383                && (board[toY][toX] == EmptySquare)) {
10384         board[fromY][fromX] = EmptySquare;
10385         board[toY][toX] = piece;
10386         if(toY == epRank - 128 - 1)
10387             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10388         else
10389             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10390     } else if ((fromY == 3)
10391                && (toX == fromX)
10392                && gameInfo.variant == VariantBerolina
10393                && (board[fromY][fromX] == BlackPawn)
10394                && (board[toY][toX] == EmptySquare)) {
10395         board[fromY][fromX] = EmptySquare;
10396         board[toY][toX] = BlackPawn;
10397         if(oldEP & EP_BEROLIN_A) {
10398                 captured = board[fromY][fromX-1];
10399                 board[fromY][fromX-1] = EmptySquare;
10400         }else{  captured = board[fromY][fromX+1];
10401                 board[fromY][fromX+1] = EmptySquare;
10402         }
10403     } else {
10404         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10405         board[fromY][fromX] = EmptySquare;
10406         board[toY][toX] = piece;
10407     }
10408   }
10409
10410     if (gameInfo.holdingsWidth != 0) {
10411
10412       /* !!A lot more code needs to be written to support holdings  */
10413       /* [HGM] OK, so I have written it. Holdings are stored in the */
10414       /* penultimate board files, so they are automaticlly stored   */
10415       /* in the game history.                                       */
10416       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10417                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10418         /* Delete from holdings, by decreasing count */
10419         /* and erasing image if necessary            */
10420         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10421         if(p < (int) BlackPawn) { /* white drop */
10422              p -= (int)WhitePawn;
10423                  p = PieceToNumber((ChessSquare)p);
10424              if(p >= gameInfo.holdingsSize) p = 0;
10425              if(--board[p][BOARD_WIDTH-2] <= 0)
10426                   board[p][BOARD_WIDTH-1] = EmptySquare;
10427              if((int)board[p][BOARD_WIDTH-2] < 0)
10428                         board[p][BOARD_WIDTH-2] = 0;
10429         } else {                  /* black drop */
10430              p -= (int)BlackPawn;
10431                  p = PieceToNumber((ChessSquare)p);
10432              if(p >= gameInfo.holdingsSize) p = 0;
10433              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10434                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10435              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10436                         board[BOARD_HEIGHT-1-p][1] = 0;
10437         }
10438       }
10439       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10440           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10441         /* [HGM] holdings: Add to holdings, if holdings exist */
10442         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10443                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10444                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10445         }
10446         p = (int) captured;
10447         if (p >= (int) BlackPawn) {
10448           p -= (int)BlackPawn;
10449           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10450                   /* Restore shogi-promoted piece to its original  first */
10451                   captured = (ChessSquare) (DEMOTED(captured));
10452                   p = DEMOTED(p);
10453           }
10454           p = PieceToNumber((ChessSquare)p);
10455           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10456           board[p][BOARD_WIDTH-2]++;
10457           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10458         } else {
10459           p -= (int)WhitePawn;
10460           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10461                   captured = (ChessSquare) (DEMOTED(captured));
10462                   p = DEMOTED(p);
10463           }
10464           p = PieceToNumber((ChessSquare)p);
10465           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10466           board[BOARD_HEIGHT-1-p][1]++;
10467           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10468         }
10469       }
10470     } else if (gameInfo.variant == VariantAtomic) {
10471       if (captured != EmptySquare) {
10472         int y, x;
10473         for (y = toY-1; y <= toY+1; y++) {
10474           for (x = toX-1; x <= toX+1; x++) {
10475             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10476                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10477               board[y][x] = EmptySquare;
10478             }
10479           }
10480         }
10481         board[toY][toX] = EmptySquare;
10482       }
10483     }
10484
10485     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10486         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10487     } else
10488     if(promoChar == '+') {
10489         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10490         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10491         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10492           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10493     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10494         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10495         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10496            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10497         board[toY][toX] = newPiece;
10498     }
10499     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10500                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10501         // [HGM] superchess: take promotion piece out of holdings
10502         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10503         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10504             if(!--board[k][BOARD_WIDTH-2])
10505                 board[k][BOARD_WIDTH-1] = EmptySquare;
10506         } else {
10507             if(!--board[BOARD_HEIGHT-1-k][1])
10508                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10509         }
10510     }
10511 }
10512
10513 /* Updates forwardMostMove */
10514 void
10515 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10516 {
10517     int x = toX, y = toY;
10518     char *s = parseList[forwardMostMove];
10519     ChessSquare p = boards[forwardMostMove][toY][toX];
10520 //    forwardMostMove++; // [HGM] bare: moved downstream
10521
10522     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10523     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10524     (void) CoordsToAlgebraic(boards[forwardMostMove],
10525                              PosFlags(forwardMostMove),
10526                              fromY, fromX, y, x, (killX < 0)*promoChar,
10527                              s);
10528     if(kill2X >= 0 && kill2Y >= 0)
10529         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10530     if(killX >= 0 && killY >= 0)
10531         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10532                                            toX + AAA, toY + ONE - '0', promoChar);
10533
10534     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10535         int timeLeft; static int lastLoadFlag=0; int king, piece;
10536         piece = boards[forwardMostMove][fromY][fromX];
10537         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10538         if(gameInfo.variant == VariantKnightmate)
10539             king += (int) WhiteUnicorn - (int) WhiteKing;
10540         if(forwardMostMove == 0) {
10541             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10542                 fprintf(serverMoves, "%s;", UserName());
10543             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10544                 fprintf(serverMoves, "%s;", second.tidy);
10545             fprintf(serverMoves, "%s;", first.tidy);
10546             if(gameMode == MachinePlaysWhite)
10547                 fprintf(serverMoves, "%s;", UserName());
10548             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10549                 fprintf(serverMoves, "%s;", second.tidy);
10550         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10551         lastLoadFlag = loadFlag;
10552         // print base move
10553         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10554         // print castling suffix
10555         if( toY == fromY && piece == king ) {
10556             if(toX-fromX > 1)
10557                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10558             if(fromX-toX >1)
10559                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10560         }
10561         // e.p. suffix
10562         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10563              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10564              boards[forwardMostMove][toY][toX] == EmptySquare
10565              && fromX != toX && fromY != toY)
10566                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10567         // promotion suffix
10568         if(promoChar != NULLCHAR) {
10569             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10570                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10571                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10572             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10573         }
10574         if(!loadFlag) {
10575                 char buf[MOVE_LEN*2], *p; int len;
10576             fprintf(serverMoves, "/%d/%d",
10577                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10578             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10579             else                      timeLeft = blackTimeRemaining/1000;
10580             fprintf(serverMoves, "/%d", timeLeft);
10581                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10582                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10583                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10584                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10585             fprintf(serverMoves, "/%s", buf);
10586         }
10587         fflush(serverMoves);
10588     }
10589
10590     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10591         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10592       return;
10593     }
10594     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10595     if (commentList[forwardMostMove+1] != NULL) {
10596         free(commentList[forwardMostMove+1]);
10597         commentList[forwardMostMove+1] = NULL;
10598     }
10599     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10600     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10601     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10602     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10603     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10604     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10605     adjustedClock = FALSE;
10606     gameInfo.result = GameUnfinished;
10607     if (gameInfo.resultDetails != NULL) {
10608         free(gameInfo.resultDetails);
10609         gameInfo.resultDetails = NULL;
10610     }
10611     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10612                               moveList[forwardMostMove - 1]);
10613     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10614       case MT_NONE:
10615       case MT_STALEMATE:
10616       default:
10617         break;
10618       case MT_CHECK:
10619         if(!IS_SHOGI(gameInfo.variant))
10620             strcat(parseList[forwardMostMove - 1], "+");
10621         break;
10622       case MT_CHECKMATE:
10623       case MT_STAINMATE:
10624         strcat(parseList[forwardMostMove - 1], "#");
10625         break;
10626     }
10627 }
10628
10629 /* Updates currentMove if not pausing */
10630 void
10631 ShowMove (int fromX, int fromY, int toX, int toY)
10632 {
10633     int instant = (gameMode == PlayFromGameFile) ?
10634         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10635     if(appData.noGUI) return;
10636     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10637         if (!instant) {
10638             if (forwardMostMove == currentMove + 1) {
10639                 AnimateMove(boards[forwardMostMove - 1],
10640                             fromX, fromY, toX, toY);
10641             }
10642         }
10643         currentMove = forwardMostMove;
10644     }
10645
10646     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10647
10648     if (instant) return;
10649
10650     DisplayMove(currentMove - 1);
10651     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10652             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10653                 SetHighlights(fromX, fromY, toX, toY);
10654             }
10655     }
10656     DrawPosition(FALSE, boards[currentMove]);
10657     DisplayBothClocks();
10658     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10659 }
10660
10661 void
10662 SendEgtPath (ChessProgramState *cps)
10663 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10664         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10665
10666         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10667
10668         while(*p) {
10669             char c, *q = name+1, *r, *s;
10670
10671             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10672             while(*p && *p != ',') *q++ = *p++;
10673             *q++ = ':'; *q = 0;
10674             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10675                 strcmp(name, ",nalimov:") == 0 ) {
10676                 // take nalimov path from the menu-changeable option first, if it is defined
10677               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10678                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10679             } else
10680             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10681                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10682                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10683                 s = r = StrStr(s, ":") + 1; // beginning of path info
10684                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10685                 c = *r; *r = 0;             // temporarily null-terminate path info
10686                     *--q = 0;               // strip of trailig ':' from name
10687                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10688                 *r = c;
10689                 SendToProgram(buf,cps);     // send egtbpath command for this format
10690             }
10691             if(*p == ',') p++; // read away comma to position for next format name
10692         }
10693 }
10694
10695 static int
10696 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10697 {
10698       int width = 8, height = 8, holdings = 0;             // most common sizes
10699       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10700       // correct the deviations default for each variant
10701       if( v == VariantXiangqi ) width = 9,  height = 10;
10702       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10703       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10704       if( v == VariantCapablanca || v == VariantCapaRandom ||
10705           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10706                                 width = 10;
10707       if( v == VariantCourier ) width = 12;
10708       if( v == VariantSuper )                            holdings = 8;
10709       if( v == VariantGreat )   width = 10,              holdings = 8;
10710       if( v == VariantSChess )                           holdings = 7;
10711       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10712       if( v == VariantChuChess) width = 10, height = 10;
10713       if( v == VariantChu )     width = 12, height = 12;
10714       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10715              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10716              holdingsSize >= 0 && holdingsSize != holdings;
10717 }
10718
10719 char variantError[MSG_SIZ];
10720
10721 char *
10722 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10723 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10724       char *p, *variant = VariantName(v);
10725       static char b[MSG_SIZ];
10726       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10727            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10728                                                holdingsSize, variant); // cook up sized variant name
10729            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10730            if(StrStr(list, b) == NULL) {
10731                // specific sized variant not known, check if general sizing allowed
10732                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10733                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10734                             boardWidth, boardHeight, holdingsSize, engine);
10735                    return NULL;
10736                }
10737                /* [HGM] here we really should compare with the maximum supported board size */
10738            }
10739       } else snprintf(b, MSG_SIZ,"%s", variant);
10740       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10741       p = StrStr(list, b);
10742       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10743       if(p == NULL) {
10744           // occurs not at all in list, or only as sub-string
10745           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10746           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10747               int l = strlen(variantError);
10748               char *q;
10749               while(p != list && p[-1] != ',') p--;
10750               q = strchr(p, ',');
10751               if(q) *q = NULLCHAR;
10752               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10753               if(q) *q= ',';
10754           }
10755           return NULL;
10756       }
10757       return b;
10758 }
10759
10760 void
10761 InitChessProgram (ChessProgramState *cps, int setup)
10762 /* setup needed to setup FRC opening position */
10763 {
10764     char buf[MSG_SIZ], *b;
10765     if (appData.noChessProgram) return;
10766     hintRequested = FALSE;
10767     bookRequested = FALSE;
10768
10769     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10770     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10771     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10772     if(cps->memSize) { /* [HGM] memory */
10773       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10774         SendToProgram(buf, cps);
10775     }
10776     SendEgtPath(cps); /* [HGM] EGT */
10777     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10778       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10779         SendToProgram(buf, cps);
10780     }
10781
10782     setboardSpoiledMachineBlack = FALSE;
10783     SendToProgram(cps->initString, cps);
10784     if (gameInfo.variant != VariantNormal &&
10785         gameInfo.variant != VariantLoadable
10786         /* [HGM] also send variant if board size non-standard */
10787         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10788
10789       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10790                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10791       if (b == NULL) {
10792         VariantClass v;
10793         char c, *q = cps->variants, *p = strchr(q, ',');
10794         if(p) *p = NULLCHAR;
10795         v = StringToVariant(q);
10796         DisplayError(variantError, 0);
10797         if(v != VariantUnknown && cps == &first) {
10798             int w, h, s;
10799             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10800                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10801             ASSIGN(appData.variant, q);
10802             Reset(TRUE, FALSE);
10803         }
10804         if(p) *p = ',';
10805         return;
10806       }
10807
10808       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10809       SendToProgram(buf, cps);
10810     }
10811     currentlyInitializedVariant = gameInfo.variant;
10812
10813     /* [HGM] send opening position in FRC to first engine */
10814     if(setup) {
10815           SendToProgram("force\n", cps);
10816           SendBoard(cps, 0);
10817           /* engine is now in force mode! Set flag to wake it up after first move. */
10818           setboardSpoiledMachineBlack = 1;
10819     }
10820
10821     if (cps->sendICS) {
10822       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10823       SendToProgram(buf, cps);
10824     }
10825     cps->maybeThinking = FALSE;
10826     cps->offeredDraw = 0;
10827     if (!appData.icsActive) {
10828         SendTimeControl(cps, movesPerSession, timeControl,
10829                         timeIncrement, appData.searchDepth,
10830                         searchTime);
10831     }
10832     if (appData.showThinking
10833         // [HGM] thinking: four options require thinking output to be sent
10834         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10835                                 ) {
10836         SendToProgram("post\n", cps);
10837     }
10838     SendToProgram("hard\n", cps);
10839     if (!appData.ponderNextMove) {
10840         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10841            it without being sure what state we are in first.  "hard"
10842            is not a toggle, so that one is OK.
10843          */
10844         SendToProgram("easy\n", cps);
10845     }
10846     if (cps->usePing) {
10847       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10848       SendToProgram(buf, cps);
10849     }
10850     cps->initDone = TRUE;
10851     ClearEngineOutputPane(cps == &second);
10852 }
10853
10854
10855 void
10856 ResendOptions (ChessProgramState *cps)
10857 { // send the stored value of the options
10858   int i;
10859   char buf[MSG_SIZ];
10860   Option *opt = cps->option;
10861   for(i=0; i<cps->nrOptions; i++, opt++) {
10862       switch(opt->type) {
10863         case Spin:
10864         case Slider:
10865         case CheckBox:
10866             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10867           break;
10868         case ComboBox:
10869           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10870           break;
10871         default:
10872             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10873           break;
10874         case Button:
10875         case SaveButton:
10876           continue;
10877       }
10878       SendToProgram(buf, cps);
10879   }
10880 }
10881
10882 void
10883 StartChessProgram (ChessProgramState *cps)
10884 {
10885     char buf[MSG_SIZ];
10886     int err;
10887
10888     if (appData.noChessProgram) return;
10889     cps->initDone = FALSE;
10890
10891     if (strcmp(cps->host, "localhost") == 0) {
10892         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10893     } else if (*appData.remoteShell == NULLCHAR) {
10894         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10895     } else {
10896         if (*appData.remoteUser == NULLCHAR) {
10897           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10898                     cps->program);
10899         } else {
10900           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10901                     cps->host, appData.remoteUser, cps->program);
10902         }
10903         err = StartChildProcess(buf, "", &cps->pr);
10904     }
10905
10906     if (err != 0) {
10907       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10908         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10909         if(cps != &first) return;
10910         appData.noChessProgram = TRUE;
10911         ThawUI();
10912         SetNCPMode();
10913 //      DisplayFatalError(buf, err, 1);
10914 //      cps->pr = NoProc;
10915 //      cps->isr = NULL;
10916         return;
10917     }
10918
10919     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10920     if (cps->protocolVersion > 1) {
10921       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10922       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10923         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10924         cps->comboCnt = 0;  //                and values of combo boxes
10925       }
10926       SendToProgram(buf, cps);
10927       if(cps->reload) ResendOptions(cps);
10928     } else {
10929       SendToProgram("xboard\n", cps);
10930     }
10931 }
10932
10933 void
10934 TwoMachinesEventIfReady P((void))
10935 {
10936   static int curMess = 0;
10937   if (first.lastPing != first.lastPong) {
10938     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10939     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10940     return;
10941   }
10942   if (second.lastPing != second.lastPong) {
10943     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10944     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10945     return;
10946   }
10947   DisplayMessage("", ""); curMess = 0;
10948   TwoMachinesEvent();
10949 }
10950
10951 char *
10952 MakeName (char *template)
10953 {
10954     time_t clock;
10955     struct tm *tm;
10956     static char buf[MSG_SIZ];
10957     char *p = buf;
10958     int i;
10959
10960     clock = time((time_t *)NULL);
10961     tm = localtime(&clock);
10962
10963     while(*p++ = *template++) if(p[-1] == '%') {
10964         switch(*template++) {
10965           case 0:   *p = 0; return buf;
10966           case 'Y': i = tm->tm_year+1900; break;
10967           case 'y': i = tm->tm_year-100; break;
10968           case 'M': i = tm->tm_mon+1; break;
10969           case 'd': i = tm->tm_mday; break;
10970           case 'h': i = tm->tm_hour; break;
10971           case 'm': i = tm->tm_min; break;
10972           case 's': i = tm->tm_sec; break;
10973           default:  i = 0;
10974         }
10975         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10976     }
10977     return buf;
10978 }
10979
10980 int
10981 CountPlayers (char *p)
10982 {
10983     int n = 0;
10984     while(p = strchr(p, '\n')) p++, n++; // count participants
10985     return n;
10986 }
10987
10988 FILE *
10989 WriteTourneyFile (char *results, FILE *f)
10990 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10991     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10992     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10993         // create a file with tournament description
10994         fprintf(f, "-participants {%s}\n", appData.participants);
10995         fprintf(f, "-seedBase %d\n", appData.seedBase);
10996         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10997         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10998         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10999         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11000         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11001         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11002         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11003         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11004         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11005         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11006         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11007         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11008         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11009         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11010         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11011         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11012         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11013         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11014         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11015         fprintf(f, "-smpCores %d\n", appData.smpCores);
11016         if(searchTime > 0)
11017                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11018         else {
11019                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11020                 fprintf(f, "-tc %s\n", appData.timeControl);
11021                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11022         }
11023         fprintf(f, "-results \"%s\"\n", results);
11024     }
11025     return f;
11026 }
11027
11028 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11029
11030 void
11031 Substitute (char *participants, int expunge)
11032 {
11033     int i, changed, changes=0, nPlayers=0;
11034     char *p, *q, *r, buf[MSG_SIZ];
11035     if(participants == NULL) return;
11036     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11037     r = p = participants; q = appData.participants;
11038     while(*p && *p == *q) {
11039         if(*p == '\n') r = p+1, nPlayers++;
11040         p++; q++;
11041     }
11042     if(*p) { // difference
11043         while(*p && *p++ != '\n');
11044         while(*q && *q++ != '\n');
11045       changed = nPlayers;
11046         changes = 1 + (strcmp(p, q) != 0);
11047     }
11048     if(changes == 1) { // a single engine mnemonic was changed
11049         q = r; while(*q) nPlayers += (*q++ == '\n');
11050         p = buf; while(*r && (*p = *r++) != '\n') p++;
11051         *p = NULLCHAR;
11052         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11053         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11054         if(mnemonic[i]) { // The substitute is valid
11055             FILE *f;
11056             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11057                 flock(fileno(f), LOCK_EX);
11058                 ParseArgsFromFile(f);
11059                 fseek(f, 0, SEEK_SET);
11060                 FREE(appData.participants); appData.participants = participants;
11061                 if(expunge) { // erase results of replaced engine
11062                     int len = strlen(appData.results), w, b, dummy;
11063                     for(i=0; i<len; i++) {
11064                         Pairing(i, nPlayers, &w, &b, &dummy);
11065                         if((w == changed || b == changed) && appData.results[i] == '*') {
11066                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11067                             fclose(f);
11068                             return;
11069                         }
11070                     }
11071                     for(i=0; i<len; i++) {
11072                         Pairing(i, nPlayers, &w, &b, &dummy);
11073                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11074                     }
11075                 }
11076                 WriteTourneyFile(appData.results, f);
11077                 fclose(f); // release lock
11078                 return;
11079             }
11080         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11081     }
11082     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11083     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11084     free(participants);
11085     return;
11086 }
11087
11088 int
11089 CheckPlayers (char *participants)
11090 {
11091         int i;
11092         char buf[MSG_SIZ], *p;
11093         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11094         while(p = strchr(participants, '\n')) {
11095             *p = NULLCHAR;
11096             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11097             if(!mnemonic[i]) {
11098                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11099                 *p = '\n';
11100                 DisplayError(buf, 0);
11101                 return 1;
11102             }
11103             *p = '\n';
11104             participants = p + 1;
11105         }
11106         return 0;
11107 }
11108
11109 int
11110 CreateTourney (char *name)
11111 {
11112         FILE *f;
11113         if(matchMode && strcmp(name, appData.tourneyFile)) {
11114              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11115         }
11116         if(name[0] == NULLCHAR) {
11117             if(appData.participants[0])
11118                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11119             return 0;
11120         }
11121         f = fopen(name, "r");
11122         if(f) { // file exists
11123             ASSIGN(appData.tourneyFile, name);
11124             ParseArgsFromFile(f); // parse it
11125         } else {
11126             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11127             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11128                 DisplayError(_("Not enough participants"), 0);
11129                 return 0;
11130             }
11131             if(CheckPlayers(appData.participants)) return 0;
11132             ASSIGN(appData.tourneyFile, name);
11133             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11134             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11135         }
11136         fclose(f);
11137         appData.noChessProgram = FALSE;
11138         appData.clockMode = TRUE;
11139         SetGNUMode();
11140         return 1;
11141 }
11142
11143 int
11144 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11145 {
11146     char buf[MSG_SIZ], *p, *q;
11147     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11148     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11149     skip = !all && group[0]; // if group requested, we start in skip mode
11150     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11151         p = names; q = buf; header = 0;
11152         while(*p && *p != '\n') *q++ = *p++;
11153         *q = 0;
11154         if(*p == '\n') p++;
11155         if(buf[0] == '#') {
11156             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11157             depth++; // we must be entering a new group
11158             if(all) continue; // suppress printing group headers when complete list requested
11159             header = 1;
11160             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11161         }
11162         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11163         if(engineList[i]) free(engineList[i]);
11164         engineList[i] = strdup(buf);
11165         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11166         if(engineMnemonic[i]) free(engineMnemonic[i]);
11167         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11168             strcat(buf, " (");
11169             sscanf(q + 8, "%s", buf + strlen(buf));
11170             strcat(buf, ")");
11171         }
11172         engineMnemonic[i] = strdup(buf);
11173         i++;
11174     }
11175     engineList[i] = engineMnemonic[i] = NULL;
11176     return i;
11177 }
11178
11179 // following implemented as macro to avoid type limitations
11180 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11181
11182 void
11183 SwapEngines (int n)
11184 {   // swap settings for first engine and other engine (so far only some selected options)
11185     int h;
11186     char *p;
11187     if(n == 0) return;
11188     SWAP(directory, p)
11189     SWAP(chessProgram, p)
11190     SWAP(isUCI, h)
11191     SWAP(hasOwnBookUCI, h)
11192     SWAP(protocolVersion, h)
11193     SWAP(reuse, h)
11194     SWAP(scoreIsAbsolute, h)
11195     SWAP(timeOdds, h)
11196     SWAP(logo, p)
11197     SWAP(pgnName, p)
11198     SWAP(pvSAN, h)
11199     SWAP(engOptions, p)
11200     SWAP(engInitString, p)
11201     SWAP(computerString, p)
11202     SWAP(features, p)
11203     SWAP(fenOverride, p)
11204     SWAP(NPS, h)
11205     SWAP(accumulateTC, h)
11206     SWAP(drawDepth, h)
11207     SWAP(host, p)
11208     SWAP(pseudo, h)
11209 }
11210
11211 int
11212 GetEngineLine (char *s, int n)
11213 {
11214     int i;
11215     char buf[MSG_SIZ];
11216     extern char *icsNames;
11217     if(!s || !*s) return 0;
11218     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11219     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11220     if(!mnemonic[i]) return 0;
11221     if(n == 11) return 1; // just testing if there was a match
11222     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11223     if(n == 1) SwapEngines(n);
11224     ParseArgsFromString(buf);
11225     if(n == 1) SwapEngines(n);
11226     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11227         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11228         ParseArgsFromString(buf);
11229     }
11230     return 1;
11231 }
11232
11233 int
11234 SetPlayer (int player, char *p)
11235 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11236     int i;
11237     char buf[MSG_SIZ], *engineName;
11238     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11239     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11240     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11241     if(mnemonic[i]) {
11242         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11243         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11244         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11245         ParseArgsFromString(buf);
11246     } else { // no engine with this nickname is installed!
11247         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11248         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11249         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11250         ModeHighlight();
11251         DisplayError(buf, 0);
11252         return 0;
11253     }
11254     free(engineName);
11255     return i;
11256 }
11257
11258 char *recentEngines;
11259
11260 void
11261 RecentEngineEvent (int nr)
11262 {
11263     int n;
11264 //    SwapEngines(1); // bump first to second
11265 //    ReplaceEngine(&second, 1); // and load it there
11266     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11267     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11268     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11269         ReplaceEngine(&first, 0);
11270         FloatToFront(&appData.recentEngineList, command[n]);
11271     }
11272 }
11273
11274 int
11275 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11276 {   // determine players from game number
11277     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11278
11279     if(appData.tourneyType == 0) {
11280         roundsPerCycle = (nPlayers - 1) | 1;
11281         pairingsPerRound = nPlayers / 2;
11282     } else if(appData.tourneyType > 0) {
11283         roundsPerCycle = nPlayers - appData.tourneyType;
11284         pairingsPerRound = appData.tourneyType;
11285     }
11286     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11287     gamesPerCycle = gamesPerRound * roundsPerCycle;
11288     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11289     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11290     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11291     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11292     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11293     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11294
11295     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11296     if(appData.roundSync) *syncInterval = gamesPerRound;
11297
11298     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11299
11300     if(appData.tourneyType == 0) {
11301         if(curPairing == (nPlayers-1)/2 ) {
11302             *whitePlayer = curRound;
11303             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11304         } else {
11305             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11306             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11307             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11308             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11309         }
11310     } else if(appData.tourneyType > 1) {
11311         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11312         *whitePlayer = curRound + appData.tourneyType;
11313     } else if(appData.tourneyType > 0) {
11314         *whitePlayer = curPairing;
11315         *blackPlayer = curRound + appData.tourneyType;
11316     }
11317
11318     // take care of white/black alternation per round.
11319     // For cycles and games this is already taken care of by default, derived from matchGame!
11320     return curRound & 1;
11321 }
11322
11323 int
11324 NextTourneyGame (int nr, int *swapColors)
11325 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11326     char *p, *q;
11327     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11328     FILE *tf;
11329     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11330     tf = fopen(appData.tourneyFile, "r");
11331     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11332     ParseArgsFromFile(tf); fclose(tf);
11333     InitTimeControls(); // TC might be altered from tourney file
11334
11335     nPlayers = CountPlayers(appData.participants); // count participants
11336     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11337     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11338
11339     if(syncInterval) {
11340         p = q = appData.results;
11341         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11342         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11343             DisplayMessage(_("Waiting for other game(s)"),"");
11344             waitingForGame = TRUE;
11345             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11346             return 0;
11347         }
11348         waitingForGame = FALSE;
11349     }
11350
11351     if(appData.tourneyType < 0) {
11352         if(nr>=0 && !pairingReceived) {
11353             char buf[1<<16];
11354             if(pairing.pr == NoProc) {
11355                 if(!appData.pairingEngine[0]) {
11356                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11357                     return 0;
11358                 }
11359                 StartChessProgram(&pairing); // starts the pairing engine
11360             }
11361             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11362             SendToProgram(buf, &pairing);
11363             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11364             SendToProgram(buf, &pairing);
11365             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11366         }
11367         pairingReceived = 0;                              // ... so we continue here
11368         *swapColors = 0;
11369         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11370         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11371         matchGame = 1; roundNr = nr / syncInterval + 1;
11372     }
11373
11374     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11375
11376     // redefine engines, engine dir, etc.
11377     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11378     if(first.pr == NoProc) {
11379       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11380       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11381     }
11382     if(second.pr == NoProc) {
11383       SwapEngines(1);
11384       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11385       SwapEngines(1);         // and make that valid for second engine by swapping
11386       InitEngine(&second, 1);
11387     }
11388     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11389     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11390     return OK;
11391 }
11392
11393 void
11394 NextMatchGame ()
11395 {   // performs game initialization that does not invoke engines, and then tries to start the game
11396     int res, firstWhite, swapColors = 0;
11397     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11398     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
11399         char buf[MSG_SIZ];
11400         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11401         if(strcmp(buf, currentDebugFile)) { // name has changed
11402             FILE *f = fopen(buf, "w");
11403             if(f) { // if opening the new file failed, just keep using the old one
11404                 ASSIGN(currentDebugFile, buf);
11405                 fclose(debugFP);
11406                 debugFP = f;
11407             }
11408             if(appData.serverFileName) {
11409                 if(serverFP) fclose(serverFP);
11410                 serverFP = fopen(appData.serverFileName, "w");
11411                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11412                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11413             }
11414         }
11415     }
11416     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11417     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11418     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11419     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11420     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11421     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11422     Reset(FALSE, first.pr != NoProc);
11423     res = LoadGameOrPosition(matchGame); // setup game
11424     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11425     if(!res) return; // abort when bad game/pos file
11426     TwoMachinesEvent();
11427 }
11428
11429 void
11430 UserAdjudicationEvent (int result)
11431 {
11432     ChessMove gameResult = GameIsDrawn;
11433
11434     if( result > 0 ) {
11435         gameResult = WhiteWins;
11436     }
11437     else if( result < 0 ) {
11438         gameResult = BlackWins;
11439     }
11440
11441     if( gameMode == TwoMachinesPlay ) {
11442         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11443     }
11444 }
11445
11446
11447 // [HGM] save: calculate checksum of game to make games easily identifiable
11448 int
11449 StringCheckSum (char *s)
11450 {
11451         int i = 0;
11452         if(s==NULL) return 0;
11453         while(*s) i = i*259 + *s++;
11454         return i;
11455 }
11456
11457 int
11458 GameCheckSum ()
11459 {
11460         int i, sum=0;
11461         for(i=backwardMostMove; i<forwardMostMove; i++) {
11462                 sum += pvInfoList[i].depth;
11463                 sum += StringCheckSum(parseList[i]);
11464                 sum += StringCheckSum(commentList[i]);
11465                 sum *= 261;
11466         }
11467         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11468         return sum + StringCheckSum(commentList[i]);
11469 } // end of save patch
11470
11471 void
11472 GameEnds (ChessMove result, char *resultDetails, int whosays)
11473 {
11474     GameMode nextGameMode;
11475     int isIcsGame;
11476     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11477
11478     if(endingGame) return; /* [HGM] crash: forbid recursion */
11479     endingGame = 1;
11480     if(twoBoards) { // [HGM] dual: switch back to one board
11481         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11482         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11483     }
11484     if (appData.debugMode) {
11485       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11486               result, resultDetails ? resultDetails : "(null)", whosays);
11487     }
11488
11489     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11490
11491     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11492
11493     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11494         /* If we are playing on ICS, the server decides when the
11495            game is over, but the engine can offer to draw, claim
11496            a draw, or resign.
11497          */
11498 #if ZIPPY
11499         if (appData.zippyPlay && first.initDone) {
11500             if (result == GameIsDrawn) {
11501                 /* In case draw still needs to be claimed */
11502                 SendToICS(ics_prefix);
11503                 SendToICS("draw\n");
11504             } else if (StrCaseStr(resultDetails, "resign")) {
11505                 SendToICS(ics_prefix);
11506                 SendToICS("resign\n");
11507             }
11508         }
11509 #endif
11510         endingGame = 0; /* [HGM] crash */
11511         return;
11512     }
11513
11514     /* If we're loading the game from a file, stop */
11515     if (whosays == GE_FILE) {
11516       (void) StopLoadGameTimer();
11517       gameFileFP = NULL;
11518     }
11519
11520     /* Cancel draw offers */
11521     first.offeredDraw = second.offeredDraw = 0;
11522
11523     /* If this is an ICS game, only ICS can really say it's done;
11524        if not, anyone can. */
11525     isIcsGame = (gameMode == IcsPlayingWhite ||
11526                  gameMode == IcsPlayingBlack ||
11527                  gameMode == IcsObserving    ||
11528                  gameMode == IcsExamining);
11529
11530     if (!isIcsGame || whosays == GE_ICS) {
11531         /* OK -- not an ICS game, or ICS said it was done */
11532         StopClocks();
11533         if (!isIcsGame && !appData.noChessProgram)
11534           SetUserThinkingEnables();
11535
11536         /* [HGM] if a machine claims the game end we verify this claim */
11537         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11538             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11539                 char claimer;
11540                 ChessMove trueResult = (ChessMove) -1;
11541
11542                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11543                                             first.twoMachinesColor[0] :
11544                                             second.twoMachinesColor[0] ;
11545
11546                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11547                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11548                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11549                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11550                 } else
11551                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11552                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11553                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11554                 } else
11555                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11556                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11557                 }
11558
11559                 // now verify win claims, but not in drop games, as we don't understand those yet
11560                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11561                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11562                     (result == WhiteWins && claimer == 'w' ||
11563                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11564                       if (appData.debugMode) {
11565                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11566                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11567                       }
11568                       if(result != trueResult) {
11569                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11570                               result = claimer == 'w' ? BlackWins : WhiteWins;
11571                               resultDetails = buf;
11572                       }
11573                 } else
11574                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11575                     && (forwardMostMove <= backwardMostMove ||
11576                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11577                         (claimer=='b')==(forwardMostMove&1))
11578                                                                                   ) {
11579                       /* [HGM] verify: draws that were not flagged are false claims */
11580                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11581                       result = claimer == 'w' ? BlackWins : WhiteWins;
11582                       resultDetails = buf;
11583                 }
11584                 /* (Claiming a loss is accepted no questions asked!) */
11585             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11586                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11587                 result = GameUnfinished;
11588                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11589             }
11590             /* [HGM] bare: don't allow bare King to win */
11591             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11592                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11593                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11594                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11595                && result != GameIsDrawn)
11596             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11597                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11598                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11599                         if(p >= 0 && p <= (int)WhiteKing) k++;
11600                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11601                 }
11602                 if (appData.debugMode) {
11603                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11604                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11605                 }
11606                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11607                         result = GameIsDrawn;
11608                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11609                         resultDetails = buf;
11610                 }
11611             }
11612         }
11613
11614
11615         if(serverMoves != NULL && !loadFlag) { char c = '=';
11616             if(result==WhiteWins) c = '+';
11617             if(result==BlackWins) c = '-';
11618             if(resultDetails != NULL)
11619                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11620         }
11621         if (resultDetails != NULL) {
11622             gameInfo.result = result;
11623             gameInfo.resultDetails = StrSave(resultDetails);
11624
11625             /* display last move only if game was not loaded from file */
11626             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11627                 DisplayMove(currentMove - 1);
11628
11629             if (forwardMostMove != 0) {
11630                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11631                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11632                                                                 ) {
11633                     if (*appData.saveGameFile != NULLCHAR) {
11634                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11635                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11636                         else
11637                         SaveGameToFile(appData.saveGameFile, TRUE);
11638                     } else if (appData.autoSaveGames) {
11639                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11640                     }
11641                     if (*appData.savePositionFile != NULLCHAR) {
11642                         SavePositionToFile(appData.savePositionFile);
11643                     }
11644                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11645                 }
11646             }
11647
11648             /* Tell program how game ended in case it is learning */
11649             /* [HGM] Moved this to after saving the PGN, just in case */
11650             /* engine died and we got here through time loss. In that */
11651             /* case we will get a fatal error writing the pipe, which */
11652             /* would otherwise lose us the PGN.                       */
11653             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11654             /* output during GameEnds should never be fatal anymore   */
11655             if (gameMode == MachinePlaysWhite ||
11656                 gameMode == MachinePlaysBlack ||
11657                 gameMode == TwoMachinesPlay ||
11658                 gameMode == IcsPlayingWhite ||
11659                 gameMode == IcsPlayingBlack ||
11660                 gameMode == BeginningOfGame) {
11661                 char buf[MSG_SIZ];
11662                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11663                         resultDetails);
11664                 if (first.pr != NoProc) {
11665                     SendToProgram(buf, &first);
11666                 }
11667                 if (second.pr != NoProc &&
11668                     gameMode == TwoMachinesPlay) {
11669                     SendToProgram(buf, &second);
11670                 }
11671             }
11672         }
11673
11674         if (appData.icsActive) {
11675             if (appData.quietPlay &&
11676                 (gameMode == IcsPlayingWhite ||
11677                  gameMode == IcsPlayingBlack)) {
11678                 SendToICS(ics_prefix);
11679                 SendToICS("set shout 1\n");
11680             }
11681             nextGameMode = IcsIdle;
11682             ics_user_moved = FALSE;
11683             /* clean up premove.  It's ugly when the game has ended and the
11684              * premove highlights are still on the board.
11685              */
11686             if (gotPremove) {
11687               gotPremove = FALSE;
11688               ClearPremoveHighlights();
11689               DrawPosition(FALSE, boards[currentMove]);
11690             }
11691             if (whosays == GE_ICS) {
11692                 switch (result) {
11693                 case WhiteWins:
11694                     if (gameMode == IcsPlayingWhite)
11695                         PlayIcsWinSound();
11696                     else if(gameMode == IcsPlayingBlack)
11697                         PlayIcsLossSound();
11698                     break;
11699                 case BlackWins:
11700                     if (gameMode == IcsPlayingBlack)
11701                         PlayIcsWinSound();
11702                     else if(gameMode == IcsPlayingWhite)
11703                         PlayIcsLossSound();
11704                     break;
11705                 case GameIsDrawn:
11706                     PlayIcsDrawSound();
11707                     break;
11708                 default:
11709                     PlayIcsUnfinishedSound();
11710                 }
11711             }
11712             if(appData.quitNext) { ExitEvent(0); return; }
11713         } else if (gameMode == EditGame ||
11714                    gameMode == PlayFromGameFile ||
11715                    gameMode == AnalyzeMode ||
11716                    gameMode == AnalyzeFile) {
11717             nextGameMode = gameMode;
11718         } else {
11719             nextGameMode = EndOfGame;
11720         }
11721         pausing = FALSE;
11722         ModeHighlight();
11723     } else {
11724         nextGameMode = gameMode;
11725     }
11726
11727     if (appData.noChessProgram) {
11728         gameMode = nextGameMode;
11729         ModeHighlight();
11730         endingGame = 0; /* [HGM] crash */
11731         return;
11732     }
11733
11734     if (first.reuse) {
11735         /* Put first chess program into idle state */
11736         if (first.pr != NoProc &&
11737             (gameMode == MachinePlaysWhite ||
11738              gameMode == MachinePlaysBlack ||
11739              gameMode == TwoMachinesPlay ||
11740              gameMode == IcsPlayingWhite ||
11741              gameMode == IcsPlayingBlack ||
11742              gameMode == BeginningOfGame)) {
11743             SendToProgram("force\n", &first);
11744             if (first.usePing) {
11745               char buf[MSG_SIZ];
11746               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11747               SendToProgram(buf, &first);
11748             }
11749         }
11750     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11751         /* Kill off first chess program */
11752         if (first.isr != NULL)
11753           RemoveInputSource(first.isr);
11754         first.isr = NULL;
11755
11756         if (first.pr != NoProc) {
11757             ExitAnalyzeMode();
11758             DoSleep( appData.delayBeforeQuit );
11759             SendToProgram("quit\n", &first);
11760             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11761             first.reload = TRUE;
11762         }
11763         first.pr = NoProc;
11764     }
11765     if (second.reuse) {
11766         /* Put second chess program into idle state */
11767         if (second.pr != NoProc &&
11768             gameMode == TwoMachinesPlay) {
11769             SendToProgram("force\n", &second);
11770             if (second.usePing) {
11771               char buf[MSG_SIZ];
11772               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11773               SendToProgram(buf, &second);
11774             }
11775         }
11776     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11777         /* Kill off second chess program */
11778         if (second.isr != NULL)
11779           RemoveInputSource(second.isr);
11780         second.isr = NULL;
11781
11782         if (second.pr != NoProc) {
11783             DoSleep( appData.delayBeforeQuit );
11784             SendToProgram("quit\n", &second);
11785             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11786             second.reload = TRUE;
11787         }
11788         second.pr = NoProc;
11789     }
11790
11791     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11792         char resChar = '=';
11793         switch (result) {
11794         case WhiteWins:
11795           resChar = '+';
11796           if (first.twoMachinesColor[0] == 'w') {
11797             first.matchWins++;
11798           } else {
11799             second.matchWins++;
11800           }
11801           break;
11802         case BlackWins:
11803           resChar = '-';
11804           if (first.twoMachinesColor[0] == 'b') {
11805             first.matchWins++;
11806           } else {
11807             second.matchWins++;
11808           }
11809           break;
11810         case GameUnfinished:
11811           resChar = ' ';
11812         default:
11813           break;
11814         }
11815
11816         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11817         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11818             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11819             ReserveGame(nextGame, resChar); // sets nextGame
11820             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11821             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11822         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11823
11824         if (nextGame <= appData.matchGames && !abortMatch) {
11825             gameMode = nextGameMode;
11826             matchGame = nextGame; // this will be overruled in tourney mode!
11827             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11828             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11829             endingGame = 0; /* [HGM] crash */
11830             return;
11831         } else {
11832             gameMode = nextGameMode;
11833             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11834                      first.tidy, second.tidy,
11835                      first.matchWins, second.matchWins,
11836                      appData.matchGames - (first.matchWins + second.matchWins));
11837             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11838             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11839             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11840             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11841                 first.twoMachinesColor = "black\n";
11842                 second.twoMachinesColor = "white\n";
11843             } else {
11844                 first.twoMachinesColor = "white\n";
11845                 second.twoMachinesColor = "black\n";
11846             }
11847         }
11848     }
11849     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11850         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11851       ExitAnalyzeMode();
11852     gameMode = nextGameMode;
11853     ModeHighlight();
11854     endingGame = 0;  /* [HGM] crash */
11855     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11856         if(matchMode == TRUE) { // match through command line: exit with or without popup
11857             if(ranking) {
11858                 ToNrEvent(forwardMostMove);
11859                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11860                 else ExitEvent(0);
11861             } else DisplayFatalError(buf, 0, 0);
11862         } else { // match through menu; just stop, with or without popup
11863             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11864             ModeHighlight();
11865             if(ranking){
11866                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11867             } else DisplayNote(buf);
11868       }
11869       if(ranking) free(ranking);
11870     }
11871 }
11872
11873 /* Assumes program was just initialized (initString sent).
11874    Leaves program in force mode. */
11875 void
11876 FeedMovesToProgram (ChessProgramState *cps, int upto)
11877 {
11878     int i;
11879
11880     if (appData.debugMode)
11881       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11882               startedFromSetupPosition ? "position and " : "",
11883               backwardMostMove, upto, cps->which);
11884     if(currentlyInitializedVariant != gameInfo.variant) {
11885       char buf[MSG_SIZ];
11886         // [HGM] variantswitch: make engine aware of new variant
11887         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11888                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11889                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11890         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11891         SendToProgram(buf, cps);
11892         currentlyInitializedVariant = gameInfo.variant;
11893     }
11894     SendToProgram("force\n", cps);
11895     if (startedFromSetupPosition) {
11896         SendBoard(cps, backwardMostMove);
11897     if (appData.debugMode) {
11898         fprintf(debugFP, "feedMoves\n");
11899     }
11900     }
11901     for (i = backwardMostMove; i < upto; i++) {
11902         SendMoveToProgram(i, cps);
11903     }
11904 }
11905
11906
11907 int
11908 ResurrectChessProgram ()
11909 {
11910      /* The chess program may have exited.
11911         If so, restart it and feed it all the moves made so far. */
11912     static int doInit = 0;
11913
11914     if (appData.noChessProgram) return 1;
11915
11916     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11917         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11918         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11919         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11920     } else {
11921         if (first.pr != NoProc) return 1;
11922         StartChessProgram(&first);
11923     }
11924     InitChessProgram(&first, FALSE);
11925     FeedMovesToProgram(&first, currentMove);
11926
11927     if (!first.sendTime) {
11928         /* can't tell gnuchess what its clock should read,
11929            so we bow to its notion. */
11930         ResetClocks();
11931         timeRemaining[0][currentMove] = whiteTimeRemaining;
11932         timeRemaining[1][currentMove] = blackTimeRemaining;
11933     }
11934
11935     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11936                 appData.icsEngineAnalyze) && first.analysisSupport) {
11937       SendToProgram("analyze\n", &first);
11938       first.analyzing = TRUE;
11939     }
11940     return 1;
11941 }
11942
11943 /*
11944  * Button procedures
11945  */
11946 void
11947 Reset (int redraw, int init)
11948 {
11949     int i;
11950
11951     if (appData.debugMode) {
11952         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11953                 redraw, init, gameMode);
11954     }
11955     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11956     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11957     CleanupTail(); // [HGM] vari: delete any stored variations
11958     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11959     pausing = pauseExamInvalid = FALSE;
11960     startedFromSetupPosition = blackPlaysFirst = FALSE;
11961     firstMove = TRUE;
11962     whiteFlag = blackFlag = FALSE;
11963     userOfferedDraw = FALSE;
11964     hintRequested = bookRequested = FALSE;
11965     first.maybeThinking = FALSE;
11966     second.maybeThinking = FALSE;
11967     first.bookSuspend = FALSE; // [HGM] book
11968     second.bookSuspend = FALSE;
11969     thinkOutput[0] = NULLCHAR;
11970     lastHint[0] = NULLCHAR;
11971     ClearGameInfo(&gameInfo);
11972     gameInfo.variant = StringToVariant(appData.variant);
11973     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11974     ics_user_moved = ics_clock_paused = FALSE;
11975     ics_getting_history = H_FALSE;
11976     ics_gamenum = -1;
11977     white_holding[0] = black_holding[0] = NULLCHAR;
11978     ClearProgramStats();
11979     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11980
11981     ResetFrontEnd();
11982     ClearHighlights();
11983     flipView = appData.flipView;
11984     ClearPremoveHighlights();
11985     gotPremove = FALSE;
11986     alarmSounded = FALSE;
11987     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11988
11989     GameEnds(EndOfFile, NULL, GE_PLAYER);
11990     if(appData.serverMovesName != NULL) {
11991         /* [HGM] prepare to make moves file for broadcasting */
11992         clock_t t = clock();
11993         if(serverMoves != NULL) fclose(serverMoves);
11994         serverMoves = fopen(appData.serverMovesName, "r");
11995         if(serverMoves != NULL) {
11996             fclose(serverMoves);
11997             /* delay 15 sec before overwriting, so all clients can see end */
11998             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11999         }
12000         serverMoves = fopen(appData.serverMovesName, "w");
12001     }
12002
12003     ExitAnalyzeMode();
12004     gameMode = BeginningOfGame;
12005     ModeHighlight();
12006     if(appData.icsActive) gameInfo.variant = VariantNormal;
12007     currentMove = forwardMostMove = backwardMostMove = 0;
12008     MarkTargetSquares(1);
12009     InitPosition(redraw);
12010     for (i = 0; i < MAX_MOVES; i++) {
12011         if (commentList[i] != NULL) {
12012             free(commentList[i]);
12013             commentList[i] = NULL;
12014         }
12015     }
12016     ResetClocks();
12017     timeRemaining[0][0] = whiteTimeRemaining;
12018     timeRemaining[1][0] = blackTimeRemaining;
12019
12020     if (first.pr == NoProc) {
12021         StartChessProgram(&first);
12022     }
12023     if (init) {
12024             InitChessProgram(&first, startedFromSetupPosition);
12025     }
12026     DisplayTitle("");
12027     DisplayMessage("", "");
12028     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12029     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12030     ClearMap();        // [HGM] exclude: invalidate map
12031 }
12032
12033 void
12034 AutoPlayGameLoop ()
12035 {
12036     for (;;) {
12037         if (!AutoPlayOneMove())
12038           return;
12039         if (matchMode || appData.timeDelay == 0)
12040           continue;
12041         if (appData.timeDelay < 0)
12042           return;
12043         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12044         break;
12045     }
12046 }
12047
12048 void
12049 AnalyzeNextGame()
12050 {
12051     ReloadGame(1); // next game
12052 }
12053
12054 int
12055 AutoPlayOneMove ()
12056 {
12057     int fromX, fromY, toX, toY;
12058
12059     if (appData.debugMode) {
12060       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12061     }
12062
12063     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12064       return FALSE;
12065
12066     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12067       pvInfoList[currentMove].depth = programStats.depth;
12068       pvInfoList[currentMove].score = programStats.score;
12069       pvInfoList[currentMove].time  = 0;
12070       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12071       else { // append analysis of final position as comment
12072         char buf[MSG_SIZ];
12073         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12074         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12075       }
12076       programStats.depth = 0;
12077     }
12078
12079     if (currentMove >= forwardMostMove) {
12080       if(gameMode == AnalyzeFile) {
12081           if(appData.loadGameIndex == -1) {
12082             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12083           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12084           } else {
12085           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12086         }
12087       }
12088 //      gameMode = EndOfGame;
12089 //      ModeHighlight();
12090
12091       /* [AS] Clear current move marker at the end of a game */
12092       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12093
12094       return FALSE;
12095     }
12096
12097     toX = moveList[currentMove][2] - AAA;
12098     toY = moveList[currentMove][3] - ONE;
12099
12100     if (moveList[currentMove][1] == '@') {
12101         if (appData.highlightLastMove) {
12102             SetHighlights(-1, -1, toX, toY);
12103         }
12104     } else {
12105         fromX = moveList[currentMove][0] - AAA;
12106         fromY = moveList[currentMove][1] - ONE;
12107
12108         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12109
12110         if(moveList[currentMove][4] == ';') { // multi-leg
12111             killX = moveList[currentMove][5] - AAA;
12112             killY = moveList[currentMove][6] - ONE;
12113         }
12114         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12115         killX = killY = -1;
12116
12117         if (appData.highlightLastMove) {
12118             SetHighlights(fromX, fromY, toX, toY);
12119         }
12120     }
12121     DisplayMove(currentMove);
12122     SendMoveToProgram(currentMove++, &first);
12123     DisplayBothClocks();
12124     DrawPosition(FALSE, boards[currentMove]);
12125     // [HGM] PV info: always display, routine tests if empty
12126     DisplayComment(currentMove - 1, commentList[currentMove]);
12127     return TRUE;
12128 }
12129
12130
12131 int
12132 LoadGameOneMove (ChessMove readAhead)
12133 {
12134     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12135     char promoChar = NULLCHAR;
12136     ChessMove moveType;
12137     char move[MSG_SIZ];
12138     char *p, *q;
12139
12140     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12141         gameMode != AnalyzeMode && gameMode != Training) {
12142         gameFileFP = NULL;
12143         return FALSE;
12144     }
12145
12146     yyboardindex = forwardMostMove;
12147     if (readAhead != EndOfFile) {
12148       moveType = readAhead;
12149     } else {
12150       if (gameFileFP == NULL)
12151           return FALSE;
12152       moveType = (ChessMove) Myylex();
12153     }
12154
12155     done = FALSE;
12156     switch (moveType) {
12157       case Comment:
12158         if (appData.debugMode)
12159           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12160         p = yy_text;
12161
12162         /* append the comment but don't display it */
12163         AppendComment(currentMove, p, FALSE);
12164         return TRUE;
12165
12166       case WhiteCapturesEnPassant:
12167       case BlackCapturesEnPassant:
12168       case WhitePromotion:
12169       case BlackPromotion:
12170       case WhiteNonPromotion:
12171       case BlackNonPromotion:
12172       case NormalMove:
12173       case FirstLeg:
12174       case WhiteKingSideCastle:
12175       case WhiteQueenSideCastle:
12176       case BlackKingSideCastle:
12177       case BlackQueenSideCastle:
12178       case WhiteKingSideCastleWild:
12179       case WhiteQueenSideCastleWild:
12180       case BlackKingSideCastleWild:
12181       case BlackQueenSideCastleWild:
12182       /* PUSH Fabien */
12183       case WhiteHSideCastleFR:
12184       case WhiteASideCastleFR:
12185       case BlackHSideCastleFR:
12186       case BlackASideCastleFR:
12187       /* POP Fabien */
12188         if (appData.debugMode)
12189           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12190         fromX = currentMoveString[0] - AAA;
12191         fromY = currentMoveString[1] - ONE;
12192         toX = currentMoveString[2] - AAA;
12193         toY = currentMoveString[3] - ONE;
12194         promoChar = currentMoveString[4];
12195         if(promoChar == ';') promoChar = currentMoveString[7];
12196         break;
12197
12198       case WhiteDrop:
12199       case BlackDrop:
12200         if (appData.debugMode)
12201           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12202         fromX = moveType == WhiteDrop ?
12203           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12204         (int) CharToPiece(ToLower(currentMoveString[0]));
12205         fromY = DROP_RANK;
12206         toX = currentMoveString[2] - AAA;
12207         toY = currentMoveString[3] - ONE;
12208         break;
12209
12210       case WhiteWins:
12211       case BlackWins:
12212       case GameIsDrawn:
12213       case GameUnfinished:
12214         if (appData.debugMode)
12215           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12216         p = strchr(yy_text, '{');
12217         if (p == NULL) p = strchr(yy_text, '(');
12218         if (p == NULL) {
12219             p = yy_text;
12220             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12221         } else {
12222             q = strchr(p, *p == '{' ? '}' : ')');
12223             if (q != NULL) *q = NULLCHAR;
12224             p++;
12225         }
12226         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12227         GameEnds(moveType, p, GE_FILE);
12228         done = TRUE;
12229         if (cmailMsgLoaded) {
12230             ClearHighlights();
12231             flipView = WhiteOnMove(currentMove);
12232             if (moveType == GameUnfinished) flipView = !flipView;
12233             if (appData.debugMode)
12234               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12235         }
12236         break;
12237
12238       case EndOfFile:
12239         if (appData.debugMode)
12240           fprintf(debugFP, "Parser hit end of file\n");
12241         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12242           case MT_NONE:
12243           case MT_CHECK:
12244             break;
12245           case MT_CHECKMATE:
12246           case MT_STAINMATE:
12247             if (WhiteOnMove(currentMove)) {
12248                 GameEnds(BlackWins, "Black mates", GE_FILE);
12249             } else {
12250                 GameEnds(WhiteWins, "White mates", GE_FILE);
12251             }
12252             break;
12253           case MT_STALEMATE:
12254             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12255             break;
12256         }
12257         done = TRUE;
12258         break;
12259
12260       case MoveNumberOne:
12261         if (lastLoadGameStart == GNUChessGame) {
12262             /* GNUChessGames have numbers, but they aren't move numbers */
12263             if (appData.debugMode)
12264               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12265                       yy_text, (int) moveType);
12266             return LoadGameOneMove(EndOfFile); /* tail recursion */
12267         }
12268         /* else fall thru */
12269
12270       case XBoardGame:
12271       case GNUChessGame:
12272       case PGNTag:
12273         /* Reached start of next game in file */
12274         if (appData.debugMode)
12275           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12276         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12277           case MT_NONE:
12278           case MT_CHECK:
12279             break;
12280           case MT_CHECKMATE:
12281           case MT_STAINMATE:
12282             if (WhiteOnMove(currentMove)) {
12283                 GameEnds(BlackWins, "Black mates", GE_FILE);
12284             } else {
12285                 GameEnds(WhiteWins, "White mates", GE_FILE);
12286             }
12287             break;
12288           case MT_STALEMATE:
12289             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12290             break;
12291         }
12292         done = TRUE;
12293         break;
12294
12295       case PositionDiagram:     /* should not happen; ignore */
12296       case ElapsedTime:         /* ignore */
12297       case NAG:                 /* ignore */
12298         if (appData.debugMode)
12299           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12300                   yy_text, (int) moveType);
12301         return LoadGameOneMove(EndOfFile); /* tail recursion */
12302
12303       case IllegalMove:
12304         if (appData.testLegality) {
12305             if (appData.debugMode)
12306               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12307             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12308                     (forwardMostMove / 2) + 1,
12309                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12310             DisplayError(move, 0);
12311             done = TRUE;
12312         } else {
12313             if (appData.debugMode)
12314               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12315                       yy_text, currentMoveString);
12316             if(currentMoveString[1] == '@') {
12317                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12318                 fromY = DROP_RANK;
12319             } else {
12320                 fromX = currentMoveString[0] - AAA;
12321                 fromY = currentMoveString[1] - ONE;
12322             }
12323             toX = currentMoveString[2] - AAA;
12324             toY = currentMoveString[3] - ONE;
12325             promoChar = currentMoveString[4];
12326         }
12327         break;
12328
12329       case AmbiguousMove:
12330         if (appData.debugMode)
12331           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12332         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12333                 (forwardMostMove / 2) + 1,
12334                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12335         DisplayError(move, 0);
12336         done = TRUE;
12337         break;
12338
12339       default:
12340       case ImpossibleMove:
12341         if (appData.debugMode)
12342           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12343         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12344                 (forwardMostMove / 2) + 1,
12345                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12346         DisplayError(move, 0);
12347         done = TRUE;
12348         break;
12349     }
12350
12351     if (done) {
12352         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12353             DrawPosition(FALSE, boards[currentMove]);
12354             DisplayBothClocks();
12355             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12356               DisplayComment(currentMove - 1, commentList[currentMove]);
12357         }
12358         (void) StopLoadGameTimer();
12359         gameFileFP = NULL;
12360         cmailOldMove = forwardMostMove;
12361         return FALSE;
12362     } else {
12363         /* currentMoveString is set as a side-effect of yylex */
12364
12365         thinkOutput[0] = NULLCHAR;
12366         MakeMove(fromX, fromY, toX, toY, promoChar);
12367         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12368         currentMove = forwardMostMove;
12369         return TRUE;
12370     }
12371 }
12372
12373 /* Load the nth game from the given file */
12374 int
12375 LoadGameFromFile (char *filename, int n, char *title, int useList)
12376 {
12377     FILE *f;
12378     char buf[MSG_SIZ];
12379
12380     if (strcmp(filename, "-") == 0) {
12381         f = stdin;
12382         title = "stdin";
12383     } else {
12384         f = fopen(filename, "rb");
12385         if (f == NULL) {
12386           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12387             DisplayError(buf, errno);
12388             return FALSE;
12389         }
12390     }
12391     if (fseek(f, 0, 0) == -1) {
12392         /* f is not seekable; probably a pipe */
12393         useList = FALSE;
12394     }
12395     if (useList && n == 0) {
12396         int error = GameListBuild(f);
12397         if (error) {
12398             DisplayError(_("Cannot build game list"), error);
12399         } else if (!ListEmpty(&gameList) &&
12400                    ((ListGame *) gameList.tailPred)->number > 1) {
12401             GameListPopUp(f, title);
12402             return TRUE;
12403         }
12404         GameListDestroy();
12405         n = 1;
12406     }
12407     if (n == 0) n = 1;
12408     return LoadGame(f, n, title, FALSE);
12409 }
12410
12411
12412 void
12413 MakeRegisteredMove ()
12414 {
12415     int fromX, fromY, toX, toY;
12416     char promoChar;
12417     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12418         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12419           case CMAIL_MOVE:
12420           case CMAIL_DRAW:
12421             if (appData.debugMode)
12422               fprintf(debugFP, "Restoring %s for game %d\n",
12423                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12424
12425             thinkOutput[0] = NULLCHAR;
12426             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12427             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12428             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12429             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12430             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12431             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12432             MakeMove(fromX, fromY, toX, toY, promoChar);
12433             ShowMove(fromX, fromY, toX, toY);
12434
12435             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12436               case MT_NONE:
12437               case MT_CHECK:
12438                 break;
12439
12440               case MT_CHECKMATE:
12441               case MT_STAINMATE:
12442                 if (WhiteOnMove(currentMove)) {
12443                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12444                 } else {
12445                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12446                 }
12447                 break;
12448
12449               case MT_STALEMATE:
12450                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12451                 break;
12452             }
12453
12454             break;
12455
12456           case CMAIL_RESIGN:
12457             if (WhiteOnMove(currentMove)) {
12458                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12459             } else {
12460                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12461             }
12462             break;
12463
12464           case CMAIL_ACCEPT:
12465             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12466             break;
12467
12468           default:
12469             break;
12470         }
12471     }
12472
12473     return;
12474 }
12475
12476 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12477 int
12478 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12479 {
12480     int retVal;
12481
12482     if (gameNumber > nCmailGames) {
12483         DisplayError(_("No more games in this message"), 0);
12484         return FALSE;
12485     }
12486     if (f == lastLoadGameFP) {
12487         int offset = gameNumber - lastLoadGameNumber;
12488         if (offset == 0) {
12489             cmailMsg[0] = NULLCHAR;
12490             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12491                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12492                 nCmailMovesRegistered--;
12493             }
12494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12495             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12496                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12497             }
12498         } else {
12499             if (! RegisterMove()) return FALSE;
12500         }
12501     }
12502
12503     retVal = LoadGame(f, gameNumber, title, useList);
12504
12505     /* Make move registered during previous look at this game, if any */
12506     MakeRegisteredMove();
12507
12508     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12509         commentList[currentMove]
12510           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12511         DisplayComment(currentMove - 1, commentList[currentMove]);
12512     }
12513
12514     return retVal;
12515 }
12516
12517 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12518 int
12519 ReloadGame (int offset)
12520 {
12521     int gameNumber = lastLoadGameNumber + offset;
12522     if (lastLoadGameFP == NULL) {
12523         DisplayError(_("No game has been loaded yet"), 0);
12524         return FALSE;
12525     }
12526     if (gameNumber <= 0) {
12527         DisplayError(_("Can't back up any further"), 0);
12528         return FALSE;
12529     }
12530     if (cmailMsgLoaded) {
12531         return CmailLoadGame(lastLoadGameFP, gameNumber,
12532                              lastLoadGameTitle, lastLoadGameUseList);
12533     } else {
12534         return LoadGame(lastLoadGameFP, gameNumber,
12535                         lastLoadGameTitle, lastLoadGameUseList);
12536     }
12537 }
12538
12539 int keys[EmptySquare+1];
12540
12541 int
12542 PositionMatches (Board b1, Board b2)
12543 {
12544     int r, f, sum=0;
12545     switch(appData.searchMode) {
12546         case 1: return CompareWithRights(b1, b2);
12547         case 2:
12548             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12549                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12550             }
12551             return TRUE;
12552         case 3:
12553             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12554               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12555                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12556             }
12557             return sum==0;
12558         case 4:
12559             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12560                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12561             }
12562             return sum==0;
12563     }
12564     return TRUE;
12565 }
12566
12567 #define Q_PROMO  4
12568 #define Q_EP     3
12569 #define Q_BCASTL 2
12570 #define Q_WCASTL 1
12571
12572 int pieceList[256], quickBoard[256];
12573 ChessSquare pieceType[256] = { EmptySquare };
12574 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12575 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12576 int soughtTotal, turn;
12577 Boolean epOK, flipSearch;
12578
12579 typedef struct {
12580     unsigned char piece, to;
12581 } Move;
12582
12583 #define DSIZE (250000)
12584
12585 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12586 Move *moveDatabase = initialSpace;
12587 unsigned int movePtr, dataSize = DSIZE;
12588
12589 int
12590 MakePieceList (Board board, int *counts)
12591 {
12592     int r, f, n=Q_PROMO, total=0;
12593     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12594     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12595         int sq = f + (r<<4);
12596         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12597             quickBoard[sq] = ++n;
12598             pieceList[n] = sq;
12599             pieceType[n] = board[r][f];
12600             counts[board[r][f]]++;
12601             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12602             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12603             total++;
12604         }
12605     }
12606     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12607     return total;
12608 }
12609
12610 void
12611 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12612 {
12613     int sq = fromX + (fromY<<4);
12614     int piece = quickBoard[sq], rook;
12615     quickBoard[sq] = 0;
12616     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12617     if(piece == pieceList[1] && fromY == toY) {
12618       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12619         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12620         moveDatabase[movePtr++].piece = Q_WCASTL;
12621         quickBoard[sq] = piece;
12622         piece = quickBoard[from]; quickBoard[from] = 0;
12623         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12624       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12625         quickBoard[sq] = 0; // remove Rook
12626         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12627         moveDatabase[movePtr++].piece = Q_WCASTL;
12628         quickBoard[sq] = pieceList[1]; // put King
12629         piece = rook;
12630         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12631       }
12632     } else
12633     if(piece == pieceList[2] && fromY == toY) {
12634       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12635         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12636         moveDatabase[movePtr++].piece = Q_BCASTL;
12637         quickBoard[sq] = piece;
12638         piece = quickBoard[from]; quickBoard[from] = 0;
12639         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12640       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12641         quickBoard[sq] = 0; // remove Rook
12642         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12643         moveDatabase[movePtr++].piece = Q_BCASTL;
12644         quickBoard[sq] = pieceList[2]; // put King
12645         piece = rook;
12646         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12647       }
12648     } else
12649     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12650         quickBoard[(fromY<<4)+toX] = 0;
12651         moveDatabase[movePtr].piece = Q_EP;
12652         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12653         moveDatabase[movePtr].to = sq;
12654     } else
12655     if(promoPiece != pieceType[piece]) {
12656         moveDatabase[movePtr++].piece = Q_PROMO;
12657         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12658     }
12659     moveDatabase[movePtr].piece = piece;
12660     quickBoard[sq] = piece;
12661     movePtr++;
12662 }
12663
12664 int
12665 PackGame (Board board)
12666 {
12667     Move *newSpace = NULL;
12668     moveDatabase[movePtr].piece = 0; // terminate previous game
12669     if(movePtr > dataSize) {
12670         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12671         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12672         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12673         if(newSpace) {
12674             int i;
12675             Move *p = moveDatabase, *q = newSpace;
12676             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12677             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12678             moveDatabase = newSpace;
12679         } else { // calloc failed, we must be out of memory. Too bad...
12680             dataSize = 0; // prevent calloc events for all subsequent games
12681             return 0;     // and signal this one isn't cached
12682         }
12683     }
12684     movePtr++;
12685     MakePieceList(board, counts);
12686     return movePtr;
12687 }
12688
12689 int
12690 QuickCompare (Board board, int *minCounts, int *maxCounts)
12691 {   // compare according to search mode
12692     int r, f;
12693     switch(appData.searchMode)
12694     {
12695       case 1: // exact position match
12696         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12697         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12698             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12699         }
12700         break;
12701       case 2: // can have extra material on empty squares
12702         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12703             if(board[r][f] == EmptySquare) continue;
12704             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12705         }
12706         break;
12707       case 3: // material with exact Pawn structure
12708         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12709             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12710             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12711         } // fall through to material comparison
12712       case 4: // exact material
12713         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12714         break;
12715       case 6: // material range with given imbalance
12716         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12717         // fall through to range comparison
12718       case 5: // material range
12719         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12720     }
12721     return TRUE;
12722 }
12723
12724 int
12725 QuickScan (Board board, Move *move)
12726 {   // reconstruct game,and compare all positions in it
12727     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12728     do {
12729         int piece = move->piece;
12730         int to = move->to, from = pieceList[piece];
12731         if(found < 0) { // if already found just scan to game end for final piece count
12732           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12733            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12734            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12735                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12736             ) {
12737             static int lastCounts[EmptySquare+1];
12738             int i;
12739             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12740             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12741           } else stretch = 0;
12742           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12743           if(found >= 0 && !appData.minPieces) return found;
12744         }
12745         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12746           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12747           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12748             piece = (++move)->piece;
12749             from = pieceList[piece];
12750             counts[pieceType[piece]]--;
12751             pieceType[piece] = (ChessSquare) move->to;
12752             counts[move->to]++;
12753           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12754             counts[pieceType[quickBoard[to]]]--;
12755             quickBoard[to] = 0; total--;
12756             move++;
12757             continue;
12758           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12759             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12760             from  = pieceList[piece]; // so this must be King
12761             quickBoard[from] = 0;
12762             pieceList[piece] = to;
12763             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12764             quickBoard[from] = 0; // rook
12765             quickBoard[to] = piece;
12766             to = move->to; piece = move->piece;
12767             goto aftercastle;
12768           }
12769         }
12770         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12771         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12772         quickBoard[from] = 0;
12773       aftercastle:
12774         quickBoard[to] = piece;
12775         pieceList[piece] = to;
12776         cnt++; turn ^= 3;
12777         move++;
12778     } while(1);
12779 }
12780
12781 void
12782 InitSearch ()
12783 {
12784     int r, f;
12785     flipSearch = FALSE;
12786     CopyBoard(soughtBoard, boards[currentMove]);
12787     soughtTotal = MakePieceList(soughtBoard, maxSought);
12788     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12789     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12790     CopyBoard(reverseBoard, boards[currentMove]);
12791     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12792         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12793         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12794         reverseBoard[r][f] = piece;
12795     }
12796     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12797     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12798     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12799                  || (boards[currentMove][CASTLING][2] == NoRights ||
12800                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12801                  && (boards[currentMove][CASTLING][5] == NoRights ||
12802                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12803       ) {
12804         flipSearch = TRUE;
12805         CopyBoard(flipBoard, soughtBoard);
12806         CopyBoard(rotateBoard, reverseBoard);
12807         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12808             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12809             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12810         }
12811     }
12812     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12813     if(appData.searchMode >= 5) {
12814         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12815         MakePieceList(soughtBoard, minSought);
12816         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12817     }
12818     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12819         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12820 }
12821
12822 GameInfo dummyInfo;
12823 static int creatingBook;
12824
12825 int
12826 GameContainsPosition (FILE *f, ListGame *lg)
12827 {
12828     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12829     int fromX, fromY, toX, toY;
12830     char promoChar;
12831     static int initDone=FALSE;
12832
12833     // weed out games based on numerical tag comparison
12834     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12835     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12836     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12837     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12838     if(!initDone) {
12839         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12840         initDone = TRUE;
12841     }
12842     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12843     else CopyBoard(boards[scratch], initialPosition); // default start position
12844     if(lg->moves) {
12845         turn = btm + 1;
12846         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12847         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12848     }
12849     if(btm) plyNr++;
12850     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12851     fseek(f, lg->offset, 0);
12852     yynewfile(f);
12853     while(1) {
12854         yyboardindex = scratch;
12855         quickFlag = plyNr+1;
12856         next = Myylex();
12857         quickFlag = 0;
12858         switch(next) {
12859             case PGNTag:
12860                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12861             default:
12862                 continue;
12863
12864             case XBoardGame:
12865             case GNUChessGame:
12866                 if(plyNr) return -1; // after we have seen moves, this is for new game
12867               continue;
12868
12869             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12870             case ImpossibleMove:
12871             case WhiteWins: // game ends here with these four
12872             case BlackWins:
12873             case GameIsDrawn:
12874             case GameUnfinished:
12875                 return -1;
12876
12877             case IllegalMove:
12878                 if(appData.testLegality) return -1;
12879             case WhiteCapturesEnPassant:
12880             case BlackCapturesEnPassant:
12881             case WhitePromotion:
12882             case BlackPromotion:
12883             case WhiteNonPromotion:
12884             case BlackNonPromotion:
12885             case NormalMove:
12886             case FirstLeg:
12887             case WhiteKingSideCastle:
12888             case WhiteQueenSideCastle:
12889             case BlackKingSideCastle:
12890             case BlackQueenSideCastle:
12891             case WhiteKingSideCastleWild:
12892             case WhiteQueenSideCastleWild:
12893             case BlackKingSideCastleWild:
12894             case BlackQueenSideCastleWild:
12895             case WhiteHSideCastleFR:
12896             case WhiteASideCastleFR:
12897             case BlackHSideCastleFR:
12898             case BlackASideCastleFR:
12899                 fromX = currentMoveString[0] - AAA;
12900                 fromY = currentMoveString[1] - ONE;
12901                 toX = currentMoveString[2] - AAA;
12902                 toY = currentMoveString[3] - ONE;
12903                 promoChar = currentMoveString[4];
12904                 break;
12905             case WhiteDrop:
12906             case BlackDrop:
12907                 fromX = next == WhiteDrop ?
12908                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12909                   (int) CharToPiece(ToLower(currentMoveString[0]));
12910                 fromY = DROP_RANK;
12911                 toX = currentMoveString[2] - AAA;
12912                 toY = currentMoveString[3] - ONE;
12913                 promoChar = 0;
12914                 break;
12915         }
12916         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12917         plyNr++;
12918         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12919         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12920         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12921         if(appData.findMirror) {
12922             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12923             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12924         }
12925     }
12926 }
12927
12928 /* Load the nth game from open file f */
12929 int
12930 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12931 {
12932     ChessMove cm;
12933     char buf[MSG_SIZ];
12934     int gn = gameNumber;
12935     ListGame *lg = NULL;
12936     int numPGNTags = 0, i;
12937     int err, pos = -1;
12938     GameMode oldGameMode;
12939     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12940     char oldName[MSG_SIZ];
12941
12942     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12943
12944     if (appData.debugMode)
12945         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12946
12947     if (gameMode == Training )
12948         SetTrainingModeOff();
12949
12950     oldGameMode = gameMode;
12951     if (gameMode != BeginningOfGame) {
12952       Reset(FALSE, TRUE);
12953     }
12954     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12955
12956     gameFileFP = f;
12957     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12958         fclose(lastLoadGameFP);
12959     }
12960
12961     if (useList) {
12962         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12963
12964         if (lg) {
12965             fseek(f, lg->offset, 0);
12966             GameListHighlight(gameNumber);
12967             pos = lg->position;
12968             gn = 1;
12969         }
12970         else {
12971             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12972               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12973             else
12974             DisplayError(_("Game number out of range"), 0);
12975             return FALSE;
12976         }
12977     } else {
12978         GameListDestroy();
12979         if (fseek(f, 0, 0) == -1) {
12980             if (f == lastLoadGameFP ?
12981                 gameNumber == lastLoadGameNumber + 1 :
12982                 gameNumber == 1) {
12983                 gn = 1;
12984             } else {
12985                 DisplayError(_("Can't seek on game file"), 0);
12986                 return FALSE;
12987             }
12988         }
12989     }
12990     lastLoadGameFP = f;
12991     lastLoadGameNumber = gameNumber;
12992     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12993     lastLoadGameUseList = useList;
12994
12995     yynewfile(f);
12996
12997     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12998       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12999                 lg->gameInfo.black);
13000             DisplayTitle(buf);
13001     } else if (*title != NULLCHAR) {
13002         if (gameNumber > 1) {
13003           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13004             DisplayTitle(buf);
13005         } else {
13006             DisplayTitle(title);
13007         }
13008     }
13009
13010     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13011         gameMode = PlayFromGameFile;
13012         ModeHighlight();
13013     }
13014
13015     currentMove = forwardMostMove = backwardMostMove = 0;
13016     CopyBoard(boards[0], initialPosition);
13017     StopClocks();
13018
13019     /*
13020      * Skip the first gn-1 games in the file.
13021      * Also skip over anything that precedes an identifiable
13022      * start of game marker, to avoid being confused by
13023      * garbage at the start of the file.  Currently
13024      * recognized start of game markers are the move number "1",
13025      * the pattern "gnuchess .* game", the pattern
13026      * "^[#;%] [^ ]* game file", and a PGN tag block.
13027      * A game that starts with one of the latter two patterns
13028      * will also have a move number 1, possibly
13029      * following a position diagram.
13030      * 5-4-02: Let's try being more lenient and allowing a game to
13031      * start with an unnumbered move.  Does that break anything?
13032      */
13033     cm = lastLoadGameStart = EndOfFile;
13034     while (gn > 0) {
13035         yyboardindex = forwardMostMove;
13036         cm = (ChessMove) Myylex();
13037         switch (cm) {
13038           case EndOfFile:
13039             if (cmailMsgLoaded) {
13040                 nCmailGames = CMAIL_MAX_GAMES - gn;
13041             } else {
13042                 Reset(TRUE, TRUE);
13043                 DisplayError(_("Game not found in file"), 0);
13044             }
13045             return FALSE;
13046
13047           case GNUChessGame:
13048           case XBoardGame:
13049             gn--;
13050             lastLoadGameStart = cm;
13051             break;
13052
13053           case MoveNumberOne:
13054             switch (lastLoadGameStart) {
13055               case GNUChessGame:
13056               case XBoardGame:
13057               case PGNTag:
13058                 break;
13059               case MoveNumberOne:
13060               case EndOfFile:
13061                 gn--;           /* count this game */
13062                 lastLoadGameStart = cm;
13063                 break;
13064               default:
13065                 /* impossible */
13066                 break;
13067             }
13068             break;
13069
13070           case PGNTag:
13071             switch (lastLoadGameStart) {
13072               case GNUChessGame:
13073               case PGNTag:
13074               case MoveNumberOne:
13075               case EndOfFile:
13076                 gn--;           /* count this game */
13077                 lastLoadGameStart = cm;
13078                 break;
13079               case XBoardGame:
13080                 lastLoadGameStart = cm; /* game counted already */
13081                 break;
13082               default:
13083                 /* impossible */
13084                 break;
13085             }
13086             if (gn > 0) {
13087                 do {
13088                     yyboardindex = forwardMostMove;
13089                     cm = (ChessMove) Myylex();
13090                 } while (cm == PGNTag || cm == Comment);
13091             }
13092             break;
13093
13094           case WhiteWins:
13095           case BlackWins:
13096           case GameIsDrawn:
13097             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13098                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13099                     != CMAIL_OLD_RESULT) {
13100                     nCmailResults ++ ;
13101                     cmailResult[  CMAIL_MAX_GAMES
13102                                 - gn - 1] = CMAIL_OLD_RESULT;
13103                 }
13104             }
13105             break;
13106
13107           case NormalMove:
13108           case FirstLeg:
13109             /* Only a NormalMove can be at the start of a game
13110              * without a position diagram. */
13111             if (lastLoadGameStart == EndOfFile ) {
13112               gn--;
13113               lastLoadGameStart = MoveNumberOne;
13114             }
13115             break;
13116
13117           default:
13118             break;
13119         }
13120     }
13121
13122     if (appData.debugMode)
13123       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13124
13125     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13126
13127     if (cm == XBoardGame) {
13128         /* Skip any header junk before position diagram and/or move 1 */
13129         for (;;) {
13130             yyboardindex = forwardMostMove;
13131             cm = (ChessMove) Myylex();
13132
13133             if (cm == EndOfFile ||
13134                 cm == GNUChessGame || cm == XBoardGame) {
13135                 /* Empty game; pretend end-of-file and handle later */
13136                 cm = EndOfFile;
13137                 break;
13138             }
13139
13140             if (cm == MoveNumberOne || cm == PositionDiagram ||
13141                 cm == PGNTag || cm == Comment)
13142               break;
13143         }
13144     } else if (cm == GNUChessGame) {
13145         if (gameInfo.event != NULL) {
13146             free(gameInfo.event);
13147         }
13148         gameInfo.event = StrSave(yy_text);
13149     }
13150
13151     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13152     while (cm == PGNTag) {
13153         if (appData.debugMode)
13154           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13155         err = ParsePGNTag(yy_text, &gameInfo);
13156         if (!err) numPGNTags++;
13157
13158         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13159         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13160             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13161             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13162             InitPosition(TRUE);
13163             oldVariant = gameInfo.variant;
13164             if (appData.debugMode)
13165               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13166         }
13167
13168
13169         if (gameInfo.fen != NULL) {
13170           Board initial_position;
13171           startedFromSetupPosition = TRUE;
13172           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13173             Reset(TRUE, TRUE);
13174             DisplayError(_("Bad FEN position in file"), 0);
13175             return FALSE;
13176           }
13177           CopyBoard(boards[0], initial_position);
13178           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13179             CopyBoard(initialPosition, initial_position);
13180           if (blackPlaysFirst) {
13181             currentMove = forwardMostMove = backwardMostMove = 1;
13182             CopyBoard(boards[1], initial_position);
13183             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13184             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13185             timeRemaining[0][1] = whiteTimeRemaining;
13186             timeRemaining[1][1] = blackTimeRemaining;
13187             if (commentList[0] != NULL) {
13188               commentList[1] = commentList[0];
13189               commentList[0] = NULL;
13190             }
13191           } else {
13192             currentMove = forwardMostMove = backwardMostMove = 0;
13193           }
13194           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13195           {   int i;
13196               initialRulePlies = FENrulePlies;
13197               for( i=0; i< nrCastlingRights; i++ )
13198                   initialRights[i] = initial_position[CASTLING][i];
13199           }
13200           yyboardindex = forwardMostMove;
13201           free(gameInfo.fen);
13202           gameInfo.fen = NULL;
13203         }
13204
13205         yyboardindex = forwardMostMove;
13206         cm = (ChessMove) Myylex();
13207
13208         /* Handle comments interspersed among the tags */
13209         while (cm == Comment) {
13210             char *p;
13211             if (appData.debugMode)
13212               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13213             p = yy_text;
13214             AppendComment(currentMove, p, FALSE);
13215             yyboardindex = forwardMostMove;
13216             cm = (ChessMove) Myylex();
13217         }
13218     }
13219
13220     /* don't rely on existence of Event tag since if game was
13221      * pasted from clipboard the Event tag may not exist
13222      */
13223     if (numPGNTags > 0){
13224         char *tags;
13225         if (gameInfo.variant == VariantNormal) {
13226           VariantClass v = StringToVariant(gameInfo.event);
13227           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13228           if(v < VariantShogi) gameInfo.variant = v;
13229         }
13230         if (!matchMode) {
13231           if( appData.autoDisplayTags ) {
13232             tags = PGNTags(&gameInfo);
13233             TagsPopUp(tags, CmailMsg());
13234             free(tags);
13235           }
13236         }
13237     } else {
13238         /* Make something up, but don't display it now */
13239         SetGameInfo();
13240         TagsPopDown();
13241     }
13242
13243     if (cm == PositionDiagram) {
13244         int i, j;
13245         char *p;
13246         Board initial_position;
13247
13248         if (appData.debugMode)
13249           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13250
13251         if (!startedFromSetupPosition) {
13252             p = yy_text;
13253             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13254               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13255                 switch (*p) {
13256                   case '{':
13257                   case '[':
13258                   case '-':
13259                   case ' ':
13260                   case '\t':
13261                   case '\n':
13262                   case '\r':
13263                     break;
13264                   default:
13265                     initial_position[i][j++] = CharToPiece(*p);
13266                     break;
13267                 }
13268             while (*p == ' ' || *p == '\t' ||
13269                    *p == '\n' || *p == '\r') p++;
13270
13271             if (strncmp(p, "black", strlen("black"))==0)
13272               blackPlaysFirst = TRUE;
13273             else
13274               blackPlaysFirst = FALSE;
13275             startedFromSetupPosition = TRUE;
13276
13277             CopyBoard(boards[0], initial_position);
13278             if (blackPlaysFirst) {
13279                 currentMove = forwardMostMove = backwardMostMove = 1;
13280                 CopyBoard(boards[1], initial_position);
13281                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13282                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13283                 timeRemaining[0][1] = whiteTimeRemaining;
13284                 timeRemaining[1][1] = blackTimeRemaining;
13285                 if (commentList[0] != NULL) {
13286                     commentList[1] = commentList[0];
13287                     commentList[0] = NULL;
13288                 }
13289             } else {
13290                 currentMove = forwardMostMove = backwardMostMove = 0;
13291             }
13292         }
13293         yyboardindex = forwardMostMove;
13294         cm = (ChessMove) Myylex();
13295     }
13296
13297   if(!creatingBook) {
13298     if (first.pr == NoProc) {
13299         StartChessProgram(&first);
13300     }
13301     InitChessProgram(&first, FALSE);
13302     if(gameInfo.variant == VariantUnknown && *oldName) {
13303         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13304         gameInfo.variant = v;
13305     }
13306     SendToProgram("force\n", &first);
13307     if (startedFromSetupPosition) {
13308         SendBoard(&first, forwardMostMove);
13309     if (appData.debugMode) {
13310         fprintf(debugFP, "Load Game\n");
13311     }
13312         DisplayBothClocks();
13313     }
13314   }
13315
13316     /* [HGM] server: flag to write setup moves in broadcast file as one */
13317     loadFlag = appData.suppressLoadMoves;
13318
13319     while (cm == Comment) {
13320         char *p;
13321         if (appData.debugMode)
13322           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13323         p = yy_text;
13324         AppendComment(currentMove, p, FALSE);
13325         yyboardindex = forwardMostMove;
13326         cm = (ChessMove) Myylex();
13327     }
13328
13329     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13330         cm == WhiteWins || cm == BlackWins ||
13331         cm == GameIsDrawn || cm == GameUnfinished) {
13332         DisplayMessage("", _("No moves in game"));
13333         if (cmailMsgLoaded) {
13334             if (appData.debugMode)
13335               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13336             ClearHighlights();
13337             flipView = FALSE;
13338         }
13339         DrawPosition(FALSE, boards[currentMove]);
13340         DisplayBothClocks();
13341         gameMode = EditGame;
13342         ModeHighlight();
13343         gameFileFP = NULL;
13344         cmailOldMove = 0;
13345         return TRUE;
13346     }
13347
13348     // [HGM] PV info: routine tests if comment empty
13349     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13350         DisplayComment(currentMove - 1, commentList[currentMove]);
13351     }
13352     if (!matchMode && appData.timeDelay != 0)
13353       DrawPosition(FALSE, boards[currentMove]);
13354
13355     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13356       programStats.ok_to_send = 1;
13357     }
13358
13359     /* if the first token after the PGN tags is a move
13360      * and not move number 1, retrieve it from the parser
13361      */
13362     if (cm != MoveNumberOne)
13363         LoadGameOneMove(cm);
13364
13365     /* load the remaining moves from the file */
13366     while (LoadGameOneMove(EndOfFile)) {
13367       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13368       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13369     }
13370
13371     /* rewind to the start of the game */
13372     currentMove = backwardMostMove;
13373
13374     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13375
13376     if (oldGameMode == AnalyzeFile) {
13377       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13378       AnalyzeFileEvent();
13379     } else
13380     if (oldGameMode == AnalyzeMode) {
13381       AnalyzeFileEvent();
13382     }
13383
13384     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13385         long int w, b; // [HGM] adjourn: restore saved clock times
13386         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13387         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13388             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13389             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13390         }
13391     }
13392
13393     if(creatingBook) return TRUE;
13394     if (!matchMode && pos > 0) {
13395         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13396     } else
13397     if (matchMode || appData.timeDelay == 0) {
13398       ToEndEvent();
13399     } else if (appData.timeDelay > 0) {
13400       AutoPlayGameLoop();
13401     }
13402
13403     if (appData.debugMode)
13404         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13405
13406     loadFlag = 0; /* [HGM] true game starts */
13407     return TRUE;
13408 }
13409
13410 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13411 int
13412 ReloadPosition (int offset)
13413 {
13414     int positionNumber = lastLoadPositionNumber + offset;
13415     if (lastLoadPositionFP == NULL) {
13416         DisplayError(_("No position has been loaded yet"), 0);
13417         return FALSE;
13418     }
13419     if (positionNumber <= 0) {
13420         DisplayError(_("Can't back up any further"), 0);
13421         return FALSE;
13422     }
13423     return LoadPosition(lastLoadPositionFP, positionNumber,
13424                         lastLoadPositionTitle);
13425 }
13426
13427 /* Load the nth position from the given file */
13428 int
13429 LoadPositionFromFile (char *filename, int n, char *title)
13430 {
13431     FILE *f;
13432     char buf[MSG_SIZ];
13433
13434     if (strcmp(filename, "-") == 0) {
13435         return LoadPosition(stdin, n, "stdin");
13436     } else {
13437         f = fopen(filename, "rb");
13438         if (f == NULL) {
13439             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13440             DisplayError(buf, errno);
13441             return FALSE;
13442         } else {
13443             return LoadPosition(f, n, title);
13444         }
13445     }
13446 }
13447
13448 /* Load the nth position from the given open file, and close it */
13449 int
13450 LoadPosition (FILE *f, int positionNumber, char *title)
13451 {
13452     char *p, line[MSG_SIZ];
13453     Board initial_position;
13454     int i, j, fenMode, pn;
13455
13456     if (gameMode == Training )
13457         SetTrainingModeOff();
13458
13459     if (gameMode != BeginningOfGame) {
13460         Reset(FALSE, TRUE);
13461     }
13462     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13463         fclose(lastLoadPositionFP);
13464     }
13465     if (positionNumber == 0) positionNumber = 1;
13466     lastLoadPositionFP = f;
13467     lastLoadPositionNumber = positionNumber;
13468     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13469     if (first.pr == NoProc && !appData.noChessProgram) {
13470       StartChessProgram(&first);
13471       InitChessProgram(&first, FALSE);
13472     }
13473     pn = positionNumber;
13474     if (positionNumber < 0) {
13475         /* Negative position number means to seek to that byte offset */
13476         if (fseek(f, -positionNumber, 0) == -1) {
13477             DisplayError(_("Can't seek on position file"), 0);
13478             return FALSE;
13479         };
13480         pn = 1;
13481     } else {
13482         if (fseek(f, 0, 0) == -1) {
13483             if (f == lastLoadPositionFP ?
13484                 positionNumber == lastLoadPositionNumber + 1 :
13485                 positionNumber == 1) {
13486                 pn = 1;
13487             } else {
13488                 DisplayError(_("Can't seek on position file"), 0);
13489                 return FALSE;
13490             }
13491         }
13492     }
13493     /* See if this file is FEN or old-style xboard */
13494     if (fgets(line, MSG_SIZ, f) == NULL) {
13495         DisplayError(_("Position not found in file"), 0);
13496         return FALSE;
13497     }
13498     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13499     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13500
13501     if (pn >= 2) {
13502         if (fenMode || line[0] == '#') pn--;
13503         while (pn > 0) {
13504             /* skip positions before number pn */
13505             if (fgets(line, MSG_SIZ, f) == NULL) {
13506                 Reset(TRUE, TRUE);
13507                 DisplayError(_("Position not found in file"), 0);
13508                 return FALSE;
13509             }
13510             if (fenMode || line[0] == '#') pn--;
13511         }
13512     }
13513
13514     if (fenMode) {
13515         char *p;
13516         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13517             DisplayError(_("Bad FEN position in file"), 0);
13518             return FALSE;
13519         }
13520         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13521             sscanf(p+3, "%s", bestMove);
13522         } else *bestMove = NULLCHAR;
13523     } else {
13524         (void) fgets(line, MSG_SIZ, f);
13525         (void) fgets(line, MSG_SIZ, f);
13526
13527         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13528             (void) fgets(line, MSG_SIZ, f);
13529             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13530                 if (*p == ' ')
13531                   continue;
13532                 initial_position[i][j++] = CharToPiece(*p);
13533             }
13534         }
13535
13536         blackPlaysFirst = FALSE;
13537         if (!feof(f)) {
13538             (void) fgets(line, MSG_SIZ, f);
13539             if (strncmp(line, "black", strlen("black"))==0)
13540               blackPlaysFirst = TRUE;
13541         }
13542     }
13543     startedFromSetupPosition = TRUE;
13544
13545     CopyBoard(boards[0], initial_position);
13546     if (blackPlaysFirst) {
13547         currentMove = forwardMostMove = backwardMostMove = 1;
13548         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13549         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13550         CopyBoard(boards[1], initial_position);
13551         DisplayMessage("", _("Black to play"));
13552     } else {
13553         currentMove = forwardMostMove = backwardMostMove = 0;
13554         DisplayMessage("", _("White to play"));
13555     }
13556     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13557     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13558         SendToProgram("force\n", &first);
13559         SendBoard(&first, forwardMostMove);
13560     }
13561     if (appData.debugMode) {
13562 int i, j;
13563   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13564   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13565         fprintf(debugFP, "Load Position\n");
13566     }
13567
13568     if (positionNumber > 1) {
13569       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13570         DisplayTitle(line);
13571     } else {
13572         DisplayTitle(title);
13573     }
13574     gameMode = EditGame;
13575     ModeHighlight();
13576     ResetClocks();
13577     timeRemaining[0][1] = whiteTimeRemaining;
13578     timeRemaining[1][1] = blackTimeRemaining;
13579     DrawPosition(FALSE, boards[currentMove]);
13580
13581     return TRUE;
13582 }
13583
13584
13585 void
13586 CopyPlayerNameIntoFileName (char **dest, char *src)
13587 {
13588     while (*src != NULLCHAR && *src != ',') {
13589         if (*src == ' ') {
13590             *(*dest)++ = '_';
13591             src++;
13592         } else {
13593             *(*dest)++ = *src++;
13594         }
13595     }
13596 }
13597
13598 char *
13599 DefaultFileName (char *ext)
13600 {
13601     static char def[MSG_SIZ];
13602     char *p;
13603
13604     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13605         p = def;
13606         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13607         *p++ = '-';
13608         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13609         *p++ = '.';
13610         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13611     } else {
13612         def[0] = NULLCHAR;
13613     }
13614     return def;
13615 }
13616
13617 /* Save the current game to the given file */
13618 int
13619 SaveGameToFile (char *filename, int append)
13620 {
13621     FILE *f;
13622     char buf[MSG_SIZ];
13623     int result, i, t,tot=0;
13624
13625     if (strcmp(filename, "-") == 0) {
13626         return SaveGame(stdout, 0, NULL);
13627     } else {
13628         for(i=0; i<10; i++) { // upto 10 tries
13629              f = fopen(filename, append ? "a" : "w");
13630              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13631              if(f || errno != 13) break;
13632              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13633              tot += t;
13634         }
13635         if (f == NULL) {
13636             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13637             DisplayError(buf, errno);
13638             return FALSE;
13639         } else {
13640             safeStrCpy(buf, lastMsg, MSG_SIZ);
13641             DisplayMessage(_("Waiting for access to save file"), "");
13642             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13643             DisplayMessage(_("Saving game"), "");
13644             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13645             result = SaveGame(f, 0, NULL);
13646             DisplayMessage(buf, "");
13647             return result;
13648         }
13649     }
13650 }
13651
13652 char *
13653 SavePart (char *str)
13654 {
13655     static char buf[MSG_SIZ];
13656     char *p;
13657
13658     p = strchr(str, ' ');
13659     if (p == NULL) return str;
13660     strncpy(buf, str, p - str);
13661     buf[p - str] = NULLCHAR;
13662     return buf;
13663 }
13664
13665 #define PGN_MAX_LINE 75
13666
13667 #define PGN_SIDE_WHITE  0
13668 #define PGN_SIDE_BLACK  1
13669
13670 static int
13671 FindFirstMoveOutOfBook (int side)
13672 {
13673     int result = -1;
13674
13675     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13676         int index = backwardMostMove;
13677         int has_book_hit = 0;
13678
13679         if( (index % 2) != side ) {
13680             index++;
13681         }
13682
13683         while( index < forwardMostMove ) {
13684             /* Check to see if engine is in book */
13685             int depth = pvInfoList[index].depth;
13686             int score = pvInfoList[index].score;
13687             int in_book = 0;
13688
13689             if( depth <= 2 ) {
13690                 in_book = 1;
13691             }
13692             else if( score == 0 && depth == 63 ) {
13693                 in_book = 1; /* Zappa */
13694             }
13695             else if( score == 2 && depth == 99 ) {
13696                 in_book = 1; /* Abrok */
13697             }
13698
13699             has_book_hit += in_book;
13700
13701             if( ! in_book ) {
13702                 result = index;
13703
13704                 break;
13705             }
13706
13707             index += 2;
13708         }
13709     }
13710
13711     return result;
13712 }
13713
13714 void
13715 GetOutOfBookInfo (char * buf)
13716 {
13717     int oob[2];
13718     int i;
13719     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13720
13721     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13722     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13723
13724     *buf = '\0';
13725
13726     if( oob[0] >= 0 || oob[1] >= 0 ) {
13727         for( i=0; i<2; i++ ) {
13728             int idx = oob[i];
13729
13730             if( idx >= 0 ) {
13731                 if( i > 0 && oob[0] >= 0 ) {
13732                     strcat( buf, "   " );
13733                 }
13734
13735                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13736                 sprintf( buf+strlen(buf), "%s%.2f",
13737                     pvInfoList[idx].score >= 0 ? "+" : "",
13738                     pvInfoList[idx].score / 100.0 );
13739             }
13740         }
13741     }
13742 }
13743
13744 /* Save game in PGN style */
13745 static void
13746 SaveGamePGN2 (FILE *f)
13747 {
13748     int i, offset, linelen, newblock;
13749 //    char *movetext;
13750     char numtext[32];
13751     int movelen, numlen, blank;
13752     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13753
13754     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13755
13756     PrintPGNTags(f, &gameInfo);
13757
13758     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13759
13760     if (backwardMostMove > 0 || startedFromSetupPosition) {
13761         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13762         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13763         fprintf(f, "\n{--------------\n");
13764         PrintPosition(f, backwardMostMove);
13765         fprintf(f, "--------------}\n");
13766         free(fen);
13767     }
13768     else {
13769         /* [AS] Out of book annotation */
13770         if( appData.saveOutOfBookInfo ) {
13771             char buf[64];
13772
13773             GetOutOfBookInfo( buf );
13774
13775             if( buf[0] != '\0' ) {
13776                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13777             }
13778         }
13779
13780         fprintf(f, "\n");
13781     }
13782
13783     i = backwardMostMove;
13784     linelen = 0;
13785     newblock = TRUE;
13786
13787     while (i < forwardMostMove) {
13788         /* Print comments preceding this move */
13789         if (commentList[i] != NULL) {
13790             if (linelen > 0) fprintf(f, "\n");
13791             fprintf(f, "%s", commentList[i]);
13792             linelen = 0;
13793             newblock = TRUE;
13794         }
13795
13796         /* Format move number */
13797         if ((i % 2) == 0)
13798           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13799         else
13800           if (newblock)
13801             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13802           else
13803             numtext[0] = NULLCHAR;
13804
13805         numlen = strlen(numtext);
13806         newblock = FALSE;
13807
13808         /* Print move number */
13809         blank = linelen > 0 && numlen > 0;
13810         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13811             fprintf(f, "\n");
13812             linelen = 0;
13813             blank = 0;
13814         }
13815         if (blank) {
13816             fprintf(f, " ");
13817             linelen++;
13818         }
13819         fprintf(f, "%s", numtext);
13820         linelen += numlen;
13821
13822         /* Get move */
13823         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13824         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13825
13826         /* Print move */
13827         blank = linelen > 0 && movelen > 0;
13828         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13829             fprintf(f, "\n");
13830             linelen = 0;
13831             blank = 0;
13832         }
13833         if (blank) {
13834             fprintf(f, " ");
13835             linelen++;
13836         }
13837         fprintf(f, "%s", move_buffer);
13838         linelen += movelen;
13839
13840         /* [AS] Add PV info if present */
13841         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13842             /* [HGM] add time */
13843             char buf[MSG_SIZ]; int seconds;
13844
13845             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13846
13847             if( seconds <= 0)
13848               buf[0] = 0;
13849             else
13850               if( seconds < 30 )
13851                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13852               else
13853                 {
13854                   seconds = (seconds + 4)/10; // round to full seconds
13855                   if( seconds < 60 )
13856                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13857                   else
13858                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13859                 }
13860
13861             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13862                       pvInfoList[i].score >= 0 ? "+" : "",
13863                       pvInfoList[i].score / 100.0,
13864                       pvInfoList[i].depth,
13865                       buf );
13866
13867             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13868
13869             /* Print score/depth */
13870             blank = linelen > 0 && movelen > 0;
13871             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13872                 fprintf(f, "\n");
13873                 linelen = 0;
13874                 blank = 0;
13875             }
13876             if (blank) {
13877                 fprintf(f, " ");
13878                 linelen++;
13879             }
13880             fprintf(f, "%s", move_buffer);
13881             linelen += movelen;
13882         }
13883
13884         i++;
13885     }
13886
13887     /* Start a new line */
13888     if (linelen > 0) fprintf(f, "\n");
13889
13890     /* Print comments after last move */
13891     if (commentList[i] != NULL) {
13892         fprintf(f, "%s\n", commentList[i]);
13893     }
13894
13895     /* Print result */
13896     if (gameInfo.resultDetails != NULL &&
13897         gameInfo.resultDetails[0] != NULLCHAR) {
13898         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13899         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13900            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13901             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13902         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13903     } else {
13904         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13905     }
13906 }
13907
13908 /* Save game in PGN style and close the file */
13909 int
13910 SaveGamePGN (FILE *f)
13911 {
13912     SaveGamePGN2(f);
13913     fclose(f);
13914     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13915     return TRUE;
13916 }
13917
13918 /* Save game in old style and close the file */
13919 int
13920 SaveGameOldStyle (FILE *f)
13921 {
13922     int i, offset;
13923     time_t tm;
13924
13925     tm = time((time_t *) NULL);
13926
13927     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13928     PrintOpponents(f);
13929
13930     if (backwardMostMove > 0 || startedFromSetupPosition) {
13931         fprintf(f, "\n[--------------\n");
13932         PrintPosition(f, backwardMostMove);
13933         fprintf(f, "--------------]\n");
13934     } else {
13935         fprintf(f, "\n");
13936     }
13937
13938     i = backwardMostMove;
13939     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13940
13941     while (i < forwardMostMove) {
13942         if (commentList[i] != NULL) {
13943             fprintf(f, "[%s]\n", commentList[i]);
13944         }
13945
13946         if ((i % 2) == 1) {
13947             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13948             i++;
13949         } else {
13950             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13951             i++;
13952             if (commentList[i] != NULL) {
13953                 fprintf(f, "\n");
13954                 continue;
13955             }
13956             if (i >= forwardMostMove) {
13957                 fprintf(f, "\n");
13958                 break;
13959             }
13960             fprintf(f, "%s\n", parseList[i]);
13961             i++;
13962         }
13963     }
13964
13965     if (commentList[i] != NULL) {
13966         fprintf(f, "[%s]\n", commentList[i]);
13967     }
13968
13969     /* This isn't really the old style, but it's close enough */
13970     if (gameInfo.resultDetails != NULL &&
13971         gameInfo.resultDetails[0] != NULLCHAR) {
13972         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13973                 gameInfo.resultDetails);
13974     } else {
13975         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13976     }
13977
13978     fclose(f);
13979     return TRUE;
13980 }
13981
13982 /* Save the current game to open file f and close the file */
13983 int
13984 SaveGame (FILE *f, int dummy, char *dummy2)
13985 {
13986     if (gameMode == EditPosition) EditPositionDone(TRUE);
13987     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13988     if (appData.oldSaveStyle)
13989       return SaveGameOldStyle(f);
13990     else
13991       return SaveGamePGN(f);
13992 }
13993
13994 /* Save the current position to the given file */
13995 int
13996 SavePositionToFile (char *filename)
13997 {
13998     FILE *f;
13999     char buf[MSG_SIZ];
14000
14001     if (strcmp(filename, "-") == 0) {
14002         return SavePosition(stdout, 0, NULL);
14003     } else {
14004         f = fopen(filename, "a");
14005         if (f == NULL) {
14006             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14007             DisplayError(buf, errno);
14008             return FALSE;
14009         } else {
14010             safeStrCpy(buf, lastMsg, MSG_SIZ);
14011             DisplayMessage(_("Waiting for access to save file"), "");
14012             flock(fileno(f), LOCK_EX); // [HGM] lock
14013             DisplayMessage(_("Saving position"), "");
14014             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14015             SavePosition(f, 0, NULL);
14016             DisplayMessage(buf, "");
14017             return TRUE;
14018         }
14019     }
14020 }
14021
14022 /* Save the current position to the given open file and close the file */
14023 int
14024 SavePosition (FILE *f, int dummy, char *dummy2)
14025 {
14026     time_t tm;
14027     char *fen;
14028
14029     if (gameMode == EditPosition) EditPositionDone(TRUE);
14030     if (appData.oldSaveStyle) {
14031         tm = time((time_t *) NULL);
14032
14033         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14034         PrintOpponents(f);
14035         fprintf(f, "[--------------\n");
14036         PrintPosition(f, currentMove);
14037         fprintf(f, "--------------]\n");
14038     } else {
14039         fen = PositionToFEN(currentMove, NULL, 1);
14040         fprintf(f, "%s\n", fen);
14041         free(fen);
14042     }
14043     fclose(f);
14044     return TRUE;
14045 }
14046
14047 void
14048 ReloadCmailMsgEvent (int unregister)
14049 {
14050 #if !WIN32
14051     static char *inFilename = NULL;
14052     static char *outFilename;
14053     int i;
14054     struct stat inbuf, outbuf;
14055     int status;
14056
14057     /* Any registered moves are unregistered if unregister is set, */
14058     /* i.e. invoked by the signal handler */
14059     if (unregister) {
14060         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14061             cmailMoveRegistered[i] = FALSE;
14062             if (cmailCommentList[i] != NULL) {
14063                 free(cmailCommentList[i]);
14064                 cmailCommentList[i] = NULL;
14065             }
14066         }
14067         nCmailMovesRegistered = 0;
14068     }
14069
14070     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14071         cmailResult[i] = CMAIL_NOT_RESULT;
14072     }
14073     nCmailResults = 0;
14074
14075     if (inFilename == NULL) {
14076         /* Because the filenames are static they only get malloced once  */
14077         /* and they never get freed                                      */
14078         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14079         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14080
14081         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14082         sprintf(outFilename, "%s.out", appData.cmailGameName);
14083     }
14084
14085     status = stat(outFilename, &outbuf);
14086     if (status < 0) {
14087         cmailMailedMove = FALSE;
14088     } else {
14089         status = stat(inFilename, &inbuf);
14090         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14091     }
14092
14093     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14094        counts the games, notes how each one terminated, etc.
14095
14096        It would be nice to remove this kludge and instead gather all
14097        the information while building the game list.  (And to keep it
14098        in the game list nodes instead of having a bunch of fixed-size
14099        parallel arrays.)  Note this will require getting each game's
14100        termination from the PGN tags, as the game list builder does
14101        not process the game moves.  --mann
14102        */
14103     cmailMsgLoaded = TRUE;
14104     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14105
14106     /* Load first game in the file or popup game menu */
14107     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14108
14109 #endif /* !WIN32 */
14110     return;
14111 }
14112
14113 int
14114 RegisterMove ()
14115 {
14116     FILE *f;
14117     char string[MSG_SIZ];
14118
14119     if (   cmailMailedMove
14120         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14121         return TRUE;            /* Allow free viewing  */
14122     }
14123
14124     /* Unregister move to ensure that we don't leave RegisterMove        */
14125     /* with the move registered when the conditions for registering no   */
14126     /* longer hold                                                       */
14127     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14128         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14129         nCmailMovesRegistered --;
14130
14131         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14132           {
14133               free(cmailCommentList[lastLoadGameNumber - 1]);
14134               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14135           }
14136     }
14137
14138     if (cmailOldMove == -1) {
14139         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14140         return FALSE;
14141     }
14142
14143     if (currentMove > cmailOldMove + 1) {
14144         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14145         return FALSE;
14146     }
14147
14148     if (currentMove < cmailOldMove) {
14149         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14150         return FALSE;
14151     }
14152
14153     if (forwardMostMove > currentMove) {
14154         /* Silently truncate extra moves */
14155         TruncateGame();
14156     }
14157
14158     if (   (currentMove == cmailOldMove + 1)
14159         || (   (currentMove == cmailOldMove)
14160             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14161                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14162         if (gameInfo.result != GameUnfinished) {
14163             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14164         }
14165
14166         if (commentList[currentMove] != NULL) {
14167             cmailCommentList[lastLoadGameNumber - 1]
14168               = StrSave(commentList[currentMove]);
14169         }
14170         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14171
14172         if (appData.debugMode)
14173           fprintf(debugFP, "Saving %s for game %d\n",
14174                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14175
14176         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14177
14178         f = fopen(string, "w");
14179         if (appData.oldSaveStyle) {
14180             SaveGameOldStyle(f); /* also closes the file */
14181
14182             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14183             f = fopen(string, "w");
14184             SavePosition(f, 0, NULL); /* also closes the file */
14185         } else {
14186             fprintf(f, "{--------------\n");
14187             PrintPosition(f, currentMove);
14188             fprintf(f, "--------------}\n\n");
14189
14190             SaveGame(f, 0, NULL); /* also closes the file*/
14191         }
14192
14193         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14194         nCmailMovesRegistered ++;
14195     } else if (nCmailGames == 1) {
14196         DisplayError(_("You have not made a move yet"), 0);
14197         return FALSE;
14198     }
14199
14200     return TRUE;
14201 }
14202
14203 void
14204 MailMoveEvent ()
14205 {
14206 #if !WIN32
14207     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14208     FILE *commandOutput;
14209     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14210     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14211     int nBuffers;
14212     int i;
14213     int archived;
14214     char *arcDir;
14215
14216     if (! cmailMsgLoaded) {
14217         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14218         return;
14219     }
14220
14221     if (nCmailGames == nCmailResults) {
14222         DisplayError(_("No unfinished games"), 0);
14223         return;
14224     }
14225
14226 #if CMAIL_PROHIBIT_REMAIL
14227     if (cmailMailedMove) {
14228       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);
14229         DisplayError(msg, 0);
14230         return;
14231     }
14232 #endif
14233
14234     if (! (cmailMailedMove || RegisterMove())) return;
14235
14236     if (   cmailMailedMove
14237         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14238       snprintf(string, MSG_SIZ, partCommandString,
14239                appData.debugMode ? " -v" : "", appData.cmailGameName);
14240         commandOutput = popen(string, "r");
14241
14242         if (commandOutput == NULL) {
14243             DisplayError(_("Failed to invoke cmail"), 0);
14244         } else {
14245             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14246                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14247             }
14248             if (nBuffers > 1) {
14249                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14250                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14251                 nBytes = MSG_SIZ - 1;
14252             } else {
14253                 (void) memcpy(msg, buffer, nBytes);
14254             }
14255             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14256
14257             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14258                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14259
14260                 archived = TRUE;
14261                 for (i = 0; i < nCmailGames; i ++) {
14262                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14263                         archived = FALSE;
14264                     }
14265                 }
14266                 if (   archived
14267                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14268                         != NULL)) {
14269                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14270                            arcDir,
14271                            appData.cmailGameName,
14272                            gameInfo.date);
14273                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14274                     cmailMsgLoaded = FALSE;
14275                 }
14276             }
14277
14278             DisplayInformation(msg);
14279             pclose(commandOutput);
14280         }
14281     } else {
14282         if ((*cmailMsg) != '\0') {
14283             DisplayInformation(cmailMsg);
14284         }
14285     }
14286
14287     return;
14288 #endif /* !WIN32 */
14289 }
14290
14291 char *
14292 CmailMsg ()
14293 {
14294 #if WIN32
14295     return NULL;
14296 #else
14297     int  prependComma = 0;
14298     char number[5];
14299     char string[MSG_SIZ];       /* Space for game-list */
14300     int  i;
14301
14302     if (!cmailMsgLoaded) return "";
14303
14304     if (cmailMailedMove) {
14305       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14306     } else {
14307         /* Create a list of games left */
14308       snprintf(string, MSG_SIZ, "[");
14309         for (i = 0; i < nCmailGames; i ++) {
14310             if (! (   cmailMoveRegistered[i]
14311                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14312                 if (prependComma) {
14313                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14314                 } else {
14315                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14316                     prependComma = 1;
14317                 }
14318
14319                 strcat(string, number);
14320             }
14321         }
14322         strcat(string, "]");
14323
14324         if (nCmailMovesRegistered + nCmailResults == 0) {
14325             switch (nCmailGames) {
14326               case 1:
14327                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14328                 break;
14329
14330               case 2:
14331                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14332                 break;
14333
14334               default:
14335                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14336                          nCmailGames);
14337                 break;
14338             }
14339         } else {
14340             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14341               case 1:
14342                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14343                          string);
14344                 break;
14345
14346               case 0:
14347                 if (nCmailResults == nCmailGames) {
14348                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14349                 } else {
14350                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14351                 }
14352                 break;
14353
14354               default:
14355                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14356                          string);
14357             }
14358         }
14359     }
14360     return cmailMsg;
14361 #endif /* WIN32 */
14362 }
14363
14364 void
14365 ResetGameEvent ()
14366 {
14367     if (gameMode == Training)
14368       SetTrainingModeOff();
14369
14370     Reset(TRUE, TRUE);
14371     cmailMsgLoaded = FALSE;
14372     if (appData.icsActive) {
14373       SendToICS(ics_prefix);
14374       SendToICS("refresh\n");
14375     }
14376 }
14377
14378 void
14379 ExitEvent (int status)
14380 {
14381     exiting++;
14382     if (exiting > 2) {
14383       /* Give up on clean exit */
14384       exit(status);
14385     }
14386     if (exiting > 1) {
14387       /* Keep trying for clean exit */
14388       return;
14389     }
14390
14391     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14392     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14393
14394     if (telnetISR != NULL) {
14395       RemoveInputSource(telnetISR);
14396     }
14397     if (icsPR != NoProc) {
14398       DestroyChildProcess(icsPR, TRUE);
14399     }
14400
14401     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14402     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14403
14404     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14405     /* make sure this other one finishes before killing it!                  */
14406     if(endingGame) { int count = 0;
14407         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14408         while(endingGame && count++ < 10) DoSleep(1);
14409         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14410     }
14411
14412     /* Kill off chess programs */
14413     if (first.pr != NoProc) {
14414         ExitAnalyzeMode();
14415
14416         DoSleep( appData.delayBeforeQuit );
14417         SendToProgram("quit\n", &first);
14418         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14419     }
14420     if (second.pr != NoProc) {
14421         DoSleep( appData.delayBeforeQuit );
14422         SendToProgram("quit\n", &second);
14423         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14424     }
14425     if (first.isr != NULL) {
14426         RemoveInputSource(first.isr);
14427     }
14428     if (second.isr != NULL) {
14429         RemoveInputSource(second.isr);
14430     }
14431
14432     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14433     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14434
14435     ShutDownFrontEnd();
14436     exit(status);
14437 }
14438
14439 void
14440 PauseEngine (ChessProgramState *cps)
14441 {
14442     SendToProgram("pause\n", cps);
14443     cps->pause = 2;
14444 }
14445
14446 void
14447 UnPauseEngine (ChessProgramState *cps)
14448 {
14449     SendToProgram("resume\n", cps);
14450     cps->pause = 1;
14451 }
14452
14453 void
14454 PauseEvent ()
14455 {
14456     if (appData.debugMode)
14457         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14458     if (pausing) {
14459         pausing = FALSE;
14460         ModeHighlight();
14461         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14462             StartClocks();
14463             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14464                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14465                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14466             }
14467             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14468             HandleMachineMove(stashedInputMove, stalledEngine);
14469             stalledEngine = NULL;
14470             return;
14471         }
14472         if (gameMode == MachinePlaysWhite ||
14473             gameMode == TwoMachinesPlay   ||
14474             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14475             if(first.pause)  UnPauseEngine(&first);
14476             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14477             if(second.pause) UnPauseEngine(&second);
14478             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14479             StartClocks();
14480         } else {
14481             DisplayBothClocks();
14482         }
14483         if (gameMode == PlayFromGameFile) {
14484             if (appData.timeDelay >= 0)
14485                 AutoPlayGameLoop();
14486         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14487             Reset(FALSE, TRUE);
14488             SendToICS(ics_prefix);
14489             SendToICS("refresh\n");
14490         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14491             ForwardInner(forwardMostMove);
14492         }
14493         pauseExamInvalid = FALSE;
14494     } else {
14495         switch (gameMode) {
14496           default:
14497             return;
14498           case IcsExamining:
14499             pauseExamForwardMostMove = forwardMostMove;
14500             pauseExamInvalid = FALSE;
14501             /* fall through */
14502           case IcsObserving:
14503           case IcsPlayingWhite:
14504           case IcsPlayingBlack:
14505             pausing = TRUE;
14506             ModeHighlight();
14507             return;
14508           case PlayFromGameFile:
14509             (void) StopLoadGameTimer();
14510             pausing = TRUE;
14511             ModeHighlight();
14512             break;
14513           case BeginningOfGame:
14514             if (appData.icsActive) return;
14515             /* else fall through */
14516           case MachinePlaysWhite:
14517           case MachinePlaysBlack:
14518           case TwoMachinesPlay:
14519             if (forwardMostMove == 0)
14520               return;           /* don't pause if no one has moved */
14521             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14522                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14523                 if(onMove->pause) {           // thinking engine can be paused
14524                     PauseEngine(onMove);      // do it
14525                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14526                         PauseEngine(onMove->other);
14527                     else
14528                         SendToProgram("easy\n", onMove->other);
14529                     StopClocks();
14530                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14531             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14532                 if(first.pause) {
14533                     PauseEngine(&first);
14534                     StopClocks();
14535                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14536             } else { // human on move, pause pondering by either method
14537                 if(first.pause)
14538                     PauseEngine(&first);
14539                 else if(appData.ponderNextMove)
14540                     SendToProgram("easy\n", &first);
14541                 StopClocks();
14542             }
14543             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14544           case AnalyzeMode:
14545             pausing = TRUE;
14546             ModeHighlight();
14547             break;
14548         }
14549     }
14550 }
14551
14552 void
14553 EditCommentEvent ()
14554 {
14555     char title[MSG_SIZ];
14556
14557     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14558       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14559     } else {
14560       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14561                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14562                parseList[currentMove - 1]);
14563     }
14564
14565     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14566 }
14567
14568
14569 void
14570 EditTagsEvent ()
14571 {
14572     char *tags = PGNTags(&gameInfo);
14573     bookUp = FALSE;
14574     EditTagsPopUp(tags, NULL);
14575     free(tags);
14576 }
14577
14578 void
14579 ToggleSecond ()
14580 {
14581   if(second.analyzing) {
14582     SendToProgram("exit\n", &second);
14583     second.analyzing = FALSE;
14584   } else {
14585     if (second.pr == NoProc) StartChessProgram(&second);
14586     InitChessProgram(&second, FALSE);
14587     FeedMovesToProgram(&second, currentMove);
14588
14589     SendToProgram("analyze\n", &second);
14590     second.analyzing = TRUE;
14591   }
14592 }
14593
14594 /* Toggle ShowThinking */
14595 void
14596 ToggleShowThinking()
14597 {
14598   appData.showThinking = !appData.showThinking;
14599   ShowThinkingEvent();
14600 }
14601
14602 int
14603 AnalyzeModeEvent ()
14604 {
14605     char buf[MSG_SIZ];
14606
14607     if (!first.analysisSupport) {
14608       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14609       DisplayError(buf, 0);
14610       return 0;
14611     }
14612     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14613     if (appData.icsActive) {
14614         if (gameMode != IcsObserving) {
14615           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14616             DisplayError(buf, 0);
14617             /* secure check */
14618             if (appData.icsEngineAnalyze) {
14619                 if (appData.debugMode)
14620                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14621                 ExitAnalyzeMode();
14622                 ModeHighlight();
14623             }
14624             return 0;
14625         }
14626         /* if enable, user wants to disable icsEngineAnalyze */
14627         if (appData.icsEngineAnalyze) {
14628                 ExitAnalyzeMode();
14629                 ModeHighlight();
14630                 return 0;
14631         }
14632         appData.icsEngineAnalyze = TRUE;
14633         if (appData.debugMode)
14634             fprintf(debugFP, "ICS engine analyze starting... \n");
14635     }
14636
14637     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14638     if (appData.noChessProgram || gameMode == AnalyzeMode)
14639       return 0;
14640
14641     if (gameMode != AnalyzeFile) {
14642         if (!appData.icsEngineAnalyze) {
14643                EditGameEvent();
14644                if (gameMode != EditGame) return 0;
14645         }
14646         if (!appData.showThinking) ToggleShowThinking();
14647         ResurrectChessProgram();
14648         SendToProgram("analyze\n", &first);
14649         first.analyzing = TRUE;
14650         /*first.maybeThinking = TRUE;*/
14651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14652         EngineOutputPopUp();
14653     }
14654     if (!appData.icsEngineAnalyze) {
14655         gameMode = AnalyzeMode;
14656         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14657     }
14658     pausing = FALSE;
14659     ModeHighlight();
14660     SetGameInfo();
14661
14662     StartAnalysisClock();
14663     GetTimeMark(&lastNodeCountTime);
14664     lastNodeCount = 0;
14665     return 1;
14666 }
14667
14668 void
14669 AnalyzeFileEvent ()
14670 {
14671     if (appData.noChessProgram || gameMode == AnalyzeFile)
14672       return;
14673
14674     if (!first.analysisSupport) {
14675       char buf[MSG_SIZ];
14676       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14677       DisplayError(buf, 0);
14678       return;
14679     }
14680
14681     if (gameMode != AnalyzeMode) {
14682         keepInfo = 1; // mere annotating should not alter PGN tags
14683         EditGameEvent();
14684         keepInfo = 0;
14685         if (gameMode != EditGame) return;
14686         if (!appData.showThinking) ToggleShowThinking();
14687         ResurrectChessProgram();
14688         SendToProgram("analyze\n", &first);
14689         first.analyzing = TRUE;
14690         /*first.maybeThinking = TRUE;*/
14691         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14692         EngineOutputPopUp();
14693     }
14694     gameMode = AnalyzeFile;
14695     pausing = FALSE;
14696     ModeHighlight();
14697
14698     StartAnalysisClock();
14699     GetTimeMark(&lastNodeCountTime);
14700     lastNodeCount = 0;
14701     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14702     AnalysisPeriodicEvent(1);
14703 }
14704
14705 void
14706 MachineWhiteEvent ()
14707 {
14708     char buf[MSG_SIZ];
14709     char *bookHit = NULL;
14710
14711     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14712       return;
14713
14714
14715     if (gameMode == PlayFromGameFile ||
14716         gameMode == TwoMachinesPlay  ||
14717         gameMode == Training         ||
14718         gameMode == AnalyzeMode      ||
14719         gameMode == EndOfGame)
14720         EditGameEvent();
14721
14722     if (gameMode == EditPosition)
14723         EditPositionDone(TRUE);
14724
14725     if (!WhiteOnMove(currentMove)) {
14726         DisplayError(_("It is not White's turn"), 0);
14727         return;
14728     }
14729
14730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14731       ExitAnalyzeMode();
14732
14733     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14734         gameMode == AnalyzeFile)
14735         TruncateGame();
14736
14737     ResurrectChessProgram();    /* in case it isn't running */
14738     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14739         gameMode = MachinePlaysWhite;
14740         ResetClocks();
14741     } else
14742     gameMode = MachinePlaysWhite;
14743     pausing = FALSE;
14744     ModeHighlight();
14745     SetGameInfo();
14746     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14747     DisplayTitle(buf);
14748     if (first.sendName) {
14749       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14750       SendToProgram(buf, &first);
14751     }
14752     if (first.sendTime) {
14753       if (first.useColors) {
14754         SendToProgram("black\n", &first); /*gnu kludge*/
14755       }
14756       SendTimeRemaining(&first, TRUE);
14757     }
14758     if (first.useColors) {
14759       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14760     }
14761     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14762     SetMachineThinkingEnables();
14763     first.maybeThinking = TRUE;
14764     StartClocks();
14765     firstMove = FALSE;
14766
14767     if (appData.autoFlipView && !flipView) {
14768       flipView = !flipView;
14769       DrawPosition(FALSE, NULL);
14770       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14771     }
14772
14773     if(bookHit) { // [HGM] book: simulate book reply
14774         static char bookMove[MSG_SIZ]; // a bit generous?
14775
14776         programStats.nodes = programStats.depth = programStats.time =
14777         programStats.score = programStats.got_only_move = 0;
14778         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14779
14780         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14781         strcat(bookMove, bookHit);
14782         HandleMachineMove(bookMove, &first);
14783     }
14784 }
14785
14786 void
14787 MachineBlackEvent ()
14788 {
14789   char buf[MSG_SIZ];
14790   char *bookHit = NULL;
14791
14792     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14793         return;
14794
14795
14796     if (gameMode == PlayFromGameFile ||
14797         gameMode == TwoMachinesPlay  ||
14798         gameMode == Training         ||
14799         gameMode == AnalyzeMode      ||
14800         gameMode == EndOfGame)
14801         EditGameEvent();
14802
14803     if (gameMode == EditPosition)
14804         EditPositionDone(TRUE);
14805
14806     if (WhiteOnMove(currentMove)) {
14807         DisplayError(_("It is not Black's turn"), 0);
14808         return;
14809     }
14810
14811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14812       ExitAnalyzeMode();
14813
14814     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14815         gameMode == AnalyzeFile)
14816         TruncateGame();
14817
14818     ResurrectChessProgram();    /* in case it isn't running */
14819     gameMode = MachinePlaysBlack;
14820     pausing = FALSE;
14821     ModeHighlight();
14822     SetGameInfo();
14823     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14824     DisplayTitle(buf);
14825     if (first.sendName) {
14826       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14827       SendToProgram(buf, &first);
14828     }
14829     if (first.sendTime) {
14830       if (first.useColors) {
14831         SendToProgram("white\n", &first); /*gnu kludge*/
14832       }
14833       SendTimeRemaining(&first, FALSE);
14834     }
14835     if (first.useColors) {
14836       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14837     }
14838     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14839     SetMachineThinkingEnables();
14840     first.maybeThinking = TRUE;
14841     StartClocks();
14842
14843     if (appData.autoFlipView && flipView) {
14844       flipView = !flipView;
14845       DrawPosition(FALSE, NULL);
14846       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14847     }
14848     if(bookHit) { // [HGM] book: simulate book reply
14849         static char bookMove[MSG_SIZ]; // a bit generous?
14850
14851         programStats.nodes = programStats.depth = programStats.time =
14852         programStats.score = programStats.got_only_move = 0;
14853         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14854
14855         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14856         strcat(bookMove, bookHit);
14857         HandleMachineMove(bookMove, &first);
14858     }
14859 }
14860
14861
14862 void
14863 DisplayTwoMachinesTitle ()
14864 {
14865     char buf[MSG_SIZ];
14866     if (appData.matchGames > 0) {
14867         if(appData.tourneyFile[0]) {
14868           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14869                    gameInfo.white, _("vs."), gameInfo.black,
14870                    nextGame+1, appData.matchGames+1,
14871                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14872         } else
14873         if (first.twoMachinesColor[0] == 'w') {
14874           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14875                    gameInfo.white, _("vs."),  gameInfo.black,
14876                    first.matchWins, second.matchWins,
14877                    matchGame - 1 - (first.matchWins + second.matchWins));
14878         } else {
14879           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14880                    gameInfo.white, _("vs."), gameInfo.black,
14881                    second.matchWins, first.matchWins,
14882                    matchGame - 1 - (first.matchWins + second.matchWins));
14883         }
14884     } else {
14885       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14886     }
14887     DisplayTitle(buf);
14888 }
14889
14890 void
14891 SettingsMenuIfReady ()
14892 {
14893   if (second.lastPing != second.lastPong) {
14894     DisplayMessage("", _("Waiting for second chess program"));
14895     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14896     return;
14897   }
14898   ThawUI();
14899   DisplayMessage("", "");
14900   SettingsPopUp(&second);
14901 }
14902
14903 int
14904 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14905 {
14906     char buf[MSG_SIZ];
14907     if (cps->pr == NoProc) {
14908         StartChessProgram(cps);
14909         if (cps->protocolVersion == 1) {
14910           retry();
14911           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14912         } else {
14913           /* kludge: allow timeout for initial "feature" command */
14914           if(retry != TwoMachinesEventIfReady) FreezeUI();
14915           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14916           DisplayMessage("", buf);
14917           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14918         }
14919         return 1;
14920     }
14921     return 0;
14922 }
14923
14924 void
14925 TwoMachinesEvent P((void))
14926 {
14927     int i;
14928     char buf[MSG_SIZ];
14929     ChessProgramState *onmove;
14930     char *bookHit = NULL;
14931     static int stalling = 0;
14932     TimeMark now;
14933     long wait;
14934
14935     if (appData.noChessProgram) return;
14936
14937     switch (gameMode) {
14938       case TwoMachinesPlay:
14939         return;
14940       case MachinePlaysWhite:
14941       case MachinePlaysBlack:
14942         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14943             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14944             return;
14945         }
14946         /* fall through */
14947       case BeginningOfGame:
14948       case PlayFromGameFile:
14949       case EndOfGame:
14950         EditGameEvent();
14951         if (gameMode != EditGame) return;
14952         break;
14953       case EditPosition:
14954         EditPositionDone(TRUE);
14955         break;
14956       case AnalyzeMode:
14957       case AnalyzeFile:
14958         ExitAnalyzeMode();
14959         break;
14960       case EditGame:
14961       default:
14962         break;
14963     }
14964
14965 //    forwardMostMove = currentMove;
14966     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14967     startingEngine = TRUE;
14968
14969     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14970
14971     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14972     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14973       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14974       return;
14975     }
14976     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14977
14978     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14979                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14980         startingEngine = matchMode = FALSE;
14981         DisplayError("second engine does not play this", 0);
14982         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14983         EditGameEvent(); // switch back to EditGame mode
14984         return;
14985     }
14986
14987     if(!stalling) {
14988       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14989       SendToProgram("force\n", &second);
14990       stalling = 1;
14991       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14992       return;
14993     }
14994     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14995     if(appData.matchPause>10000 || appData.matchPause<10)
14996                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14997     wait = SubtractTimeMarks(&now, &pauseStart);
14998     if(wait < appData.matchPause) {
14999         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15000         return;
15001     }
15002     // we are now committed to starting the game
15003     stalling = 0;
15004     DisplayMessage("", "");
15005     if (startedFromSetupPosition) {
15006         SendBoard(&second, backwardMostMove);
15007     if (appData.debugMode) {
15008         fprintf(debugFP, "Two Machines\n");
15009     }
15010     }
15011     for (i = backwardMostMove; i < forwardMostMove; i++) {
15012         SendMoveToProgram(i, &second);
15013     }
15014
15015     gameMode = TwoMachinesPlay;
15016     pausing = startingEngine = FALSE;
15017     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15018     SetGameInfo();
15019     DisplayTwoMachinesTitle();
15020     firstMove = TRUE;
15021     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15022         onmove = &first;
15023     } else {
15024         onmove = &second;
15025     }
15026     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15027     SendToProgram(first.computerString, &first);
15028     if (first.sendName) {
15029       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15030       SendToProgram(buf, &first);
15031     }
15032     SendToProgram(second.computerString, &second);
15033     if (second.sendName) {
15034       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15035       SendToProgram(buf, &second);
15036     }
15037
15038     ResetClocks();
15039     if (!first.sendTime || !second.sendTime) {
15040         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15041         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15042     }
15043     if (onmove->sendTime) {
15044       if (onmove->useColors) {
15045         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15046       }
15047       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15048     }
15049     if (onmove->useColors) {
15050       SendToProgram(onmove->twoMachinesColor, onmove);
15051     }
15052     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15053 //    SendToProgram("go\n", onmove);
15054     onmove->maybeThinking = TRUE;
15055     SetMachineThinkingEnables();
15056
15057     StartClocks();
15058
15059     if(bookHit) { // [HGM] book: simulate book reply
15060         static char bookMove[MSG_SIZ]; // a bit generous?
15061
15062         programStats.nodes = programStats.depth = programStats.time =
15063         programStats.score = programStats.got_only_move = 0;
15064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15065
15066         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15067         strcat(bookMove, bookHit);
15068         savedMessage = bookMove; // args for deferred call
15069         savedState = onmove;
15070         ScheduleDelayedEvent(DeferredBookMove, 1);
15071     }
15072 }
15073
15074 void
15075 TrainingEvent ()
15076 {
15077     if (gameMode == Training) {
15078       SetTrainingModeOff();
15079       gameMode = PlayFromGameFile;
15080       DisplayMessage("", _("Training mode off"));
15081     } else {
15082       gameMode = Training;
15083       animateTraining = appData.animate;
15084
15085       /* make sure we are not already at the end of the game */
15086       if (currentMove < forwardMostMove) {
15087         SetTrainingModeOn();
15088         DisplayMessage("", _("Training mode on"));
15089       } else {
15090         gameMode = PlayFromGameFile;
15091         DisplayError(_("Already at end of game"), 0);
15092       }
15093     }
15094     ModeHighlight();
15095 }
15096
15097 void
15098 IcsClientEvent ()
15099 {
15100     if (!appData.icsActive) return;
15101     switch (gameMode) {
15102       case IcsPlayingWhite:
15103       case IcsPlayingBlack:
15104       case IcsObserving:
15105       case IcsIdle:
15106       case BeginningOfGame:
15107       case IcsExamining:
15108         return;
15109
15110       case EditGame:
15111         break;
15112
15113       case EditPosition:
15114         EditPositionDone(TRUE);
15115         break;
15116
15117       case AnalyzeMode:
15118       case AnalyzeFile:
15119         ExitAnalyzeMode();
15120         break;
15121
15122       default:
15123         EditGameEvent();
15124         break;
15125     }
15126
15127     gameMode = IcsIdle;
15128     ModeHighlight();
15129     return;
15130 }
15131
15132 void
15133 EditGameEvent ()
15134 {
15135     int i;
15136
15137     switch (gameMode) {
15138       case Training:
15139         SetTrainingModeOff();
15140         break;
15141       case MachinePlaysWhite:
15142       case MachinePlaysBlack:
15143       case BeginningOfGame:
15144         SendToProgram("force\n", &first);
15145         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15146             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15147                 char buf[MSG_SIZ];
15148                 abortEngineThink = TRUE;
15149                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15150                 SendToProgram(buf, &first);
15151                 DisplayMessage("Aborting engine think", "");
15152                 FreezeUI();
15153             }
15154         }
15155         SetUserThinkingEnables();
15156         break;
15157       case PlayFromGameFile:
15158         (void) StopLoadGameTimer();
15159         if (gameFileFP != NULL) {
15160             gameFileFP = NULL;
15161         }
15162         break;
15163       case EditPosition:
15164         EditPositionDone(TRUE);
15165         break;
15166       case AnalyzeMode:
15167       case AnalyzeFile:
15168         ExitAnalyzeMode();
15169         SendToProgram("force\n", &first);
15170         break;
15171       case TwoMachinesPlay:
15172         GameEnds(EndOfFile, NULL, GE_PLAYER);
15173         ResurrectChessProgram();
15174         SetUserThinkingEnables();
15175         break;
15176       case EndOfGame:
15177         ResurrectChessProgram();
15178         break;
15179       case IcsPlayingBlack:
15180       case IcsPlayingWhite:
15181         DisplayError(_("Warning: You are still playing a game"), 0);
15182         break;
15183       case IcsObserving:
15184         DisplayError(_("Warning: You are still observing a game"), 0);
15185         break;
15186       case IcsExamining:
15187         DisplayError(_("Warning: You are still examining a game"), 0);
15188         break;
15189       case IcsIdle:
15190         break;
15191       case EditGame:
15192       default:
15193         return;
15194     }
15195
15196     pausing = FALSE;
15197     StopClocks();
15198     first.offeredDraw = second.offeredDraw = 0;
15199
15200     if (gameMode == PlayFromGameFile) {
15201         whiteTimeRemaining = timeRemaining[0][currentMove];
15202         blackTimeRemaining = timeRemaining[1][currentMove];
15203         DisplayTitle("");
15204     }
15205
15206     if (gameMode == MachinePlaysWhite ||
15207         gameMode == MachinePlaysBlack ||
15208         gameMode == TwoMachinesPlay ||
15209         gameMode == EndOfGame) {
15210         i = forwardMostMove;
15211         while (i > currentMove) {
15212             SendToProgram("undo\n", &first);
15213             i--;
15214         }
15215         if(!adjustedClock) {
15216         whiteTimeRemaining = timeRemaining[0][currentMove];
15217         blackTimeRemaining = timeRemaining[1][currentMove];
15218         DisplayBothClocks();
15219         }
15220         if (whiteFlag || blackFlag) {
15221             whiteFlag = blackFlag = 0;
15222         }
15223         DisplayTitle("");
15224     }
15225
15226     gameMode = EditGame;
15227     ModeHighlight();
15228     SetGameInfo();
15229 }
15230
15231
15232 void
15233 EditPositionEvent ()
15234 {
15235     if (gameMode == EditPosition) {
15236         EditGameEvent();
15237         return;
15238     }
15239
15240     EditGameEvent();
15241     if (gameMode != EditGame) return;
15242
15243     gameMode = EditPosition;
15244     ModeHighlight();
15245     SetGameInfo();
15246     if (currentMove > 0)
15247       CopyBoard(boards[0], boards[currentMove]);
15248
15249     blackPlaysFirst = !WhiteOnMove(currentMove);
15250     ResetClocks();
15251     currentMove = forwardMostMove = backwardMostMove = 0;
15252     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15253     DisplayMove(-1);
15254     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15255 }
15256
15257 void
15258 ExitAnalyzeMode ()
15259 {
15260     /* [DM] icsEngineAnalyze - possible call from other functions */
15261     if (appData.icsEngineAnalyze) {
15262         appData.icsEngineAnalyze = FALSE;
15263
15264         DisplayMessage("",_("Close ICS engine analyze..."));
15265     }
15266     if (first.analysisSupport && first.analyzing) {
15267       SendToBoth("exit\n");
15268       first.analyzing = second.analyzing = FALSE;
15269     }
15270     thinkOutput[0] = NULLCHAR;
15271 }
15272
15273 void
15274 EditPositionDone (Boolean fakeRights)
15275 {
15276     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15277
15278     startedFromSetupPosition = TRUE;
15279     InitChessProgram(&first, FALSE);
15280     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15281       boards[0][EP_STATUS] = EP_NONE;
15282       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15283       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15284         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15285         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15286       } else boards[0][CASTLING][2] = NoRights;
15287       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15288         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15289         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15290       } else boards[0][CASTLING][5] = NoRights;
15291       if(gameInfo.variant == VariantSChess) {
15292         int i;
15293         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15294           boards[0][VIRGIN][i] = 0;
15295           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15296           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15297         }
15298       }
15299     }
15300     SendToProgram("force\n", &first);
15301     if (blackPlaysFirst) {
15302         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15303         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15304         currentMove = forwardMostMove = backwardMostMove = 1;
15305         CopyBoard(boards[1], boards[0]);
15306     } else {
15307         currentMove = forwardMostMove = backwardMostMove = 0;
15308     }
15309     SendBoard(&first, forwardMostMove);
15310     if (appData.debugMode) {
15311         fprintf(debugFP, "EditPosDone\n");
15312     }
15313     DisplayTitle("");
15314     DisplayMessage("", "");
15315     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15316     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15317     gameMode = EditGame;
15318     ModeHighlight();
15319     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15320     ClearHighlights(); /* [AS] */
15321 }
15322
15323 /* Pause for `ms' milliseconds */
15324 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15325 void
15326 TimeDelay (long ms)
15327 {
15328     TimeMark m1, m2;
15329
15330     GetTimeMark(&m1);
15331     do {
15332         GetTimeMark(&m2);
15333     } while (SubtractTimeMarks(&m2, &m1) < ms);
15334 }
15335
15336 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15337 void
15338 SendMultiLineToICS (char *buf)
15339 {
15340     char temp[MSG_SIZ+1], *p;
15341     int len;
15342
15343     len = strlen(buf);
15344     if (len > MSG_SIZ)
15345       len = MSG_SIZ;
15346
15347     strncpy(temp, buf, len);
15348     temp[len] = 0;
15349
15350     p = temp;
15351     while (*p) {
15352         if (*p == '\n' || *p == '\r')
15353           *p = ' ';
15354         ++p;
15355     }
15356
15357     strcat(temp, "\n");
15358     SendToICS(temp);
15359     SendToPlayer(temp, strlen(temp));
15360 }
15361
15362 void
15363 SetWhiteToPlayEvent ()
15364 {
15365     if (gameMode == EditPosition) {
15366         blackPlaysFirst = FALSE;
15367         DisplayBothClocks();    /* works because currentMove is 0 */
15368     } else if (gameMode == IcsExamining) {
15369         SendToICS(ics_prefix);
15370         SendToICS("tomove white\n");
15371     }
15372 }
15373
15374 void
15375 SetBlackToPlayEvent ()
15376 {
15377     if (gameMode == EditPosition) {
15378         blackPlaysFirst = TRUE;
15379         currentMove = 1;        /* kludge */
15380         DisplayBothClocks();
15381         currentMove = 0;
15382     } else if (gameMode == IcsExamining) {
15383         SendToICS(ics_prefix);
15384         SendToICS("tomove black\n");
15385     }
15386 }
15387
15388 void
15389 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15390 {
15391     char buf[MSG_SIZ];
15392     ChessSquare piece = boards[0][y][x];
15393     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15394     static int lastVariant;
15395
15396     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15397
15398     switch (selection) {
15399       case ClearBoard:
15400         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15401         MarkTargetSquares(1);
15402         CopyBoard(currentBoard, boards[0]);
15403         CopyBoard(menuBoard, initialPosition);
15404         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15405             SendToICS(ics_prefix);
15406             SendToICS("bsetup clear\n");
15407         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15408             SendToICS(ics_prefix);
15409             SendToICS("clearboard\n");
15410         } else {
15411             int nonEmpty = 0;
15412             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15413                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15414                 for (y = 0; y < BOARD_HEIGHT; y++) {
15415                     if (gameMode == IcsExamining) {
15416                         if (boards[currentMove][y][x] != EmptySquare) {
15417                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15418                                     AAA + x, ONE + y);
15419                             SendToICS(buf);
15420                         }
15421                     } else if(boards[0][y][x] != DarkSquare) {
15422                         if(boards[0][y][x] != p) nonEmpty++;
15423                         boards[0][y][x] = p;
15424                     }
15425                 }
15426             }
15427             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15428                 int r;
15429                 for(r = 0; r < BOARD_HEIGHT; r++) {
15430                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15431                     ChessSquare p = menuBoard[r][x];
15432                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15433                   }
15434                 }
15435                 DisplayMessage("Clicking clock again restores position", "");
15436                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15437                 if(!nonEmpty) { // asked to clear an empty board
15438                     CopyBoard(boards[0], menuBoard);
15439                 } else
15440                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15441                     CopyBoard(boards[0], initialPosition);
15442                 } else
15443                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15444                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15445                     CopyBoard(boards[0], erasedBoard);
15446                 } else
15447                     CopyBoard(erasedBoard, currentBoard);
15448
15449             }
15450         }
15451         if (gameMode == EditPosition) {
15452             DrawPosition(FALSE, boards[0]);
15453         }
15454         break;
15455
15456       case WhitePlay:
15457         SetWhiteToPlayEvent();
15458         break;
15459
15460       case BlackPlay:
15461         SetBlackToPlayEvent();
15462         break;
15463
15464       case EmptySquare:
15465         if (gameMode == IcsExamining) {
15466             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15467             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15468             SendToICS(buf);
15469         } else {
15470             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15471                 if(x == BOARD_LEFT-2) {
15472                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15473                     boards[0][y][1] = 0;
15474                 } else
15475                 if(x == BOARD_RGHT+1) {
15476                     if(y >= gameInfo.holdingsSize) break;
15477                     boards[0][y][BOARD_WIDTH-2] = 0;
15478                 } else break;
15479             }
15480             boards[0][y][x] = EmptySquare;
15481             DrawPosition(FALSE, boards[0]);
15482         }
15483         break;
15484
15485       case PromotePiece:
15486         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15487            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15488             selection = (ChessSquare) (PROMOTED(piece));
15489         } else if(piece == EmptySquare) selection = WhiteSilver;
15490         else selection = (ChessSquare)((int)piece - 1);
15491         goto defaultlabel;
15492
15493       case DemotePiece:
15494         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15495            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15496             selection = (ChessSquare) (DEMOTED(piece));
15497         } else if(piece == EmptySquare) selection = BlackSilver;
15498         else selection = (ChessSquare)((int)piece + 1);
15499         goto defaultlabel;
15500
15501       case WhiteQueen:
15502       case BlackQueen:
15503         if(gameInfo.variant == VariantShatranj ||
15504            gameInfo.variant == VariantXiangqi  ||
15505            gameInfo.variant == VariantCourier  ||
15506            gameInfo.variant == VariantASEAN    ||
15507            gameInfo.variant == VariantMakruk     )
15508             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15509         goto defaultlabel;
15510
15511       case WhiteKing:
15512       case BlackKing:
15513         if(gameInfo.variant == VariantXiangqi)
15514             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15515         if(gameInfo.variant == VariantKnightmate)
15516             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15517       default:
15518         defaultlabel:
15519         if (gameMode == IcsExamining) {
15520             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15521             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15522                      PieceToChar(selection), AAA + x, ONE + y);
15523             SendToICS(buf);
15524         } else {
15525             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15526                 int n;
15527                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15528                     n = PieceToNumber(selection - BlackPawn);
15529                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15530                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15531                     boards[0][BOARD_HEIGHT-1-n][1]++;
15532                 } else
15533                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15534                     n = PieceToNumber(selection);
15535                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15536                     boards[0][n][BOARD_WIDTH-1] = selection;
15537                     boards[0][n][BOARD_WIDTH-2]++;
15538                 }
15539             } else
15540             boards[0][y][x] = selection;
15541             DrawPosition(TRUE, boards[0]);
15542             ClearHighlights();
15543             fromX = fromY = -1;
15544         }
15545         break;
15546     }
15547 }
15548
15549
15550 void
15551 DropMenuEvent (ChessSquare selection, int x, int y)
15552 {
15553     ChessMove moveType;
15554
15555     switch (gameMode) {
15556       case IcsPlayingWhite:
15557       case MachinePlaysBlack:
15558         if (!WhiteOnMove(currentMove)) {
15559             DisplayMoveError(_("It is Black's turn"));
15560             return;
15561         }
15562         moveType = WhiteDrop;
15563         break;
15564       case IcsPlayingBlack:
15565       case MachinePlaysWhite:
15566         if (WhiteOnMove(currentMove)) {
15567             DisplayMoveError(_("It is White's turn"));
15568             return;
15569         }
15570         moveType = BlackDrop;
15571         break;
15572       case EditGame:
15573         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15574         break;
15575       default:
15576         return;
15577     }
15578
15579     if (moveType == BlackDrop && selection < BlackPawn) {
15580       selection = (ChessSquare) ((int) selection
15581                                  + (int) BlackPawn - (int) WhitePawn);
15582     }
15583     if (boards[currentMove][y][x] != EmptySquare) {
15584         DisplayMoveError(_("That square is occupied"));
15585         return;
15586     }
15587
15588     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15589 }
15590
15591 void
15592 AcceptEvent ()
15593 {
15594     /* Accept a pending offer of any kind from opponent */
15595
15596     if (appData.icsActive) {
15597         SendToICS(ics_prefix);
15598         SendToICS("accept\n");
15599     } else if (cmailMsgLoaded) {
15600         if (currentMove == cmailOldMove &&
15601             commentList[cmailOldMove] != NULL &&
15602             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15603                    "Black offers a draw" : "White offers a draw")) {
15604             TruncateGame();
15605             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15606             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15607         } else {
15608             DisplayError(_("There is no pending offer on this move"), 0);
15609             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15610         }
15611     } else {
15612         /* Not used for offers from chess program */
15613     }
15614 }
15615
15616 void
15617 DeclineEvent ()
15618 {
15619     /* Decline a pending offer of any kind from opponent */
15620
15621     if (appData.icsActive) {
15622         SendToICS(ics_prefix);
15623         SendToICS("decline\n");
15624     } else if (cmailMsgLoaded) {
15625         if (currentMove == cmailOldMove &&
15626             commentList[cmailOldMove] != NULL &&
15627             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15628                    "Black offers a draw" : "White offers a draw")) {
15629 #ifdef NOTDEF
15630             AppendComment(cmailOldMove, "Draw declined", TRUE);
15631             DisplayComment(cmailOldMove - 1, "Draw declined");
15632 #endif /*NOTDEF*/
15633         } else {
15634             DisplayError(_("There is no pending offer on this move"), 0);
15635         }
15636     } else {
15637         /* Not used for offers from chess program */
15638     }
15639 }
15640
15641 void
15642 RematchEvent ()
15643 {
15644     /* Issue ICS rematch command */
15645     if (appData.icsActive) {
15646         SendToICS(ics_prefix);
15647         SendToICS("rematch\n");
15648     }
15649 }
15650
15651 void
15652 CallFlagEvent ()
15653 {
15654     /* Call your opponent's flag (claim a win on time) */
15655     if (appData.icsActive) {
15656         SendToICS(ics_prefix);
15657         SendToICS("flag\n");
15658     } else {
15659         switch (gameMode) {
15660           default:
15661             return;
15662           case MachinePlaysWhite:
15663             if (whiteFlag) {
15664                 if (blackFlag)
15665                   GameEnds(GameIsDrawn, "Both players ran out of time",
15666                            GE_PLAYER);
15667                 else
15668                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15669             } else {
15670                 DisplayError(_("Your opponent is not out of time"), 0);
15671             }
15672             break;
15673           case MachinePlaysBlack:
15674             if (blackFlag) {
15675                 if (whiteFlag)
15676                   GameEnds(GameIsDrawn, "Both players ran out of time",
15677                            GE_PLAYER);
15678                 else
15679                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15680             } else {
15681                 DisplayError(_("Your opponent is not out of time"), 0);
15682             }
15683             break;
15684         }
15685     }
15686 }
15687
15688 void
15689 ClockClick (int which)
15690 {       // [HGM] code moved to back-end from winboard.c
15691         if(which) { // black clock
15692           if (gameMode == EditPosition || gameMode == IcsExamining) {
15693             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15694             SetBlackToPlayEvent();
15695           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15696                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15697           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15698           } else if (shiftKey) {
15699             AdjustClock(which, -1);
15700           } else if (gameMode == IcsPlayingWhite ||
15701                      gameMode == MachinePlaysBlack) {
15702             CallFlagEvent();
15703           }
15704         } else { // white clock
15705           if (gameMode == EditPosition || gameMode == IcsExamining) {
15706             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15707             SetWhiteToPlayEvent();
15708           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15709                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15710           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15711           } else if (shiftKey) {
15712             AdjustClock(which, -1);
15713           } else if (gameMode == IcsPlayingBlack ||
15714                    gameMode == MachinePlaysWhite) {
15715             CallFlagEvent();
15716           }
15717         }
15718 }
15719
15720 void
15721 DrawEvent ()
15722 {
15723     /* Offer draw or accept pending draw offer from opponent */
15724
15725     if (appData.icsActive) {
15726         /* Note: tournament rules require draw offers to be
15727            made after you make your move but before you punch
15728            your clock.  Currently ICS doesn't let you do that;
15729            instead, you immediately punch your clock after making
15730            a move, but you can offer a draw at any time. */
15731
15732         SendToICS(ics_prefix);
15733         SendToICS("draw\n");
15734         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15735     } else if (cmailMsgLoaded) {
15736         if (currentMove == cmailOldMove &&
15737             commentList[cmailOldMove] != NULL &&
15738             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15739                    "Black offers a draw" : "White offers a draw")) {
15740             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15741             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15742         } else if (currentMove == cmailOldMove + 1) {
15743             char *offer = WhiteOnMove(cmailOldMove) ?
15744               "White offers a draw" : "Black offers a draw";
15745             AppendComment(currentMove, offer, TRUE);
15746             DisplayComment(currentMove - 1, offer);
15747             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15748         } else {
15749             DisplayError(_("You must make your move before offering a draw"), 0);
15750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15751         }
15752     } else if (first.offeredDraw) {
15753         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15754     } else {
15755         if (first.sendDrawOffers) {
15756             SendToProgram("draw\n", &first);
15757             userOfferedDraw = TRUE;
15758         }
15759     }
15760 }
15761
15762 void
15763 AdjournEvent ()
15764 {
15765     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15766
15767     if (appData.icsActive) {
15768         SendToICS(ics_prefix);
15769         SendToICS("adjourn\n");
15770     } else {
15771         /* Currently GNU Chess doesn't offer or accept Adjourns */
15772     }
15773 }
15774
15775
15776 void
15777 AbortEvent ()
15778 {
15779     /* Offer Abort or accept pending Abort offer from opponent */
15780
15781     if (appData.icsActive) {
15782         SendToICS(ics_prefix);
15783         SendToICS("abort\n");
15784     } else {
15785         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15786     }
15787 }
15788
15789 void
15790 ResignEvent ()
15791 {
15792     /* Resign.  You can do this even if it's not your turn. */
15793
15794     if (appData.icsActive) {
15795         SendToICS(ics_prefix);
15796         SendToICS("resign\n");
15797     } else {
15798         switch (gameMode) {
15799           case MachinePlaysWhite:
15800             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15801             break;
15802           case MachinePlaysBlack:
15803             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15804             break;
15805           case EditGame:
15806             if (cmailMsgLoaded) {
15807                 TruncateGame();
15808                 if (WhiteOnMove(cmailOldMove)) {
15809                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15810                 } else {
15811                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15812                 }
15813                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15814             }
15815             break;
15816           default:
15817             break;
15818         }
15819     }
15820 }
15821
15822
15823 void
15824 StopObservingEvent ()
15825 {
15826     /* Stop observing current games */
15827     SendToICS(ics_prefix);
15828     SendToICS("unobserve\n");
15829 }
15830
15831 void
15832 StopExaminingEvent ()
15833 {
15834     /* Stop observing current game */
15835     SendToICS(ics_prefix);
15836     SendToICS("unexamine\n");
15837 }
15838
15839 void
15840 ForwardInner (int target)
15841 {
15842     int limit; int oldSeekGraphUp = seekGraphUp;
15843
15844     if (appData.debugMode)
15845         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15846                 target, currentMove, forwardMostMove);
15847
15848     if (gameMode == EditPosition)
15849       return;
15850
15851     seekGraphUp = FALSE;
15852     MarkTargetSquares(1);
15853     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15854
15855     if (gameMode == PlayFromGameFile && !pausing)
15856       PauseEvent();
15857
15858     if (gameMode == IcsExamining && pausing)
15859       limit = pauseExamForwardMostMove;
15860     else
15861       limit = forwardMostMove;
15862
15863     if (target > limit) target = limit;
15864
15865     if (target > 0 && moveList[target - 1][0]) {
15866         int fromX, fromY, toX, toY;
15867         toX = moveList[target - 1][2] - AAA;
15868         toY = moveList[target - 1][3] - ONE;
15869         if (moveList[target - 1][1] == '@') {
15870             if (appData.highlightLastMove) {
15871                 SetHighlights(-1, -1, toX, toY);
15872             }
15873         } else {
15874             fromX = moveList[target - 1][0] - AAA;
15875             fromY = moveList[target - 1][1] - ONE;
15876             if (target == currentMove + 1) {
15877                 if(moveList[target - 1][4] == ';') { // multi-leg
15878                     killX = moveList[target - 1][5] - AAA;
15879                     killY = moveList[target - 1][6] - ONE;
15880                 }
15881                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15882                 killX = killY = -1;
15883             }
15884             if (appData.highlightLastMove) {
15885                 SetHighlights(fromX, fromY, toX, toY);
15886             }
15887         }
15888     }
15889     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15890         gameMode == Training || gameMode == PlayFromGameFile ||
15891         gameMode == AnalyzeFile) {
15892         while (currentMove < target) {
15893             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15894             SendMoveToProgram(currentMove++, &first);
15895         }
15896     } else {
15897         currentMove = target;
15898     }
15899
15900     if (gameMode == EditGame || gameMode == EndOfGame) {
15901         whiteTimeRemaining = timeRemaining[0][currentMove];
15902         blackTimeRemaining = timeRemaining[1][currentMove];
15903     }
15904     DisplayBothClocks();
15905     DisplayMove(currentMove - 1);
15906     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15907     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15908     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15909         DisplayComment(currentMove - 1, commentList[currentMove]);
15910     }
15911     ClearMap(); // [HGM] exclude: invalidate map
15912 }
15913
15914
15915 void
15916 ForwardEvent ()
15917 {
15918     if (gameMode == IcsExamining && !pausing) {
15919         SendToICS(ics_prefix);
15920         SendToICS("forward\n");
15921     } else {
15922         ForwardInner(currentMove + 1);
15923     }
15924 }
15925
15926 void
15927 ToEndEvent ()
15928 {
15929     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15930         /* to optimze, we temporarily turn off analysis mode while we feed
15931          * the remaining moves to the engine. Otherwise we get analysis output
15932          * after each move.
15933          */
15934         if (first.analysisSupport) {
15935           SendToProgram("exit\nforce\n", &first);
15936           first.analyzing = FALSE;
15937         }
15938     }
15939
15940     if (gameMode == IcsExamining && !pausing) {
15941         SendToICS(ics_prefix);
15942         SendToICS("forward 999999\n");
15943     } else {
15944         ForwardInner(forwardMostMove);
15945     }
15946
15947     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15948         /* we have fed all the moves, so reactivate analysis mode */
15949         SendToProgram("analyze\n", &first);
15950         first.analyzing = TRUE;
15951         /*first.maybeThinking = TRUE;*/
15952         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15953     }
15954 }
15955
15956 void
15957 BackwardInner (int target)
15958 {
15959     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15960
15961     if (appData.debugMode)
15962         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15963                 target, currentMove, forwardMostMove);
15964
15965     if (gameMode == EditPosition) return;
15966     seekGraphUp = FALSE;
15967     MarkTargetSquares(1);
15968     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15969     if (currentMove <= backwardMostMove) {
15970         ClearHighlights();
15971         DrawPosition(full_redraw, boards[currentMove]);
15972         return;
15973     }
15974     if (gameMode == PlayFromGameFile && !pausing)
15975       PauseEvent();
15976
15977     if (moveList[target][0]) {
15978         int fromX, fromY, toX, toY;
15979         toX = moveList[target][2] - AAA;
15980         toY = moveList[target][3] - ONE;
15981         if (moveList[target][1] == '@') {
15982             if (appData.highlightLastMove) {
15983                 SetHighlights(-1, -1, toX, toY);
15984             }
15985         } else {
15986             fromX = moveList[target][0] - AAA;
15987             fromY = moveList[target][1] - ONE;
15988             if (target == currentMove - 1) {
15989                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15990             }
15991             if (appData.highlightLastMove) {
15992                 SetHighlights(fromX, fromY, toX, toY);
15993             }
15994         }
15995     }
15996     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15997         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15998         while (currentMove > target) {
15999             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16000                 // null move cannot be undone. Reload program with move history before it.
16001                 int i;
16002                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16003                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16004                 }
16005                 SendBoard(&first, i);
16006               if(second.analyzing) SendBoard(&second, i);
16007                 for(currentMove=i; currentMove<target; currentMove++) {
16008                     SendMoveToProgram(currentMove, &first);
16009                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16010                 }
16011                 break;
16012             }
16013             SendToBoth("undo\n");
16014             currentMove--;
16015         }
16016     } else {
16017         currentMove = target;
16018     }
16019
16020     if (gameMode == EditGame || gameMode == EndOfGame) {
16021         whiteTimeRemaining = timeRemaining[0][currentMove];
16022         blackTimeRemaining = timeRemaining[1][currentMove];
16023     }
16024     DisplayBothClocks();
16025     DisplayMove(currentMove - 1);
16026     DrawPosition(full_redraw, boards[currentMove]);
16027     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16028     // [HGM] PV info: routine tests if comment empty
16029     DisplayComment(currentMove - 1, commentList[currentMove]);
16030     ClearMap(); // [HGM] exclude: invalidate map
16031 }
16032
16033 void
16034 BackwardEvent ()
16035 {
16036     if (gameMode == IcsExamining && !pausing) {
16037         SendToICS(ics_prefix);
16038         SendToICS("backward\n");
16039     } else {
16040         BackwardInner(currentMove - 1);
16041     }
16042 }
16043
16044 void
16045 ToStartEvent ()
16046 {
16047     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16048         /* to optimize, we temporarily turn off analysis mode while we undo
16049          * all the moves. Otherwise we get analysis output after each undo.
16050          */
16051         if (first.analysisSupport) {
16052           SendToProgram("exit\nforce\n", &first);
16053           first.analyzing = FALSE;
16054         }
16055     }
16056
16057     if (gameMode == IcsExamining && !pausing) {
16058         SendToICS(ics_prefix);
16059         SendToICS("backward 999999\n");
16060     } else {
16061         BackwardInner(backwardMostMove);
16062     }
16063
16064     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16065         /* we have fed all the moves, so reactivate analysis mode */
16066         SendToProgram("analyze\n", &first);
16067         first.analyzing = TRUE;
16068         /*first.maybeThinking = TRUE;*/
16069         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16070     }
16071 }
16072
16073 void
16074 ToNrEvent (int to)
16075 {
16076   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16077   if (to >= forwardMostMove) to = forwardMostMove;
16078   if (to <= backwardMostMove) to = backwardMostMove;
16079   if (to < currentMove) {
16080     BackwardInner(to);
16081   } else {
16082     ForwardInner(to);
16083   }
16084 }
16085
16086 void
16087 RevertEvent (Boolean annotate)
16088 {
16089     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16090         return;
16091     }
16092     if (gameMode != IcsExamining) {
16093         DisplayError(_("You are not examining a game"), 0);
16094         return;
16095     }
16096     if (pausing) {
16097         DisplayError(_("You can't revert while pausing"), 0);
16098         return;
16099     }
16100     SendToICS(ics_prefix);
16101     SendToICS("revert\n");
16102 }
16103
16104 void
16105 RetractMoveEvent ()
16106 {
16107     switch (gameMode) {
16108       case MachinePlaysWhite:
16109       case MachinePlaysBlack:
16110         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16111             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16112             return;
16113         }
16114         if (forwardMostMove < 2) return;
16115         currentMove = forwardMostMove = forwardMostMove - 2;
16116         whiteTimeRemaining = timeRemaining[0][currentMove];
16117         blackTimeRemaining = timeRemaining[1][currentMove];
16118         DisplayBothClocks();
16119         DisplayMove(currentMove - 1);
16120         ClearHighlights();/*!! could figure this out*/
16121         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16122         SendToProgram("remove\n", &first);
16123         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16124         break;
16125
16126       case BeginningOfGame:
16127       default:
16128         break;
16129
16130       case IcsPlayingWhite:
16131       case IcsPlayingBlack:
16132         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16133             SendToICS(ics_prefix);
16134             SendToICS("takeback 2\n");
16135         } else {
16136             SendToICS(ics_prefix);
16137             SendToICS("takeback 1\n");
16138         }
16139         break;
16140     }
16141 }
16142
16143 void
16144 MoveNowEvent ()
16145 {
16146     ChessProgramState *cps;
16147
16148     switch (gameMode) {
16149       case MachinePlaysWhite:
16150         if (!WhiteOnMove(forwardMostMove)) {
16151             DisplayError(_("It is your turn"), 0);
16152             return;
16153         }
16154         cps = &first;
16155         break;
16156       case MachinePlaysBlack:
16157         if (WhiteOnMove(forwardMostMove)) {
16158             DisplayError(_("It is your turn"), 0);
16159             return;
16160         }
16161         cps = &first;
16162         break;
16163       case TwoMachinesPlay:
16164         if (WhiteOnMove(forwardMostMove) ==
16165             (first.twoMachinesColor[0] == 'w')) {
16166             cps = &first;
16167         } else {
16168             cps = &second;
16169         }
16170         break;
16171       case BeginningOfGame:
16172       default:
16173         return;
16174     }
16175     SendToProgram("?\n", cps);
16176 }
16177
16178 void
16179 TruncateGameEvent ()
16180 {
16181     EditGameEvent();
16182     if (gameMode != EditGame) return;
16183     TruncateGame();
16184 }
16185
16186 void
16187 TruncateGame ()
16188 {
16189     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16190     if (forwardMostMove > currentMove) {
16191         if (gameInfo.resultDetails != NULL) {
16192             free(gameInfo.resultDetails);
16193             gameInfo.resultDetails = NULL;
16194             gameInfo.result = GameUnfinished;
16195         }
16196         forwardMostMove = currentMove;
16197         HistorySet(parseList, backwardMostMove, forwardMostMove,
16198                    currentMove-1);
16199     }
16200 }
16201
16202 void
16203 HintEvent ()
16204 {
16205     if (appData.noChessProgram) return;
16206     switch (gameMode) {
16207       case MachinePlaysWhite:
16208         if (WhiteOnMove(forwardMostMove)) {
16209             DisplayError(_("Wait until your turn."), 0);
16210             return;
16211         }
16212         break;
16213       case BeginningOfGame:
16214       case MachinePlaysBlack:
16215         if (!WhiteOnMove(forwardMostMove)) {
16216             DisplayError(_("Wait until your turn."), 0);
16217             return;
16218         }
16219         break;
16220       default:
16221         DisplayError(_("No hint available"), 0);
16222         return;
16223     }
16224     SendToProgram("hint\n", &first);
16225     hintRequested = TRUE;
16226 }
16227
16228 int
16229 SaveSelected (FILE *g, int dummy, char *dummy2)
16230 {
16231     ListGame * lg = (ListGame *) gameList.head;
16232     int nItem, cnt=0;
16233     FILE *f;
16234
16235     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16236         DisplayError(_("Game list not loaded or empty"), 0);
16237         return 0;
16238     }
16239
16240     creatingBook = TRUE; // suppresses stuff during load game
16241
16242     /* Get list size */
16243     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16244         if(lg->position >= 0) { // selected?
16245             LoadGame(f, nItem, "", TRUE);
16246             SaveGamePGN2(g); // leaves g open
16247             cnt++; DoEvents();
16248         }
16249         lg = (ListGame *) lg->node.succ;
16250     }
16251
16252     fclose(g);
16253     creatingBook = FALSE;
16254
16255     return cnt;
16256 }
16257
16258 void
16259 CreateBookEvent ()
16260 {
16261     ListGame * lg = (ListGame *) gameList.head;
16262     FILE *f, *g;
16263     int nItem;
16264     static int secondTime = FALSE;
16265
16266     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16267         DisplayError(_("Game list not loaded or empty"), 0);
16268         return;
16269     }
16270
16271     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16272         fclose(g);
16273         secondTime++;
16274         DisplayNote(_("Book file exists! Try again for overwrite."));
16275         return;
16276     }
16277
16278     creatingBook = TRUE;
16279     secondTime = FALSE;
16280
16281     /* Get list size */
16282     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16283         if(lg->position >= 0) {
16284             LoadGame(f, nItem, "", TRUE);
16285             AddGameToBook(TRUE);
16286             DoEvents();
16287         }
16288         lg = (ListGame *) lg->node.succ;
16289     }
16290
16291     creatingBook = FALSE;
16292     FlushBook();
16293 }
16294
16295 void
16296 BookEvent ()
16297 {
16298     if (appData.noChessProgram) return;
16299     switch (gameMode) {
16300       case MachinePlaysWhite:
16301         if (WhiteOnMove(forwardMostMove)) {
16302             DisplayError(_("Wait until your turn."), 0);
16303             return;
16304         }
16305         break;
16306       case BeginningOfGame:
16307       case MachinePlaysBlack:
16308         if (!WhiteOnMove(forwardMostMove)) {
16309             DisplayError(_("Wait until your turn."), 0);
16310             return;
16311         }
16312         break;
16313       case EditPosition:
16314         EditPositionDone(TRUE);
16315         break;
16316       case TwoMachinesPlay:
16317         return;
16318       default:
16319         break;
16320     }
16321     SendToProgram("bk\n", &first);
16322     bookOutput[0] = NULLCHAR;
16323     bookRequested = TRUE;
16324 }
16325
16326 void
16327 AboutGameEvent ()
16328 {
16329     char *tags = PGNTags(&gameInfo);
16330     TagsPopUp(tags, CmailMsg());
16331     free(tags);
16332 }
16333
16334 /* end button procedures */
16335
16336 void
16337 PrintPosition (FILE *fp, int move)
16338 {
16339     int i, j;
16340
16341     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16342         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16343             char c = PieceToChar(boards[move][i][j]);
16344             fputc(c == '?' ? '.' : c, fp);
16345             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16346         }
16347     }
16348     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16349       fprintf(fp, "white to play\n");
16350     else
16351       fprintf(fp, "black to play\n");
16352 }
16353
16354 void
16355 PrintOpponents (FILE *fp)
16356 {
16357     if (gameInfo.white != NULL) {
16358         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16359     } else {
16360         fprintf(fp, "\n");
16361     }
16362 }
16363
16364 /* Find last component of program's own name, using some heuristics */
16365 void
16366 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16367 {
16368     char *p, *q, c;
16369     int local = (strcmp(host, "localhost") == 0);
16370     while (!local && (p = strchr(prog, ';')) != NULL) {
16371         p++;
16372         while (*p == ' ') p++;
16373         prog = p;
16374     }
16375     if (*prog == '"' || *prog == '\'') {
16376         q = strchr(prog + 1, *prog);
16377     } else {
16378         q = strchr(prog, ' ');
16379     }
16380     if (q == NULL) q = prog + strlen(prog);
16381     p = q;
16382     while (p >= prog && *p != '/' && *p != '\\') p--;
16383     p++;
16384     if(p == prog && *p == '"') p++;
16385     c = *q; *q = 0;
16386     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16387     memcpy(buf, p, q - p);
16388     buf[q - p] = NULLCHAR;
16389     if (!local) {
16390         strcat(buf, "@");
16391         strcat(buf, host);
16392     }
16393 }
16394
16395 char *
16396 TimeControlTagValue ()
16397 {
16398     char buf[MSG_SIZ];
16399     if (!appData.clockMode) {
16400       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16401     } else if (movesPerSession > 0) {
16402       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16403     } else if (timeIncrement == 0) {
16404       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16405     } else {
16406       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16407     }
16408     return StrSave(buf);
16409 }
16410
16411 void
16412 SetGameInfo ()
16413 {
16414     /* This routine is used only for certain modes */
16415     VariantClass v = gameInfo.variant;
16416     ChessMove r = GameUnfinished;
16417     char *p = NULL;
16418
16419     if(keepInfo) return;
16420
16421     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16422         r = gameInfo.result;
16423         p = gameInfo.resultDetails;
16424         gameInfo.resultDetails = NULL;
16425     }
16426     ClearGameInfo(&gameInfo);
16427     gameInfo.variant = v;
16428
16429     switch (gameMode) {
16430       case MachinePlaysWhite:
16431         gameInfo.event = StrSave( appData.pgnEventHeader );
16432         gameInfo.site = StrSave(HostName());
16433         gameInfo.date = PGNDate();
16434         gameInfo.round = StrSave("-");
16435         gameInfo.white = StrSave(first.tidy);
16436         gameInfo.black = StrSave(UserName());
16437         gameInfo.timeControl = TimeControlTagValue();
16438         break;
16439
16440       case MachinePlaysBlack:
16441         gameInfo.event = StrSave( appData.pgnEventHeader );
16442         gameInfo.site = StrSave(HostName());
16443         gameInfo.date = PGNDate();
16444         gameInfo.round = StrSave("-");
16445         gameInfo.white = StrSave(UserName());
16446         gameInfo.black = StrSave(first.tidy);
16447         gameInfo.timeControl = TimeControlTagValue();
16448         break;
16449
16450       case TwoMachinesPlay:
16451         gameInfo.event = StrSave( appData.pgnEventHeader );
16452         gameInfo.site = StrSave(HostName());
16453         gameInfo.date = PGNDate();
16454         if (roundNr > 0) {
16455             char buf[MSG_SIZ];
16456             snprintf(buf, MSG_SIZ, "%d", roundNr);
16457             gameInfo.round = StrSave(buf);
16458         } else {
16459             gameInfo.round = StrSave("-");
16460         }
16461         if (first.twoMachinesColor[0] == 'w') {
16462             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16463             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16464         } else {
16465             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16466             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16467         }
16468         gameInfo.timeControl = TimeControlTagValue();
16469         break;
16470
16471       case EditGame:
16472         gameInfo.event = StrSave("Edited game");
16473         gameInfo.site = StrSave(HostName());
16474         gameInfo.date = PGNDate();
16475         gameInfo.round = StrSave("-");
16476         gameInfo.white = StrSave("-");
16477         gameInfo.black = StrSave("-");
16478         gameInfo.result = r;
16479         gameInfo.resultDetails = p;
16480         break;
16481
16482       case EditPosition:
16483         gameInfo.event = StrSave("Edited position");
16484         gameInfo.site = StrSave(HostName());
16485         gameInfo.date = PGNDate();
16486         gameInfo.round = StrSave("-");
16487         gameInfo.white = StrSave("-");
16488         gameInfo.black = StrSave("-");
16489         break;
16490
16491       case IcsPlayingWhite:
16492       case IcsPlayingBlack:
16493       case IcsObserving:
16494       case IcsExamining:
16495         break;
16496
16497       case PlayFromGameFile:
16498         gameInfo.event = StrSave("Game from non-PGN file");
16499         gameInfo.site = StrSave(HostName());
16500         gameInfo.date = PGNDate();
16501         gameInfo.round = StrSave("-");
16502         gameInfo.white = StrSave("?");
16503         gameInfo.black = StrSave("?");
16504         break;
16505
16506       default:
16507         break;
16508     }
16509 }
16510
16511 void
16512 ReplaceComment (int index, char *text)
16513 {
16514     int len;
16515     char *p;
16516     float score;
16517
16518     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16519        pvInfoList[index-1].depth == len &&
16520        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16521        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16522     while (*text == '\n') text++;
16523     len = strlen(text);
16524     while (len > 0 && text[len - 1] == '\n') len--;
16525
16526     if (commentList[index] != NULL)
16527       free(commentList[index]);
16528
16529     if (len == 0) {
16530         commentList[index] = NULL;
16531         return;
16532     }
16533   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16534       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16535       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16536     commentList[index] = (char *) malloc(len + 2);
16537     strncpy(commentList[index], text, len);
16538     commentList[index][len] = '\n';
16539     commentList[index][len + 1] = NULLCHAR;
16540   } else {
16541     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16542     char *p;
16543     commentList[index] = (char *) malloc(len + 7);
16544     safeStrCpy(commentList[index], "{\n", 3);
16545     safeStrCpy(commentList[index]+2, text, len+1);
16546     commentList[index][len+2] = NULLCHAR;
16547     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16548     strcat(commentList[index], "\n}\n");
16549   }
16550 }
16551
16552 void
16553 CrushCRs (char *text)
16554 {
16555   char *p = text;
16556   char *q = text;
16557   char ch;
16558
16559   do {
16560     ch = *p++;
16561     if (ch == '\r') continue;
16562     *q++ = ch;
16563   } while (ch != '\0');
16564 }
16565
16566 void
16567 AppendComment (int index, char *text, Boolean addBraces)
16568 /* addBraces  tells if we should add {} */
16569 {
16570     int oldlen, len;
16571     char *old;
16572
16573 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16574     if(addBraces == 3) addBraces = 0; else // force appending literally
16575     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16576
16577     CrushCRs(text);
16578     while (*text == '\n') text++;
16579     len = strlen(text);
16580     while (len > 0 && text[len - 1] == '\n') len--;
16581     text[len] = NULLCHAR;
16582
16583     if (len == 0) return;
16584
16585     if (commentList[index] != NULL) {
16586       Boolean addClosingBrace = addBraces;
16587         old = commentList[index];
16588         oldlen = strlen(old);
16589         while(commentList[index][oldlen-1] ==  '\n')
16590           commentList[index][--oldlen] = NULLCHAR;
16591         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16592         safeStrCpy(commentList[index], old, oldlen + len + 6);
16593         free(old);
16594         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16595         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16596           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16597           while (*text == '\n') { text++; len--; }
16598           commentList[index][--oldlen] = NULLCHAR;
16599       }
16600         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16601         else          strcat(commentList[index], "\n");
16602         strcat(commentList[index], text);
16603         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16604         else          strcat(commentList[index], "\n");
16605     } else {
16606         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16607         if(addBraces)
16608           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16609         else commentList[index][0] = NULLCHAR;
16610         strcat(commentList[index], text);
16611         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16612         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16613     }
16614 }
16615
16616 static char *
16617 FindStr (char * text, char * sub_text)
16618 {
16619     char * result = strstr( text, sub_text );
16620
16621     if( result != NULL ) {
16622         result += strlen( sub_text );
16623     }
16624
16625     return result;
16626 }
16627
16628 /* [AS] Try to extract PV info from PGN comment */
16629 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16630 char *
16631 GetInfoFromComment (int index, char * text)
16632 {
16633     char * sep = text, *p;
16634
16635     if( text != NULL && index > 0 ) {
16636         int score = 0;
16637         int depth = 0;
16638         int time = -1, sec = 0, deci;
16639         char * s_eval = FindStr( text, "[%eval " );
16640         char * s_emt = FindStr( text, "[%emt " );
16641 #if 0
16642         if( s_eval != NULL || s_emt != NULL ) {
16643 #else
16644         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16645 #endif
16646             /* New style */
16647             char delim;
16648
16649             if( s_eval != NULL ) {
16650                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16651                     return text;
16652                 }
16653
16654                 if( delim != ']' ) {
16655                     return text;
16656                 }
16657             }
16658
16659             if( s_emt != NULL ) {
16660             }
16661                 return text;
16662         }
16663         else {
16664             /* We expect something like: [+|-]nnn.nn/dd */
16665             int score_lo = 0;
16666
16667             if(*text != '{') return text; // [HGM] braces: must be normal comment
16668
16669             sep = strchr( text, '/' );
16670             if( sep == NULL || sep < (text+4) ) {
16671                 return text;
16672             }
16673
16674             p = text;
16675             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16676             if(p[1] == '(') { // comment starts with PV
16677                p = strchr(p, ')'); // locate end of PV
16678                if(p == NULL || sep < p+5) return text;
16679                // at this point we have something like "{(.*) +0.23/6 ..."
16680                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16681                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16682                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16683             }
16684             time = -1; sec = -1; deci = -1;
16685             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16686                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16687                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16688                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16689                 return text;
16690             }
16691
16692             if( score_lo < 0 || score_lo >= 100 ) {
16693                 return text;
16694             }
16695
16696             if(sec >= 0) time = 600*time + 10*sec; else
16697             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16698
16699             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16700
16701             /* [HGM] PV time: now locate end of PV info */
16702             while( *++sep >= '0' && *sep <= '9'); // strip depth
16703             if(time >= 0)
16704             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16705             if(sec >= 0)
16706             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16707             if(deci >= 0)
16708             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16709             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16710         }
16711
16712         if( depth <= 0 ) {
16713             return text;
16714         }
16715
16716         if( time < 0 ) {
16717             time = -1;
16718         }
16719
16720         pvInfoList[index-1].depth = depth;
16721         pvInfoList[index-1].score = score;
16722         pvInfoList[index-1].time  = 10*time; // centi-sec
16723         if(*sep == '}') *sep = 0; else *--sep = '{';
16724         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16725     }
16726     return sep;
16727 }
16728
16729 void
16730 SendToProgram (char *message, ChessProgramState *cps)
16731 {
16732     int count, outCount, error;
16733     char buf[MSG_SIZ];
16734
16735     if (cps->pr == NoProc) return;
16736     Attention(cps);
16737
16738     if (appData.debugMode) {
16739         TimeMark now;
16740         GetTimeMark(&now);
16741         fprintf(debugFP, "%ld >%-6s: %s",
16742                 SubtractTimeMarks(&now, &programStartTime),
16743                 cps->which, message);
16744         if(serverFP)
16745             fprintf(serverFP, "%ld >%-6s: %s",
16746                 SubtractTimeMarks(&now, &programStartTime),
16747                 cps->which, message), fflush(serverFP);
16748     }
16749
16750     count = strlen(message);
16751     outCount = OutputToProcess(cps->pr, message, count, &error);
16752     if (outCount < count && !exiting
16753                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16754       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16755       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16756         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16757             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16758                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16759                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16760                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16761             } else {
16762                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16763                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16764                 gameInfo.result = res;
16765             }
16766             gameInfo.resultDetails = StrSave(buf);
16767         }
16768         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16769         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16770     }
16771 }
16772
16773 void
16774 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16775 {
16776     char *end_str;
16777     char buf[MSG_SIZ];
16778     ChessProgramState *cps = (ChessProgramState *)closure;
16779
16780     if (isr != cps->isr) return; /* Killed intentionally */
16781     if (count <= 0) {
16782         if (count == 0) {
16783             RemoveInputSource(cps->isr);
16784             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16785                     _(cps->which), cps->program);
16786             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16787             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16788                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16789                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16790                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16791                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16792                 } else {
16793                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16794                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16795                     gameInfo.result = res;
16796                 }
16797                 gameInfo.resultDetails = StrSave(buf);
16798             }
16799             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16800             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16801         } else {
16802             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16803                     _(cps->which), cps->program);
16804             RemoveInputSource(cps->isr);
16805
16806             /* [AS] Program is misbehaving badly... kill it */
16807             if( count == -2 ) {
16808                 DestroyChildProcess( cps->pr, 9 );
16809                 cps->pr = NoProc;
16810             }
16811
16812             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16813         }
16814         return;
16815     }
16816
16817     if ((end_str = strchr(message, '\r')) != NULL)
16818       *end_str = NULLCHAR;
16819     if ((end_str = strchr(message, '\n')) != NULL)
16820       *end_str = NULLCHAR;
16821
16822     if (appData.debugMode) {
16823         TimeMark now; int print = 1;
16824         char *quote = ""; char c; int i;
16825
16826         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16827                 char start = message[0];
16828                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16829                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16830                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16831                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16832                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16833                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16834                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16835                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16836                    sscanf(message, "hint: %c", &c)!=1 &&
16837                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16838                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16839                     print = (appData.engineComments >= 2);
16840                 }
16841                 message[0] = start; // restore original message
16842         }
16843         if(print) {
16844                 GetTimeMark(&now);
16845                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16846                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16847                         quote,
16848                         message);
16849                 if(serverFP)
16850                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16851                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16852                         quote,
16853                         message), fflush(serverFP);
16854         }
16855     }
16856
16857     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16858     if (appData.icsEngineAnalyze) {
16859         if (strstr(message, "whisper") != NULL ||
16860              strstr(message, "kibitz") != NULL ||
16861             strstr(message, "tellics") != NULL) return;
16862     }
16863
16864     HandleMachineMove(message, cps);
16865 }
16866
16867
16868 void
16869 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16870 {
16871     char buf[MSG_SIZ];
16872     int seconds;
16873
16874     if( timeControl_2 > 0 ) {
16875         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16876             tc = timeControl_2;
16877         }
16878     }
16879     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16880     inc /= cps->timeOdds;
16881     st  /= cps->timeOdds;
16882
16883     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16884
16885     if (st > 0) {
16886       /* Set exact time per move, normally using st command */
16887       if (cps->stKludge) {
16888         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16889         seconds = st % 60;
16890         if (seconds == 0) {
16891           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16892         } else {
16893           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16894         }
16895       } else {
16896         snprintf(buf, MSG_SIZ, "st %d\n", st);
16897       }
16898     } else {
16899       /* Set conventional or incremental time control, using level command */
16900       if (seconds == 0) {
16901         /* Note old gnuchess bug -- minutes:seconds used to not work.
16902            Fixed in later versions, but still avoid :seconds
16903            when seconds is 0. */
16904         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16905       } else {
16906         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16907                  seconds, inc/1000.);
16908       }
16909     }
16910     SendToProgram(buf, cps);
16911
16912     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16913     /* Orthogonally, limit search to given depth */
16914     if (sd > 0) {
16915       if (cps->sdKludge) {
16916         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16917       } else {
16918         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16919       }
16920       SendToProgram(buf, cps);
16921     }
16922
16923     if(cps->nps >= 0) { /* [HGM] nps */
16924         if(cps->supportsNPS == FALSE)
16925           cps->nps = -1; // don't use if engine explicitly says not supported!
16926         else {
16927           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16928           SendToProgram(buf, cps);
16929         }
16930     }
16931 }
16932
16933 ChessProgramState *
16934 WhitePlayer ()
16935 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16936 {
16937     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16938        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16939         return &second;
16940     return &first;
16941 }
16942
16943 void
16944 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16945 {
16946     char message[MSG_SIZ];
16947     long time, otime;
16948
16949     /* Note: this routine must be called when the clocks are stopped
16950        or when they have *just* been set or switched; otherwise
16951        it will be off by the time since the current tick started.
16952     */
16953     if (machineWhite) {
16954         time = whiteTimeRemaining / 10;
16955         otime = blackTimeRemaining / 10;
16956     } else {
16957         time = blackTimeRemaining / 10;
16958         otime = whiteTimeRemaining / 10;
16959     }
16960     /* [HGM] translate opponent's time by time-odds factor */
16961     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16962
16963     if (time <= 0) time = 1;
16964     if (otime <= 0) otime = 1;
16965
16966     snprintf(message, MSG_SIZ, "time %ld\n", time);
16967     SendToProgram(message, cps);
16968
16969     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16970     SendToProgram(message, cps);
16971 }
16972
16973 char *
16974 EngineDefinedVariant (ChessProgramState *cps, int n)
16975 {   // return name of n-th unknown variant that engine supports
16976     static char buf[MSG_SIZ];
16977     char *p, *s = cps->variants;
16978     if(!s) return NULL;
16979     do { // parse string from variants feature
16980       VariantClass v;
16981         p = strchr(s, ',');
16982         if(p) *p = NULLCHAR;
16983       v = StringToVariant(s);
16984       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16985         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16986             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16987                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16988                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16989                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16990             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16991         }
16992         if(p) *p++ = ',';
16993         if(n < 0) return buf;
16994     } while(s = p);
16995     return NULL;
16996 }
16997
16998 int
16999 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17000 {
17001   char buf[MSG_SIZ];
17002   int len = strlen(name);
17003   int val;
17004
17005   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17006     (*p) += len + 1;
17007     sscanf(*p, "%d", &val);
17008     *loc = (val != 0);
17009     while (**p && **p != ' ')
17010       (*p)++;
17011     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17012     SendToProgram(buf, cps);
17013     return TRUE;
17014   }
17015   return FALSE;
17016 }
17017
17018 int
17019 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17020 {
17021   char buf[MSG_SIZ];
17022   int len = strlen(name);
17023   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17024     (*p) += len + 1;
17025     sscanf(*p, "%d", loc);
17026     while (**p && **p != ' ') (*p)++;
17027     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17028     SendToProgram(buf, cps);
17029     return TRUE;
17030   }
17031   return FALSE;
17032 }
17033
17034 int
17035 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17036 {
17037   char buf[MSG_SIZ];
17038   int len = strlen(name);
17039   if (strncmp((*p), name, len) == 0
17040       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17041     (*p) += len + 2;
17042     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17043     sscanf(*p, "%[^\"]", *loc);
17044     while (**p && **p != '\"') (*p)++;
17045     if (**p == '\"') (*p)++;
17046     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17047     SendToProgram(buf, cps);
17048     return TRUE;
17049   }
17050   return FALSE;
17051 }
17052
17053 int
17054 ParseOption (Option *opt, ChessProgramState *cps)
17055 // [HGM] options: process the string that defines an engine option, and determine
17056 // name, type, default value, and allowed value range
17057 {
17058         char *p, *q, buf[MSG_SIZ];
17059         int n, min = (-1)<<31, max = 1<<31, def;
17060
17061         opt->target = &opt->value;   // OK for spin/slider and checkbox
17062         if(p = strstr(opt->name, " -spin ")) {
17063             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17064             if(max < min) max = min; // enforce consistency
17065             if(def < min) def = min;
17066             if(def > max) def = max;
17067             opt->value = def;
17068             opt->min = min;
17069             opt->max = max;
17070             opt->type = Spin;
17071         } else if((p = strstr(opt->name, " -slider "))) {
17072             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17073             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17074             if(max < min) max = min; // enforce consistency
17075             if(def < min) def = min;
17076             if(def > max) def = max;
17077             opt->value = def;
17078             opt->min = min;
17079             opt->max = max;
17080             opt->type = Spin; // Slider;
17081         } else if((p = strstr(opt->name, " -string "))) {
17082             opt->textValue = p+9;
17083             opt->type = TextBox;
17084             opt->target = &opt->textValue;
17085         } else if((p = strstr(opt->name, " -file "))) {
17086             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17087             opt->target = opt->textValue = p+7;
17088             opt->type = FileName; // FileName;
17089             opt->target = &opt->textValue;
17090         } else if((p = strstr(opt->name, " -path "))) {
17091             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17092             opt->target = opt->textValue = p+7;
17093             opt->type = PathName; // PathName;
17094             opt->target = &opt->textValue;
17095         } else if(p = strstr(opt->name, " -check ")) {
17096             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17097             opt->value = (def != 0);
17098             opt->type = CheckBox;
17099         } else if(p = strstr(opt->name, " -combo ")) {
17100             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17101             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17102             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17103             opt->value = n = 0;
17104             while(q = StrStr(q, " /// ")) {
17105                 n++; *q = 0;    // count choices, and null-terminate each of them
17106                 q += 5;
17107                 if(*q == '*') { // remember default, which is marked with * prefix
17108                     q++;
17109                     opt->value = n;
17110                 }
17111                 cps->comboList[cps->comboCnt++] = q;
17112             }
17113             cps->comboList[cps->comboCnt++] = NULL;
17114             opt->max = n + 1;
17115             opt->type = ComboBox;
17116         } else if(p = strstr(opt->name, " -button")) {
17117             opt->type = Button;
17118         } else if(p = strstr(opt->name, " -save")) {
17119             opt->type = SaveButton;
17120         } else return FALSE;
17121         *p = 0; // terminate option name
17122         // now look if the command-line options define a setting for this engine option.
17123         if(cps->optionSettings && cps->optionSettings[0])
17124             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17125         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17126           snprintf(buf, MSG_SIZ, "option %s", p);
17127                 if(p = strstr(buf, ",")) *p = 0;
17128                 if(q = strchr(buf, '=')) switch(opt->type) {
17129                     case ComboBox:
17130                         for(n=0; n<opt->max; n++)
17131                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17132                         break;
17133                     case TextBox:
17134                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17135                         break;
17136                     case Spin:
17137                     case CheckBox:
17138                         opt->value = atoi(q+1);
17139                     default:
17140                         break;
17141                 }
17142                 strcat(buf, "\n");
17143                 SendToProgram(buf, cps);
17144         }
17145         return TRUE;
17146 }
17147
17148 void
17149 FeatureDone (ChessProgramState *cps, int val)
17150 {
17151   DelayedEventCallback cb = GetDelayedEvent();
17152   if ((cb == InitBackEnd3 && cps == &first) ||
17153       (cb == SettingsMenuIfReady && cps == &second) ||
17154       (cb == LoadEngine) ||
17155       (cb == TwoMachinesEventIfReady)) {
17156     CancelDelayedEvent();
17157     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17158   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17159   cps->initDone = val;
17160   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17161 }
17162
17163 /* Parse feature command from engine */
17164 void
17165 ParseFeatures (char *args, ChessProgramState *cps)
17166 {
17167   char *p = args;
17168   char *q = NULL;
17169   int val;
17170   char buf[MSG_SIZ];
17171
17172   for (;;) {
17173     while (*p == ' ') p++;
17174     if (*p == NULLCHAR) return;
17175
17176     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17177     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17178     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17179     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17180     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17181     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17182     if (BoolFeature(&p, "reuse", &val, cps)) {
17183       /* Engine can disable reuse, but can't enable it if user said no */
17184       if (!val) cps->reuse = FALSE;
17185       continue;
17186     }
17187     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17188     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17189       if (gameMode == TwoMachinesPlay) {
17190         DisplayTwoMachinesTitle();
17191       } else {
17192         DisplayTitle("");
17193       }
17194       continue;
17195     }
17196     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17197     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17198     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17199     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17200     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17201     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17202     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17203     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17204     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17205     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17206     if (IntFeature(&p, "done", &val, cps)) {
17207       FeatureDone(cps, val);
17208       continue;
17209     }
17210     /* Added by Tord: */
17211     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17212     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17213     /* End of additions by Tord */
17214
17215     /* [HGM] added features: */
17216     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17217     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17218     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17219     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17220     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17221     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17222     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17223     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17224         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17225         FREE(cps->option[cps->nrOptions].name);
17226         cps->option[cps->nrOptions].name = q; q = NULL;
17227         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17228           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17229             SendToProgram(buf, cps);
17230             continue;
17231         }
17232         if(cps->nrOptions >= MAX_OPTIONS) {
17233             cps->nrOptions--;
17234             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17235             DisplayError(buf, 0);
17236         }
17237         continue;
17238     }
17239     /* End of additions by HGM */
17240
17241     /* unknown feature: complain and skip */
17242     q = p;
17243     while (*q && *q != '=') q++;
17244     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17245     SendToProgram(buf, cps);
17246     p = q;
17247     if (*p == '=') {
17248       p++;
17249       if (*p == '\"') {
17250         p++;
17251         while (*p && *p != '\"') p++;
17252         if (*p == '\"') p++;
17253       } else {
17254         while (*p && *p != ' ') p++;
17255       }
17256     }
17257   }
17258
17259 }
17260
17261 void
17262 PeriodicUpdatesEvent (int newState)
17263 {
17264     if (newState == appData.periodicUpdates)
17265       return;
17266
17267     appData.periodicUpdates=newState;
17268
17269     /* Display type changes, so update it now */
17270 //    DisplayAnalysis();
17271
17272     /* Get the ball rolling again... */
17273     if (newState) {
17274         AnalysisPeriodicEvent(1);
17275         StartAnalysisClock();
17276     }
17277 }
17278
17279 void
17280 PonderNextMoveEvent (int newState)
17281 {
17282     if (newState == appData.ponderNextMove) return;
17283     if (gameMode == EditPosition) EditPositionDone(TRUE);
17284     if (newState) {
17285         SendToProgram("hard\n", &first);
17286         if (gameMode == TwoMachinesPlay) {
17287             SendToProgram("hard\n", &second);
17288         }
17289     } else {
17290         SendToProgram("easy\n", &first);
17291         thinkOutput[0] = NULLCHAR;
17292         if (gameMode == TwoMachinesPlay) {
17293             SendToProgram("easy\n", &second);
17294         }
17295     }
17296     appData.ponderNextMove = newState;
17297 }
17298
17299 void
17300 NewSettingEvent (int option, int *feature, char *command, int value)
17301 {
17302     char buf[MSG_SIZ];
17303
17304     if (gameMode == EditPosition) EditPositionDone(TRUE);
17305     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17306     if(feature == NULL || *feature) SendToProgram(buf, &first);
17307     if (gameMode == TwoMachinesPlay) {
17308         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17309     }
17310 }
17311
17312 void
17313 ShowThinkingEvent ()
17314 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17315 {
17316     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17317     int newState = appData.showThinking
17318         // [HGM] thinking: other features now need thinking output as well
17319         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17320
17321     if (oldState == newState) return;
17322     oldState = newState;
17323     if (gameMode == EditPosition) EditPositionDone(TRUE);
17324     if (oldState) {
17325         SendToProgram("post\n", &first);
17326         if (gameMode == TwoMachinesPlay) {
17327             SendToProgram("post\n", &second);
17328         }
17329     } else {
17330         SendToProgram("nopost\n", &first);
17331         thinkOutput[0] = NULLCHAR;
17332         if (gameMode == TwoMachinesPlay) {
17333             SendToProgram("nopost\n", &second);
17334         }
17335     }
17336 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17337 }
17338
17339 void
17340 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17341 {
17342   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17343   if (pr == NoProc) return;
17344   AskQuestion(title, question, replyPrefix, pr);
17345 }
17346
17347 void
17348 TypeInEvent (char firstChar)
17349 {
17350     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17351         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17352         gameMode == AnalyzeMode || gameMode == EditGame ||
17353         gameMode == EditPosition || gameMode == IcsExamining ||
17354         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17355         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17356                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17357                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17358         gameMode == Training) PopUpMoveDialog(firstChar);
17359 }
17360
17361 void
17362 TypeInDoneEvent (char *move)
17363 {
17364         Board board;
17365         int n, fromX, fromY, toX, toY;
17366         char promoChar;
17367         ChessMove moveType;
17368
17369         // [HGM] FENedit
17370         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17371                 EditPositionPasteFEN(move);
17372                 return;
17373         }
17374         // [HGM] movenum: allow move number to be typed in any mode
17375         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17376           ToNrEvent(2*n-1);
17377           return;
17378         }
17379         // undocumented kludge: allow command-line option to be typed in!
17380         // (potentially fatal, and does not implement the effect of the option.)
17381         // should only be used for options that are values on which future decisions will be made,
17382         // and definitely not on options that would be used during initialization.
17383         if(strstr(move, "!!! -") == move) {
17384             ParseArgsFromString(move+4);
17385             return;
17386         }
17387
17388       if (gameMode != EditGame && currentMove != forwardMostMove &&
17389         gameMode != Training) {
17390         DisplayMoveError(_("Displayed move is not current"));
17391       } else {
17392         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17393           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17394         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17395         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17396           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17397           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17398         } else {
17399           DisplayMoveError(_("Could not parse move"));
17400         }
17401       }
17402 }
17403
17404 void
17405 DisplayMove (int moveNumber)
17406 {
17407     char message[MSG_SIZ];
17408     char res[MSG_SIZ];
17409     char cpThinkOutput[MSG_SIZ];
17410
17411     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17412
17413     if (moveNumber == forwardMostMove - 1 ||
17414         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17415
17416         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17417
17418         if (strchr(cpThinkOutput, '\n')) {
17419             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17420         }
17421     } else {
17422         *cpThinkOutput = NULLCHAR;
17423     }
17424
17425     /* [AS] Hide thinking from human user */
17426     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17427         *cpThinkOutput = NULLCHAR;
17428         if( thinkOutput[0] != NULLCHAR ) {
17429             int i;
17430
17431             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17432                 cpThinkOutput[i] = '.';
17433             }
17434             cpThinkOutput[i] = NULLCHAR;
17435             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17436         }
17437     }
17438
17439     if (moveNumber == forwardMostMove - 1 &&
17440         gameInfo.resultDetails != NULL) {
17441         if (gameInfo.resultDetails[0] == NULLCHAR) {
17442           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17443         } else {
17444           snprintf(res, MSG_SIZ, " {%s} %s",
17445                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17446         }
17447     } else {
17448         res[0] = NULLCHAR;
17449     }
17450
17451     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17452         DisplayMessage(res, cpThinkOutput);
17453     } else {
17454       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17455                 WhiteOnMove(moveNumber) ? " " : ".. ",
17456                 parseList[moveNumber], res);
17457         DisplayMessage(message, cpThinkOutput);
17458     }
17459 }
17460
17461 void
17462 DisplayComment (int moveNumber, char *text)
17463 {
17464     char title[MSG_SIZ];
17465
17466     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17467       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17468     } else {
17469       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17470               WhiteOnMove(moveNumber) ? " " : ".. ",
17471               parseList[moveNumber]);
17472     }
17473     if (text != NULL && (appData.autoDisplayComment || commentUp))
17474         CommentPopUp(title, text);
17475 }
17476
17477 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17478  * might be busy thinking or pondering.  It can be omitted if your
17479  * gnuchess is configured to stop thinking immediately on any user
17480  * input.  However, that gnuchess feature depends on the FIONREAD
17481  * ioctl, which does not work properly on some flavors of Unix.
17482  */
17483 void
17484 Attention (ChessProgramState *cps)
17485 {
17486 #if ATTENTION
17487     if (!cps->useSigint) return;
17488     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17489     switch (gameMode) {
17490       case MachinePlaysWhite:
17491       case MachinePlaysBlack:
17492       case TwoMachinesPlay:
17493       case IcsPlayingWhite:
17494       case IcsPlayingBlack:
17495       case AnalyzeMode:
17496       case AnalyzeFile:
17497         /* Skip if we know it isn't thinking */
17498         if (!cps->maybeThinking) return;
17499         if (appData.debugMode)
17500           fprintf(debugFP, "Interrupting %s\n", cps->which);
17501         InterruptChildProcess(cps->pr);
17502         cps->maybeThinking = FALSE;
17503         break;
17504       default:
17505         break;
17506     }
17507 #endif /*ATTENTION*/
17508 }
17509
17510 int
17511 CheckFlags ()
17512 {
17513     if (whiteTimeRemaining <= 0) {
17514         if (!whiteFlag) {
17515             whiteFlag = TRUE;
17516             if (appData.icsActive) {
17517                 if (appData.autoCallFlag &&
17518                     gameMode == IcsPlayingBlack && !blackFlag) {
17519                   SendToICS(ics_prefix);
17520                   SendToICS("flag\n");
17521                 }
17522             } else {
17523                 if (blackFlag) {
17524                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17525                 } else {
17526                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17527                     if (appData.autoCallFlag) {
17528                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17529                         return TRUE;
17530                     }
17531                 }
17532             }
17533         }
17534     }
17535     if (blackTimeRemaining <= 0) {
17536         if (!blackFlag) {
17537             blackFlag = TRUE;
17538             if (appData.icsActive) {
17539                 if (appData.autoCallFlag &&
17540                     gameMode == IcsPlayingWhite && !whiteFlag) {
17541                   SendToICS(ics_prefix);
17542                   SendToICS("flag\n");
17543                 }
17544             } else {
17545                 if (whiteFlag) {
17546                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17547                 } else {
17548                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17549                     if (appData.autoCallFlag) {
17550                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17551                         return TRUE;
17552                     }
17553                 }
17554             }
17555         }
17556     }
17557     return FALSE;
17558 }
17559
17560 void
17561 CheckTimeControl ()
17562 {
17563     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17564         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17565
17566     /*
17567      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17568      */
17569     if ( !WhiteOnMove(forwardMostMove) ) {
17570         /* White made time control */
17571         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17572         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17573         /* [HGM] time odds: correct new time quota for time odds! */
17574                                             / WhitePlayer()->timeOdds;
17575         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17576     } else {
17577         lastBlack -= blackTimeRemaining;
17578         /* Black made time control */
17579         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17580                                             / WhitePlayer()->other->timeOdds;
17581         lastWhite = whiteTimeRemaining;
17582     }
17583 }
17584
17585 void
17586 DisplayBothClocks ()
17587 {
17588     int wom = gameMode == EditPosition ?
17589       !blackPlaysFirst : WhiteOnMove(currentMove);
17590     DisplayWhiteClock(whiteTimeRemaining, wom);
17591     DisplayBlackClock(blackTimeRemaining, !wom);
17592 }
17593
17594
17595 /* Timekeeping seems to be a portability nightmare.  I think everyone
17596    has ftime(), but I'm really not sure, so I'm including some ifdefs
17597    to use other calls if you don't.  Clocks will be less accurate if
17598    you have neither ftime nor gettimeofday.
17599 */
17600
17601 /* VS 2008 requires the #include outside of the function */
17602 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17603 #include <sys/timeb.h>
17604 #endif
17605
17606 /* Get the current time as a TimeMark */
17607 void
17608 GetTimeMark (TimeMark *tm)
17609 {
17610 #if HAVE_GETTIMEOFDAY
17611
17612     struct timeval timeVal;
17613     struct timezone timeZone;
17614
17615     gettimeofday(&timeVal, &timeZone);
17616     tm->sec = (long) timeVal.tv_sec;
17617     tm->ms = (int) (timeVal.tv_usec / 1000L);
17618
17619 #else /*!HAVE_GETTIMEOFDAY*/
17620 #if HAVE_FTIME
17621
17622 // include <sys/timeb.h> / moved to just above start of function
17623     struct timeb timeB;
17624
17625     ftime(&timeB);
17626     tm->sec = (long) timeB.time;
17627     tm->ms = (int) timeB.millitm;
17628
17629 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17630     tm->sec = (long) time(NULL);
17631     tm->ms = 0;
17632 #endif
17633 #endif
17634 }
17635
17636 /* Return the difference in milliseconds between two
17637    time marks.  We assume the difference will fit in a long!
17638 */
17639 long
17640 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17641 {
17642     return 1000L*(tm2->sec - tm1->sec) +
17643            (long) (tm2->ms - tm1->ms);
17644 }
17645
17646
17647 /*
17648  * Code to manage the game clocks.
17649  *
17650  * In tournament play, black starts the clock and then white makes a move.
17651  * We give the human user a slight advantage if he is playing white---the
17652  * clocks don't run until he makes his first move, so it takes zero time.
17653  * Also, we don't account for network lag, so we could get out of sync
17654  * with GNU Chess's clock -- but then, referees are always right.
17655  */
17656
17657 static TimeMark tickStartTM;
17658 static long intendedTickLength;
17659
17660 long
17661 NextTickLength (long timeRemaining)
17662 {
17663     long nominalTickLength, nextTickLength;
17664
17665     if (timeRemaining > 0L && timeRemaining <= 10000L)
17666       nominalTickLength = 100L;
17667     else
17668       nominalTickLength = 1000L;
17669     nextTickLength = timeRemaining % nominalTickLength;
17670     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17671
17672     return nextTickLength;
17673 }
17674
17675 /* Adjust clock one minute up or down */
17676 void
17677 AdjustClock (Boolean which, int dir)
17678 {
17679     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17680     if(which) blackTimeRemaining += 60000*dir;
17681     else      whiteTimeRemaining += 60000*dir;
17682     DisplayBothClocks();
17683     adjustedClock = TRUE;
17684 }
17685
17686 /* Stop clocks and reset to a fresh time control */
17687 void
17688 ResetClocks ()
17689 {
17690     (void) StopClockTimer();
17691     if (appData.icsActive) {
17692         whiteTimeRemaining = blackTimeRemaining = 0;
17693     } else if (searchTime) {
17694         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17695         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17696     } else { /* [HGM] correct new time quote for time odds */
17697         whiteTC = blackTC = fullTimeControlString;
17698         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17699         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17700     }
17701     if (whiteFlag || blackFlag) {
17702         DisplayTitle("");
17703         whiteFlag = blackFlag = FALSE;
17704     }
17705     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17706     DisplayBothClocks();
17707     adjustedClock = FALSE;
17708 }
17709
17710 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17711
17712 /* Decrement running clock by amount of time that has passed */
17713 void
17714 DecrementClocks ()
17715 {
17716     long timeRemaining;
17717     long lastTickLength, fudge;
17718     TimeMark now;
17719
17720     if (!appData.clockMode) return;
17721     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17722
17723     GetTimeMark(&now);
17724
17725     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17726
17727     /* Fudge if we woke up a little too soon */
17728     fudge = intendedTickLength - lastTickLength;
17729     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17730
17731     if (WhiteOnMove(forwardMostMove)) {
17732         if(whiteNPS >= 0) lastTickLength = 0;
17733         timeRemaining = whiteTimeRemaining -= lastTickLength;
17734         if(timeRemaining < 0 && !appData.icsActive) {
17735             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17736             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17737                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17738                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17739             }
17740         }
17741         DisplayWhiteClock(whiteTimeRemaining - fudge,
17742                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17743     } else {
17744         if(blackNPS >= 0) lastTickLength = 0;
17745         timeRemaining = blackTimeRemaining -= lastTickLength;
17746         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17747             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17748             if(suddenDeath) {
17749                 blackStartMove = forwardMostMove;
17750                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17751             }
17752         }
17753         DisplayBlackClock(blackTimeRemaining - fudge,
17754                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17755     }
17756     if (CheckFlags()) return;
17757
17758     if(twoBoards) { // count down secondary board's clocks as well
17759         activePartnerTime -= lastTickLength;
17760         partnerUp = 1;
17761         if(activePartner == 'W')
17762             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17763         else
17764             DisplayBlackClock(activePartnerTime, TRUE);
17765         partnerUp = 0;
17766     }
17767
17768     tickStartTM = now;
17769     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17770     StartClockTimer(intendedTickLength);
17771
17772     /* if the time remaining has fallen below the alarm threshold, sound the
17773      * alarm. if the alarm has sounded and (due to a takeback or time control
17774      * with increment) the time remaining has increased to a level above the
17775      * threshold, reset the alarm so it can sound again.
17776      */
17777
17778     if (appData.icsActive && appData.icsAlarm) {
17779
17780         /* make sure we are dealing with the user's clock */
17781         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17782                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17783            )) return;
17784
17785         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17786             alarmSounded = FALSE;
17787         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17788             PlayAlarmSound();
17789             alarmSounded = TRUE;
17790         }
17791     }
17792 }
17793
17794
17795 /* A player has just moved, so stop the previously running
17796    clock and (if in clock mode) start the other one.
17797    We redisplay both clocks in case we're in ICS mode, because
17798    ICS gives us an update to both clocks after every move.
17799    Note that this routine is called *after* forwardMostMove
17800    is updated, so the last fractional tick must be subtracted
17801    from the color that is *not* on move now.
17802 */
17803 void
17804 SwitchClocks (int newMoveNr)
17805 {
17806     long lastTickLength;
17807     TimeMark now;
17808     int flagged = FALSE;
17809
17810     GetTimeMark(&now);
17811
17812     if (StopClockTimer() && appData.clockMode) {
17813         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17814         if (!WhiteOnMove(forwardMostMove)) {
17815             if(blackNPS >= 0) lastTickLength = 0;
17816             blackTimeRemaining -= lastTickLength;
17817            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17818 //         if(pvInfoList[forwardMostMove].time == -1)
17819                  pvInfoList[forwardMostMove].time =               // use GUI time
17820                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17821         } else {
17822            if(whiteNPS >= 0) lastTickLength = 0;
17823            whiteTimeRemaining -= lastTickLength;
17824            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17825 //         if(pvInfoList[forwardMostMove].time == -1)
17826                  pvInfoList[forwardMostMove].time =
17827                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17828         }
17829         flagged = CheckFlags();
17830     }
17831     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17832     CheckTimeControl();
17833
17834     if (flagged || !appData.clockMode) return;
17835
17836     switch (gameMode) {
17837       case MachinePlaysBlack:
17838       case MachinePlaysWhite:
17839       case BeginningOfGame:
17840         if (pausing) return;
17841         break;
17842
17843       case EditGame:
17844       case PlayFromGameFile:
17845       case IcsExamining:
17846         return;
17847
17848       default:
17849         break;
17850     }
17851
17852     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17853         if(WhiteOnMove(forwardMostMove))
17854              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17855         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17856     }
17857
17858     tickStartTM = now;
17859     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17860       whiteTimeRemaining : blackTimeRemaining);
17861     StartClockTimer(intendedTickLength);
17862 }
17863
17864
17865 /* Stop both clocks */
17866 void
17867 StopClocks ()
17868 {
17869     long lastTickLength;
17870     TimeMark now;
17871
17872     if (!StopClockTimer()) return;
17873     if (!appData.clockMode) return;
17874
17875     GetTimeMark(&now);
17876
17877     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17878     if (WhiteOnMove(forwardMostMove)) {
17879         if(whiteNPS >= 0) lastTickLength = 0;
17880         whiteTimeRemaining -= lastTickLength;
17881         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17882     } else {
17883         if(blackNPS >= 0) lastTickLength = 0;
17884         blackTimeRemaining -= lastTickLength;
17885         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17886     }
17887     CheckFlags();
17888 }
17889
17890 /* Start clock of player on move.  Time may have been reset, so
17891    if clock is already running, stop and restart it. */
17892 void
17893 StartClocks ()
17894 {
17895     (void) StopClockTimer(); /* in case it was running already */
17896     DisplayBothClocks();
17897     if (CheckFlags()) return;
17898
17899     if (!appData.clockMode) return;
17900     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17901
17902     GetTimeMark(&tickStartTM);
17903     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17904       whiteTimeRemaining : blackTimeRemaining);
17905
17906    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17907     whiteNPS = blackNPS = -1;
17908     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17909        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17910         whiteNPS = first.nps;
17911     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17912        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17913         blackNPS = first.nps;
17914     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17915         whiteNPS = second.nps;
17916     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17917         blackNPS = second.nps;
17918     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17919
17920     StartClockTimer(intendedTickLength);
17921 }
17922
17923 char *
17924 TimeString (long ms)
17925 {
17926     long second, minute, hour, day;
17927     char *sign = "";
17928     static char buf[32];
17929
17930     if (ms > 0 && ms <= 9900) {
17931       /* convert milliseconds to tenths, rounding up */
17932       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17933
17934       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17935       return buf;
17936     }
17937
17938     /* convert milliseconds to seconds, rounding up */
17939     /* use floating point to avoid strangeness of integer division
17940        with negative dividends on many machines */
17941     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17942
17943     if (second < 0) {
17944         sign = "-";
17945         second = -second;
17946     }
17947
17948     day = second / (60 * 60 * 24);
17949     second = second % (60 * 60 * 24);
17950     hour = second / (60 * 60);
17951     second = second % (60 * 60);
17952     minute = second / 60;
17953     second = second % 60;
17954
17955     if (day > 0)
17956       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17957               sign, day, hour, minute, second);
17958     else if (hour > 0)
17959       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17960     else
17961       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17962
17963     return buf;
17964 }
17965
17966
17967 /*
17968  * This is necessary because some C libraries aren't ANSI C compliant yet.
17969  */
17970 char *
17971 StrStr (char *string, char *match)
17972 {
17973     int i, length;
17974
17975     length = strlen(match);
17976
17977     for (i = strlen(string) - length; i >= 0; i--, string++)
17978       if (!strncmp(match, string, length))
17979         return string;
17980
17981     return NULL;
17982 }
17983
17984 char *
17985 StrCaseStr (char *string, char *match)
17986 {
17987     int i, j, length;
17988
17989     length = strlen(match);
17990
17991     for (i = strlen(string) - length; i >= 0; i--, string++) {
17992         for (j = 0; j < length; j++) {
17993             if (ToLower(match[j]) != ToLower(string[j]))
17994               break;
17995         }
17996         if (j == length) return string;
17997     }
17998
17999     return NULL;
18000 }
18001
18002 #ifndef _amigados
18003 int
18004 StrCaseCmp (char *s1, char *s2)
18005 {
18006     char c1, c2;
18007
18008     for (;;) {
18009         c1 = ToLower(*s1++);
18010         c2 = ToLower(*s2++);
18011         if (c1 > c2) return 1;
18012         if (c1 < c2) return -1;
18013         if (c1 == NULLCHAR) return 0;
18014     }
18015 }
18016
18017
18018 int
18019 ToLower (int c)
18020 {
18021     return isupper(c) ? tolower(c) : c;
18022 }
18023
18024
18025 int
18026 ToUpper (int c)
18027 {
18028     return islower(c) ? toupper(c) : c;
18029 }
18030 #endif /* !_amigados    */
18031
18032 char *
18033 StrSave (char *s)
18034 {
18035   char *ret;
18036
18037   if ((ret = (char *) malloc(strlen(s) + 1)))
18038     {
18039       safeStrCpy(ret, s, strlen(s)+1);
18040     }
18041   return ret;
18042 }
18043
18044 char *
18045 StrSavePtr (char *s, char **savePtr)
18046 {
18047     if (*savePtr) {
18048         free(*savePtr);
18049     }
18050     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18051       safeStrCpy(*savePtr, s, strlen(s)+1);
18052     }
18053     return(*savePtr);
18054 }
18055
18056 char *
18057 PGNDate ()
18058 {
18059     time_t clock;
18060     struct tm *tm;
18061     char buf[MSG_SIZ];
18062
18063     clock = time((time_t *)NULL);
18064     tm = localtime(&clock);
18065     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18066             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18067     return StrSave(buf);
18068 }
18069
18070
18071 char *
18072 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18073 {
18074     int i, j, fromX, fromY, toX, toY;
18075     int whiteToPlay, haveRights = nrCastlingRights;
18076     char buf[MSG_SIZ];
18077     char *p, *q;
18078     int emptycount;
18079     ChessSquare piece;
18080
18081     whiteToPlay = (gameMode == EditPosition) ?
18082       !blackPlaysFirst : (move % 2 == 0);
18083     p = buf;
18084
18085     /* Piece placement data */
18086     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18087         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18088         emptycount = 0;
18089         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18090             if (boards[move][i][j] == EmptySquare) {
18091                 emptycount++;
18092             } else { ChessSquare piece = boards[move][i][j];
18093                 if (emptycount > 0) {
18094                     if(emptycount<10) /* [HGM] can be >= 10 */
18095                         *p++ = '0' + emptycount;
18096                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18097                     emptycount = 0;
18098                 }
18099                 if(PieceToChar(piece) == '+') {
18100                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18101                     *p++ = '+';
18102                     piece = (ChessSquare)(CHUDEMOTED(piece));
18103                 }
18104                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18105                 if(*p = PieceSuffix(piece)) p++;
18106                 if(p[-1] == '~') {
18107                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18108                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18109                     *p++ = '~';
18110                 }
18111             }
18112         }
18113         if (emptycount > 0) {
18114             if(emptycount<10) /* [HGM] can be >= 10 */
18115                 *p++ = '0' + emptycount;
18116             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18117             emptycount = 0;
18118         }
18119         *p++ = '/';
18120     }
18121     *(p - 1) = ' ';
18122
18123     /* [HGM] print Crazyhouse or Shogi holdings */
18124     if( gameInfo.holdingsWidth ) {
18125         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18126         q = p;
18127         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18128             piece = boards[move][i][BOARD_WIDTH-1];
18129             if( piece != EmptySquare )
18130               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18131                   *p++ = PieceToChar(piece);
18132         }
18133         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18134             piece = boards[move][BOARD_HEIGHT-i-1][0];
18135             if( piece != EmptySquare )
18136               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18137                   *p++ = PieceToChar(piece);
18138         }
18139
18140         if( q == p ) *p++ = '-';
18141         *p++ = ']';
18142         *p++ = ' ';
18143     }
18144
18145     /* Active color */
18146     *p++ = whiteToPlay ? 'w' : 'b';
18147     *p++ = ' ';
18148
18149   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18150     haveRights = 0; q = p;
18151     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18152       piece = boards[move][0][i];
18153       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18154         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18155       }
18156     }
18157     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18158       piece = boards[move][BOARD_HEIGHT-1][i];
18159       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18160         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18161       }
18162     }
18163     if(p == q) *p++ = '-';
18164     *p++ = ' ';
18165   }
18166
18167   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18168     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18169   } else {
18170   if(haveRights) {
18171      int handW=0, handB=0;
18172      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18173         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18174         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18175      }
18176      q = p;
18177      if(appData.fischerCastling) {
18178         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18179            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18180                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18181         } else {
18182        /* [HGM] write directly from rights */
18183            if(boards[move][CASTLING][2] != NoRights &&
18184               boards[move][CASTLING][0] != NoRights   )
18185                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18186            if(boards[move][CASTLING][2] != NoRights &&
18187               boards[move][CASTLING][1] != NoRights   )
18188                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18189         }
18190         if(handB) {
18191            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18192                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18193         } else {
18194            if(boards[move][CASTLING][5] != NoRights &&
18195               boards[move][CASTLING][3] != NoRights   )
18196                 *p++ = boards[move][CASTLING][3] + AAA;
18197            if(boards[move][CASTLING][5] != NoRights &&
18198               boards[move][CASTLING][4] != NoRights   )
18199                 *p++ = boards[move][CASTLING][4] + AAA;
18200         }
18201      } else {
18202
18203         /* [HGM] write true castling rights */
18204         if( nrCastlingRights == 6 ) {
18205             int q, k=0;
18206             if(boards[move][CASTLING][0] != NoRights &&
18207                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18208             q = (boards[move][CASTLING][1] != NoRights &&
18209                  boards[move][CASTLING][2] != NoRights  );
18210             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18211                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18212                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18213                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18214             }
18215             if(q) *p++ = 'Q';
18216             k = 0;
18217             if(boards[move][CASTLING][3] != NoRights &&
18218                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18219             q = (boards[move][CASTLING][4] != NoRights &&
18220                  boards[move][CASTLING][5] != NoRights  );
18221             if(handB) {
18222                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18223                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18224                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18225             }
18226             if(q) *p++ = 'q';
18227         }
18228      }
18229      if (q == p) *p++ = '-'; /* No castling rights */
18230      *p++ = ' ';
18231   }
18232
18233   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18234      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18235      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18236     /* En passant target square */
18237     if (move > backwardMostMove) {
18238         fromX = moveList[move - 1][0] - AAA;
18239         fromY = moveList[move - 1][1] - ONE;
18240         toX = moveList[move - 1][2] - AAA;
18241         toY = moveList[move - 1][3] - ONE;
18242         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18243             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18244             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18245             fromX == toX) {
18246             /* 2-square pawn move just happened */
18247             *p++ = toX + AAA;
18248             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18249         } else {
18250             *p++ = '-';
18251         }
18252     } else if(move == backwardMostMove) {
18253         // [HGM] perhaps we should always do it like this, and forget the above?
18254         if((signed char)boards[move][EP_STATUS] >= 0) {
18255             *p++ = boards[move][EP_STATUS] + AAA;
18256             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18257         } else {
18258             *p++ = '-';
18259         }
18260     } else {
18261         *p++ = '-';
18262     }
18263     *p++ = ' ';
18264   }
18265   }
18266
18267     if(moveCounts)
18268     {   int i = 0, j=move;
18269
18270         /* [HGM] find reversible plies */
18271         if (appData.debugMode) { int k;
18272             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18273             for(k=backwardMostMove; k<=forwardMostMove; k++)
18274                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18275
18276         }
18277
18278         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18279         if( j == backwardMostMove ) i += initialRulePlies;
18280         sprintf(p, "%d ", i);
18281         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18282
18283         /* Fullmove number */
18284         sprintf(p, "%d", (move / 2) + 1);
18285     } else *--p = NULLCHAR;
18286
18287     return StrSave(buf);
18288 }
18289
18290 Boolean
18291 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18292 {
18293     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18294     char *p, c;
18295     int emptycount, virgin[BOARD_FILES];
18296     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18297
18298     p = fen;
18299
18300     /* Piece placement data */
18301     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18302         j = 0;
18303         for (;;) {
18304             if (*p == '/' || *p == ' ' || *p == '[' ) {
18305                 if(j > w) w = j;
18306                 emptycount = gameInfo.boardWidth - j;
18307                 while (emptycount--)
18308                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18309                 if (*p == '/') p++;
18310                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18311                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18312                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18313                     }
18314                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18315                 }
18316                 break;
18317 #if(BOARD_FILES >= 10)*0
18318             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18319                 p++; emptycount=10;
18320                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18321                 while (emptycount--)
18322                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18323 #endif
18324             } else if (*p == '*') {
18325                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18326             } else if (isdigit(*p)) {
18327                 emptycount = *p++ - '0';
18328                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18329                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18330                 while (emptycount--)
18331                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18332             } else if (*p == '<') {
18333                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18334                 else if (i != 0 || !shuffle) return FALSE;
18335                 p++;
18336             } else if (shuffle && *p == '>') {
18337                 p++; // for now ignore closing shuffle range, and assume rank-end
18338             } else if (*p == '?') {
18339                 if (j >= gameInfo.boardWidth) return FALSE;
18340                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18341                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18342             } else if (*p == '+' || isalpha(*p)) {
18343                 char *q, *s = SUFFIXES;
18344                 if (j >= gameInfo.boardWidth) return FALSE;
18345                 if(*p=='+') {
18346                     char c = *++p;
18347                     if(q = strchr(s, p[1])) p++;
18348                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18349                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18350                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18351                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18352                 } else {
18353                     char c = *p++;
18354                     if(q = strchr(s, *p)) p++;
18355                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18356                 }
18357
18358                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18359                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18360                     piece = (ChessSquare) (PROMOTED(piece));
18361                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18362                     p++;
18363                 }
18364                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18365                 if(piece == king) wKingRank = i;
18366                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18367             } else {
18368                 return FALSE;
18369             }
18370         }
18371     }
18372     while (*p == '/' || *p == ' ') p++;
18373
18374     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18375
18376     /* [HGM] by default clear Crazyhouse holdings, if present */
18377     if(gameInfo.holdingsWidth) {
18378        for(i=0; i<BOARD_HEIGHT; i++) {
18379            board[i][0]             = EmptySquare; /* black holdings */
18380            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18381            board[i][1]             = (ChessSquare) 0; /* black counts */
18382            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18383        }
18384     }
18385
18386     /* [HGM] look for Crazyhouse holdings here */
18387     while(*p==' ') p++;
18388     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18389         int swap=0, wcnt=0, bcnt=0;
18390         if(*p == '[') p++;
18391         if(*p == '<') swap++, p++;
18392         if(*p == '-' ) p++; /* empty holdings */ else {
18393             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18394             /* if we would allow FEN reading to set board size, we would   */
18395             /* have to add holdings and shift the board read so far here   */
18396             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18397                 p++;
18398                 if((int) piece >= (int) BlackPawn ) {
18399                     i = (int)piece - (int)BlackPawn;
18400                     i = PieceToNumber((ChessSquare)i);
18401                     if( i >= gameInfo.holdingsSize ) return FALSE;
18402                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18403                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18404                     bcnt++;
18405                 } else {
18406                     i = (int)piece - (int)WhitePawn;
18407                     i = PieceToNumber((ChessSquare)i);
18408                     if( i >= gameInfo.holdingsSize ) return FALSE;
18409                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18410                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18411                     wcnt++;
18412                 }
18413             }
18414             if(subst) { // substitute back-rank question marks by holdings pieces
18415                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18416                     int k, m, n = bcnt + 1;
18417                     if(board[0][j] == ClearBoard) {
18418                         if(!wcnt) return FALSE;
18419                         n = rand() % wcnt;
18420                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18421                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18422                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18423                             break;
18424                         }
18425                     }
18426                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18427                         if(!bcnt) return FALSE;
18428                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18429                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18430                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18431                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18432                             break;
18433                         }
18434                     }
18435                 }
18436                 subst = 0;
18437             }
18438         }
18439         if(*p == ']') p++;
18440     }
18441
18442     if(subst) return FALSE; // substitution requested, but no holdings
18443
18444     while(*p == ' ') p++;
18445
18446     /* Active color */
18447     c = *p++;
18448     if(appData.colorNickNames) {
18449       if( c == appData.colorNickNames[0] ) c = 'w'; else
18450       if( c == appData.colorNickNames[1] ) c = 'b';
18451     }
18452     switch (c) {
18453       case 'w':
18454         *blackPlaysFirst = FALSE;
18455         break;
18456       case 'b':
18457         *blackPlaysFirst = TRUE;
18458         break;
18459       default:
18460         return FALSE;
18461     }
18462
18463     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18464     /* return the extra info in global variiables             */
18465
18466     while(*p==' ') p++;
18467
18468     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18469         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18470         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18471     }
18472
18473     /* set defaults in case FEN is incomplete */
18474     board[EP_STATUS] = EP_UNKNOWN;
18475     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18476     for(i=0; i<nrCastlingRights; i++ ) {
18477         board[CASTLING][i] =
18478             appData.fischerCastling ? NoRights : initialRights[i];
18479     }   /* assume possible unless obviously impossible */
18480     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18481     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18482     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18483                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18484     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18485     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18486     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18487                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18488     FENrulePlies = 0;
18489
18490     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18491       char *q = p;
18492       int w=0, b=0;
18493       while(isalpha(*p)) {
18494         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18495         if(islower(*p)) b |= 1 << (*p++ - 'a');
18496       }
18497       if(*p == '-') p++;
18498       if(p != q) {
18499         board[TOUCHED_W] = ~w;
18500         board[TOUCHED_B] = ~b;
18501         while(*p == ' ') p++;
18502       }
18503     } else
18504
18505     if(nrCastlingRights) {
18506       int fischer = 0;
18507       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18508       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18509           /* castling indicator present, so default becomes no castlings */
18510           for(i=0; i<nrCastlingRights; i++ ) {
18511                  board[CASTLING][i] = NoRights;
18512           }
18513       }
18514       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18515              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18516              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18517              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18518         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18519
18520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18521             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18522             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18523         }
18524         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18525             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18526         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18527                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18528         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18529                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18530         switch(c) {
18531           case'K':
18532               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18533               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18534               board[CASTLING][2] = whiteKingFile;
18535               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18536               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18537               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18538               break;
18539           case'Q':
18540               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18541               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18542               board[CASTLING][2] = whiteKingFile;
18543               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18544               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18545               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18546               break;
18547           case'k':
18548               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18549               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18550               board[CASTLING][5] = blackKingFile;
18551               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18552               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18553               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18554               break;
18555           case'q':
18556               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18557               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18558               board[CASTLING][5] = blackKingFile;
18559               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18560               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18561               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18562           case '-':
18563               break;
18564           default: /* FRC castlings */
18565               if(c >= 'a') { /* black rights */
18566                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18567                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18568                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18569                   if(i == BOARD_RGHT) break;
18570                   board[CASTLING][5] = i;
18571                   c -= AAA;
18572                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18573                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18574                   if(c > i)
18575                       board[CASTLING][3] = c;
18576                   else
18577                       board[CASTLING][4] = c;
18578               } else { /* white rights */
18579                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18580                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18581                     if(board[0][i] == WhiteKing) break;
18582                   if(i == BOARD_RGHT) break;
18583                   board[CASTLING][2] = i;
18584                   c -= AAA - 'a' + 'A';
18585                   if(board[0][c] >= WhiteKing) break;
18586                   if(c > i)
18587                       board[CASTLING][0] = c;
18588                   else
18589                       board[CASTLING][1] = c;
18590               }
18591         }
18592       }
18593       for(i=0; i<nrCastlingRights; i++)
18594         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18595       if(gameInfo.variant == VariantSChess)
18596         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18597       if(fischer && shuffle) appData.fischerCastling = TRUE;
18598     if (appData.debugMode) {
18599         fprintf(debugFP, "FEN castling rights:");
18600         for(i=0; i<nrCastlingRights; i++)
18601         fprintf(debugFP, " %d", board[CASTLING][i]);
18602         fprintf(debugFP, "\n");
18603     }
18604
18605       while(*p==' ') p++;
18606     }
18607
18608     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18609
18610     /* read e.p. field in games that know e.p. capture */
18611     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18612        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18613        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18614       if(*p=='-') {
18615         p++; board[EP_STATUS] = EP_NONE;
18616       } else {
18617          char c = *p++ - AAA;
18618
18619          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18620          if(*p >= '0' && *p <='9') p++;
18621          board[EP_STATUS] = c;
18622       }
18623     }
18624
18625
18626     if(sscanf(p, "%d", &i) == 1) {
18627         FENrulePlies = i; /* 50-move ply counter */
18628         /* (The move number is still ignored)    */
18629     }
18630
18631     return TRUE;
18632 }
18633
18634 void
18635 EditPositionPasteFEN (char *fen)
18636 {
18637   if (fen != NULL) {
18638     Board initial_position;
18639
18640     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18641       DisplayError(_("Bad FEN position in clipboard"), 0);
18642       return ;
18643     } else {
18644       int savedBlackPlaysFirst = blackPlaysFirst;
18645       EditPositionEvent();
18646       blackPlaysFirst = savedBlackPlaysFirst;
18647       CopyBoard(boards[0], initial_position);
18648       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18649       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18650       DisplayBothClocks();
18651       DrawPosition(FALSE, boards[currentMove]);
18652     }
18653   }
18654 }
18655
18656 static char cseq[12] = "\\   ";
18657
18658 Boolean
18659 set_cont_sequence (char *new_seq)
18660 {
18661     int len;
18662     Boolean ret;
18663
18664     // handle bad attempts to set the sequence
18665         if (!new_seq)
18666                 return 0; // acceptable error - no debug
18667
18668     len = strlen(new_seq);
18669     ret = (len > 0) && (len < sizeof(cseq));
18670     if (ret)
18671       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18672     else if (appData.debugMode)
18673       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18674     return ret;
18675 }
18676
18677 /*
18678     reformat a source message so words don't cross the width boundary.  internal
18679     newlines are not removed.  returns the wrapped size (no null character unless
18680     included in source message).  If dest is NULL, only calculate the size required
18681     for the dest buffer.  lp argument indicats line position upon entry, and it's
18682     passed back upon exit.
18683 */
18684 int
18685 wrap (char *dest, char *src, int count, int width, int *lp)
18686 {
18687     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18688
18689     cseq_len = strlen(cseq);
18690     old_line = line = *lp;
18691     ansi = len = clen = 0;
18692
18693     for (i=0; i < count; i++)
18694     {
18695         if (src[i] == '\033')
18696             ansi = 1;
18697
18698         // if we hit the width, back up
18699         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18700         {
18701             // store i & len in case the word is too long
18702             old_i = i, old_len = len;
18703
18704             // find the end of the last word
18705             while (i && src[i] != ' ' && src[i] != '\n')
18706             {
18707                 i--;
18708                 len--;
18709             }
18710
18711             // word too long?  restore i & len before splitting it
18712             if ((old_i-i+clen) >= width)
18713             {
18714                 i = old_i;
18715                 len = old_len;
18716             }
18717
18718             // extra space?
18719             if (i && src[i-1] == ' ')
18720                 len--;
18721
18722             if (src[i] != ' ' && src[i] != '\n')
18723             {
18724                 i--;
18725                 if (len)
18726                     len--;
18727             }
18728
18729             // now append the newline and continuation sequence
18730             if (dest)
18731                 dest[len] = '\n';
18732             len++;
18733             if (dest)
18734                 strncpy(dest+len, cseq, cseq_len);
18735             len += cseq_len;
18736             line = cseq_len;
18737             clen = cseq_len;
18738             continue;
18739         }
18740
18741         if (dest)
18742             dest[len] = src[i];
18743         len++;
18744         if (!ansi)
18745             line++;
18746         if (src[i] == '\n')
18747             line = 0;
18748         if (src[i] == 'm')
18749             ansi = 0;
18750     }
18751     if (dest && appData.debugMode)
18752     {
18753         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18754             count, width, line, len, *lp);
18755         show_bytes(debugFP, src, count);
18756         fprintf(debugFP, "\ndest: ");
18757         show_bytes(debugFP, dest, len);
18758         fprintf(debugFP, "\n");
18759     }
18760     *lp = dest ? line : old_line;
18761
18762     return len;
18763 }
18764
18765 // [HGM] vari: routines for shelving variations
18766 Boolean modeRestore = FALSE;
18767
18768 void
18769 PushInner (int firstMove, int lastMove)
18770 {
18771         int i, j, nrMoves = lastMove - firstMove;
18772
18773         // push current tail of game on stack
18774         savedResult[storedGames] = gameInfo.result;
18775         savedDetails[storedGames] = gameInfo.resultDetails;
18776         gameInfo.resultDetails = NULL;
18777         savedFirst[storedGames] = firstMove;
18778         savedLast [storedGames] = lastMove;
18779         savedFramePtr[storedGames] = framePtr;
18780         framePtr -= nrMoves; // reserve space for the boards
18781         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18782             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18783             for(j=0; j<MOVE_LEN; j++)
18784                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18785             for(j=0; j<2*MOVE_LEN; j++)
18786                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18787             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18788             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18789             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18790             pvInfoList[firstMove+i-1].depth = 0;
18791             commentList[framePtr+i] = commentList[firstMove+i];
18792             commentList[firstMove+i] = NULL;
18793         }
18794
18795         storedGames++;
18796         forwardMostMove = firstMove; // truncate game so we can start variation
18797 }
18798
18799 void
18800 PushTail (int firstMove, int lastMove)
18801 {
18802         if(appData.icsActive) { // only in local mode
18803                 forwardMostMove = currentMove; // mimic old ICS behavior
18804                 return;
18805         }
18806         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18807
18808         PushInner(firstMove, lastMove);
18809         if(storedGames == 1) GreyRevert(FALSE);
18810         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18811 }
18812
18813 void
18814 PopInner (Boolean annotate)
18815 {
18816         int i, j, nrMoves;
18817         char buf[8000], moveBuf[20];
18818
18819         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18820         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18821         nrMoves = savedLast[storedGames] - currentMove;
18822         if(annotate) {
18823                 int cnt = 10;
18824                 if(!WhiteOnMove(currentMove))
18825                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18826                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18827                 for(i=currentMove; i<forwardMostMove; i++) {
18828                         if(WhiteOnMove(i))
18829                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18830                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18831                         strcat(buf, moveBuf);
18832                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18833                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18834                 }
18835                 strcat(buf, ")");
18836         }
18837         for(i=1; i<=nrMoves; i++) { // copy last variation back
18838             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18839             for(j=0; j<MOVE_LEN; j++)
18840                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18841             for(j=0; j<2*MOVE_LEN; j++)
18842                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18843             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18844             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18845             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18846             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18847             commentList[currentMove+i] = commentList[framePtr+i];
18848             commentList[framePtr+i] = NULL;
18849         }
18850         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18851         framePtr = savedFramePtr[storedGames];
18852         gameInfo.result = savedResult[storedGames];
18853         if(gameInfo.resultDetails != NULL) {
18854             free(gameInfo.resultDetails);
18855       }
18856         gameInfo.resultDetails = savedDetails[storedGames];
18857         forwardMostMove = currentMove + nrMoves;
18858 }
18859
18860 Boolean
18861 PopTail (Boolean annotate)
18862 {
18863         if(appData.icsActive) return FALSE; // only in local mode
18864         if(!storedGames) return FALSE; // sanity
18865         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18866
18867         PopInner(annotate);
18868         if(currentMove < forwardMostMove) ForwardEvent(); else
18869         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18870
18871         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18872         return TRUE;
18873 }
18874
18875 void
18876 CleanupTail ()
18877 {       // remove all shelved variations
18878         int i;
18879         for(i=0; i<storedGames; i++) {
18880             if(savedDetails[i])
18881                 free(savedDetails[i]);
18882             savedDetails[i] = NULL;
18883         }
18884         for(i=framePtr; i<MAX_MOVES; i++) {
18885                 if(commentList[i]) free(commentList[i]);
18886                 commentList[i] = NULL;
18887         }
18888         framePtr = MAX_MOVES-1;
18889         storedGames = 0;
18890 }
18891
18892 void
18893 LoadVariation (int index, char *text)
18894 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18895         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18896         int level = 0, move;
18897
18898         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18899         // first find outermost bracketing variation
18900         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18901             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18902                 if(*p == '{') wait = '}'; else
18903                 if(*p == '[') wait = ']'; else
18904                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18905                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18906             }
18907             if(*p == wait) wait = NULLCHAR; // closing ]} found
18908             p++;
18909         }
18910         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18911         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18912         end[1] = NULLCHAR; // clip off comment beyond variation
18913         ToNrEvent(currentMove-1);
18914         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18915         // kludge: use ParsePV() to append variation to game
18916         move = currentMove;
18917         ParsePV(start, TRUE, TRUE);
18918         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18919         ClearPremoveHighlights();
18920         CommentPopDown();
18921         ToNrEvent(currentMove+1);
18922 }
18923
18924 void
18925 LoadTheme ()
18926 {
18927     char *p, *q, buf[MSG_SIZ];
18928     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18929         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18930         ParseArgsFromString(buf);
18931         ActivateTheme(TRUE); // also redo colors
18932         return;
18933     }
18934     p = nickName;
18935     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18936     {
18937         int len;
18938         q = appData.themeNames;
18939         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18940       if(appData.useBitmaps) {
18941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18942                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18943                 appData.liteBackTextureMode,
18944                 appData.darkBackTextureMode );
18945       } else {
18946         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18947                 Col2Text(2),   // lightSquareColor
18948                 Col2Text(3) ); // darkSquareColor
18949       }
18950       if(appData.useBorder) {
18951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18952                 appData.border);
18953       } else {
18954         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18955       }
18956       if(appData.useFont) {
18957         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18958                 appData.renderPiecesWithFont,
18959                 appData.fontToPieceTable,
18960                 Col2Text(9),    // appData.fontBackColorWhite
18961                 Col2Text(10) ); // appData.fontForeColorBlack
18962       } else {
18963         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18964                 appData.pieceDirectory);
18965         if(!appData.pieceDirectory[0])
18966           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18967                 Col2Text(0),   // whitePieceColor
18968                 Col2Text(1) ); // blackPieceColor
18969       }
18970       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18971                 Col2Text(4),   // highlightSquareColor
18972                 Col2Text(5) ); // premoveHighlightColor
18973         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18974         if(insert != q) insert[-1] = NULLCHAR;
18975         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18976         if(q)   free(q);
18977     }
18978     ActivateTheme(FALSE);
18979 }