Prevent message text widening window GTK
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1000         return;
1001     }
1002     p = engineName;
1003     while(q = strchr(p, SLASH)) p = q+1;
1004     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005     if(engineDir[0] != NULLCHAR) {
1006         ASSIGN(appData.directory[i], engineDir); p = engineName;
1007     } else if(p != engineName) { // derive directory from engine path, when not given
1008         p[-1] = 0;
1009         ASSIGN(appData.directory[i], engineName);
1010         p[-1] = SLASH;
1011         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012     } else { ASSIGN(appData.directory[i], "."); }
1013     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014     if(params[0]) {
1015         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016         snprintf(command, MSG_SIZ, "%s %s", p, params);
1017         p = command;
1018     }
1019     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020     ASSIGN(appData.chessProgram[i], p);
1021     appData.isUCI[i] = isUCI;
1022     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023     appData.hasOwnBookUCI[i] = hasBook;
1024     if(!nickName[0]) useNick = FALSE;
1025     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1026     if(addToList) {
1027         int len;
1028         char quote;
1029         q = firstChessProgramNames;
1030         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033                         quote, p, quote, appData.directory[i],
1034                         useNick ? " -fn \"" : "",
1035                         useNick ? nickName : "",
1036                         useNick ? "\"" : "",
1037                         v1 ? " -firstProtocolVersion 1" : "",
1038                         hasBook ? "" : " -fNoOwnBookUCI",
1039                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040                         storeVariant ? " -variant " : "",
1041                         storeVariant ? VariantName(gameInfo.variant) : "");
1042         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044         if(insert != q) insert[-1] = NULLCHAR;
1045         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046         if(q)   free(q);
1047         FloatToFront(&appData.recentEngineList, buf);
1048     }
1049     ReplaceEngine(cps, i);
1050 }
1051
1052 void
1053 InitTimeControls ()
1054 {
1055     int matched, min, sec;
1056     /*
1057      * Parse timeControl resource
1058      */
1059     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060                           appData.movesPerSession)) {
1061         char buf[MSG_SIZ];
1062         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063         DisplayFatalError(buf, 0, 2);
1064     }
1065
1066     /*
1067      * Parse searchTime resource
1068      */
1069     if (*appData.searchTime != NULLCHAR) {
1070         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071         if (matched == 1) {
1072             searchTime = min * 60;
1073         } else if (matched == 2) {
1074             searchTime = min * 60 + sec;
1075         } else {
1076             char buf[MSG_SIZ];
1077             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078             DisplayFatalError(buf, 0, 2);
1079         }
1080     }
1081 }
1082
1083 void
1084 InitBackEnd1 ()
1085 {
1086
1087     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089
1090     GetTimeMark(&programStartTime);
1091     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092     appData.seedBase = random() + (random()<<15);
1093     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094
1095     ClearProgramStats();
1096     programStats.ok_to_send = 1;
1097     programStats.seen_stat = 0;
1098
1099     /*
1100      * Initialize game list
1101      */
1102     ListNew(&gameList);
1103
1104
1105     /*
1106      * Internet chess server status
1107      */
1108     if (appData.icsActive) {
1109         appData.matchMode = FALSE;
1110         appData.matchGames = 0;
1111 #if ZIPPY
1112         appData.noChessProgram = !appData.zippyPlay;
1113 #else
1114         appData.zippyPlay = FALSE;
1115         appData.zippyTalk = FALSE;
1116         appData.noChessProgram = TRUE;
1117 #endif
1118         if (*appData.icsHelper != NULLCHAR) {
1119             appData.useTelnet = TRUE;
1120             appData.telnetProgram = appData.icsHelper;
1121         }
1122     } else {
1123         appData.zippyTalk = appData.zippyPlay = FALSE;
1124     }
1125
1126     /* [AS] Initialize pv info list [HGM] and game state */
1127     {
1128         int i, j;
1129
1130         for( i=0; i<=framePtr; i++ ) {
1131             pvInfoList[i].depth = -1;
1132             boards[i][EP_STATUS] = EP_NONE;
1133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1134         }
1135     }
1136
1137     InitTimeControls();
1138
1139     /* [AS] Adjudication threshold */
1140     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141
1142     InitEngine(&first, 0);
1143     InitEngine(&second, 1);
1144     CommonEngineInit();
1145
1146     pairing.which = "pairing"; // pairing engine
1147     pairing.pr = NoProc;
1148     pairing.isr = NULL;
1149     pairing.program = appData.pairingEngine;
1150     pairing.host = "localhost";
1151     pairing.dir = ".";
1152
1153     if (appData.icsActive) {
1154         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1155     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156         appData.clockMode = FALSE;
1157         first.sendTime = second.sendTime = 0;
1158     }
1159
1160 #if ZIPPY
1161     /* Override some settings from environment variables, for backward
1162        compatibility.  Unfortunately it's not feasible to have the env
1163        vars just set defaults, at least in xboard.  Ugh.
1164     */
1165     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166       ZippyInit();
1167     }
1168 #endif
1169
1170     if (!appData.icsActive) {
1171       char buf[MSG_SIZ];
1172       int len;
1173
1174       /* Check for variants that are supported only in ICS mode,
1175          or not at all.  Some that are accepted here nevertheless
1176          have bugs; see comments below.
1177       */
1178       VariantClass variant = StringToVariant(appData.variant);
1179       switch (variant) {
1180       case VariantBughouse:     /* need four players and two boards */
1181       case VariantKriegspiel:   /* need to hide pieces and move details */
1182         /* case VariantFischeRandom: (Fabien: moved below) */
1183         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184         if( (len >= MSG_SIZ) && appData.debugMode )
1185           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186
1187         DisplayFatalError(buf, 0, 2);
1188         return;
1189
1190       case VariantUnknown:
1191       case VariantLoadable:
1192       case Variant29:
1193       case Variant30:
1194       case Variant31:
1195       case Variant32:
1196       case Variant33:
1197       case Variant34:
1198       case Variant35:
1199       case Variant36:
1200       default:
1201         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202         if( (len >= MSG_SIZ) && appData.debugMode )
1203           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204
1205         DisplayFatalError(buf, 0, 2);
1206         return;
1207
1208       case VariantNormal:     /* definitely works! */
1209         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211           return;
1212         }
1213       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1214       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1215       case VariantGothic:     /* [HGM] should work */
1216       case VariantCapablanca: /* [HGM] should work */
1217       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1218       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1219       case VariantChu:        /* [HGM] experimental */
1220       case VariantKnightmate: /* [HGM] should work */
1221       case VariantCylinder:   /* [HGM] untested */
1222       case VariantFalcon:     /* [HGM] untested */
1223       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224                                  offboard interposition not understood */
1225       case VariantWildCastle: /* pieces not automatically shuffled */
1226       case VariantNoCastle:   /* pieces not automatically shuffled */
1227       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228       case VariantLosers:     /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantSuicide:    /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantGiveaway:   /* should work except for win condition,
1233                                  and doesn't know captures are mandatory */
1234       case VariantTwoKings:   /* should work */
1235       case VariantAtomic:     /* should work except for win condition */
1236       case Variant3Check:     /* should work except for win condition */
1237       case VariantShatranj:   /* should work except for all win conditions */
1238       case VariantMakruk:     /* should work except for draw countdown */
1239       case VariantASEAN :     /* should work except for draw countdown */
1240       case VariantBerolina:   /* might work if TestLegality is off */
1241       case VariantCapaRandom: /* should work */
1242       case VariantJanus:      /* should work */
1243       case VariantSuper:      /* experimental */
1244       case VariantGreat:      /* experimental, requires legality testing to be off */
1245       case VariantSChess:     /* S-Chess, should work */
1246       case VariantGrand:      /* should work */
1247       case VariantSpartan:    /* should work */
1248       case VariantLion:       /* should work */
1249       case VariantChuChess:   /* should work */
1250         break;
1251       }
1252     }
1253
1254 }
1255
1256 int
1257 NextIntegerFromString (char ** str, long * value)
1258 {
1259     int result = -1;
1260     char * s = *str;
1261
1262     while( *s == ' ' || *s == '\t' ) {
1263         s++;
1264     }
1265
1266     *value = 0;
1267
1268     if( *s >= '0' && *s <= '9' ) {
1269         while( *s >= '0' && *s <= '9' ) {
1270             *value = *value * 10 + (*s - '0');
1271             s++;
1272         }
1273
1274         result = 0;
1275     }
1276
1277     *str = s;
1278
1279     return result;
1280 }
1281
1282 int
1283 NextTimeControlFromString (char ** str, long * value)
1284 {
1285     long temp;
1286     int result = NextIntegerFromString( str, &temp );
1287
1288     if( result == 0 ) {
1289         *value = temp * 60; /* Minutes */
1290         if( **str == ':' ) {
1291             (*str)++;
1292             result = NextIntegerFromString( str, &temp );
1293             *value += temp; /* Seconds */
1294         }
1295     }
1296
1297     return result;
1298 }
1299
1300 int
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303     int result = -1, type = 0; long temp, temp2;
1304
1305     if(**str != ':') return -1; // old params remain in force!
1306     (*str)++;
1307     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308     if( NextIntegerFromString( str, &temp ) ) return -1;
1309     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310
1311     if(**str != '/') {
1312         /* time only: incremental or sudden-death time control */
1313         if(**str == '+') { /* increment follows; read it */
1314             (*str)++;
1315             if(**str == '!') type = *(*str)++; // Bronstein TC
1316             if(result = NextIntegerFromString( str, &temp2)) return -1;
1317             *inc = temp2 * 1000;
1318             if(**str == '.') { // read fraction of increment
1319                 char *start = ++(*str);
1320                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321                 temp2 *= 1000;
1322                 while(start++ < *str) temp2 /= 10;
1323                 *inc += temp2;
1324             }
1325         } else *inc = 0;
1326         *moves = 0; *tc = temp * 1000; *incType = type;
1327         return 0;
1328     }
1329
1330     (*str)++; /* classical time control */
1331     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1332
1333     if(result == 0) {
1334         *moves = temp;
1335         *tc    = temp2 * 1000;
1336         *inc   = 0;
1337         *incType = type;
1338     }
1339     return result;
1340 }
1341
1342 int
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 {   /* [HGM] get time to add from the multi-session time-control string */
1345     int incType, moves=1; /* kludge to force reading of first session */
1346     long time, increment;
1347     char *s = tcString;
1348
1349     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350     do {
1351         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353         if(movenr == -1) return time;    /* last move before new session     */
1354         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356         if(!moves) return increment;     /* current session is incremental   */
1357         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358     } while(movenr >= -1);               /* try again for next session       */
1359
1360     return 0; // no new time quota on this move
1361 }
1362
1363 int
1364 ParseTimeControl (char *tc, float ti, int mps)
1365 {
1366   long tc1;
1367   long tc2;
1368   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369   int min, sec=0;
1370
1371   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1374   if(ti > 0) {
1375
1376     if(mps)
1377       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378     else
1379       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380   } else {
1381     if(mps)
1382       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383     else
1384       snprintf(buf, MSG_SIZ, ":%s", mytc);
1385   }
1386   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387
1388   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1389     return FALSE;
1390   }
1391
1392   if( *tc == '/' ) {
1393     /* Parse second time control */
1394     tc++;
1395
1396     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1397       return FALSE;
1398     }
1399
1400     if( tc2 == 0 ) {
1401       return FALSE;
1402     }
1403
1404     timeControl_2 = tc2 * 1000;
1405   }
1406   else {
1407     timeControl_2 = 0;
1408   }
1409
1410   if( tc1 == 0 ) {
1411     return FALSE;
1412   }
1413
1414   timeControl = tc1 * 1000;
1415
1416   if (ti >= 0) {
1417     timeIncrement = ti * 1000;  /* convert to ms */
1418     movesPerSession = 0;
1419   } else {
1420     timeIncrement = 0;
1421     movesPerSession = mps;
1422   }
1423   return TRUE;
1424 }
1425
1426 void
1427 InitBackEnd2 ()
1428 {
1429     if (appData.debugMode) {
1430 #    ifdef __GIT_VERSION
1431       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 #    else
1433       fprintf(debugFP, "Version: %s\n", programVersion);
1434 #    endif
1435     }
1436     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437
1438     set_cont_sequence(appData.wrapContSeq);
1439     if (appData.matchGames > 0) {
1440         appData.matchMode = TRUE;
1441     } else if (appData.matchMode) {
1442         appData.matchGames = 1;
1443     }
1444     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445         appData.matchGames = appData.sameColorGames;
1446     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449     }
1450     Reset(TRUE, FALSE);
1451     if (appData.noChessProgram || first.protocolVersion == 1) {
1452       InitBackEnd3();
1453     } else {
1454       /* kludge: allow timeout for initial "feature" commands */
1455       FreezeUI();
1456       DisplayMessage("", _("Starting chess program"));
1457       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1458     }
1459 }
1460
1461 int
1462 CalculateIndex (int index, int gameNr)
1463 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464     int res;
1465     if(index > 0) return index; // fixed nmber
1466     if(index == 0) return 1;
1467     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1469     return res;
1470 }
1471
1472 int
1473 LoadGameOrPosition (int gameNr)
1474 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475     if (*appData.loadGameFile != NULLCHAR) {
1476         if (!LoadGameFromFile(appData.loadGameFile,
1477                 CalculateIndex(appData.loadGameIndex, gameNr),
1478                               appData.loadGameFile, FALSE)) {
1479             DisplayFatalError(_("Bad game file"), 0, 1);
1480             return 0;
1481         }
1482     } else if (*appData.loadPositionFile != NULLCHAR) {
1483         if (!LoadPositionFromFile(appData.loadPositionFile,
1484                 CalculateIndex(appData.loadPositionIndex, gameNr),
1485                                   appData.loadPositionFile)) {
1486             DisplayFatalError(_("Bad position file"), 0, 1);
1487             return 0;
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 void
1494 ReserveGame (int gameNr, char resChar)
1495 {
1496     FILE *tf = fopen(appData.tourneyFile, "r+");
1497     char *p, *q, c, buf[MSG_SIZ];
1498     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499     safeStrCpy(buf, lastMsg, MSG_SIZ);
1500     DisplayMessage(_("Pick new game"), "");
1501     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502     ParseArgsFromFile(tf);
1503     p = q = appData.results;
1504     if(appData.debugMode) {
1505       char *r = appData.participants;
1506       fprintf(debugFP, "results = '%s'\n", p);
1507       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508       fprintf(debugFP, "\n");
1509     }
1510     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511     nextGame = q - p;
1512     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513     safeStrCpy(q, p, strlen(p) + 2);
1514     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518         q[nextGame] = '*';
1519     }
1520     fseek(tf, -(strlen(p)+4), SEEK_END);
1521     c = fgetc(tf);
1522     if(c != '"') // depending on DOS or Unix line endings we can be one off
1523          fseek(tf, -(strlen(p)+2), SEEK_END);
1524     else fseek(tf, -(strlen(p)+3), SEEK_END);
1525     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526     DisplayMessage(buf, "");
1527     free(p); appData.results = q;
1528     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530       int round = appData.defaultMatchGames * appData.tourneyType;
1531       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1532          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533         UnloadEngine(&first);  // next game belongs to other pairing;
1534         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535     }
1536     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1537 }
1538
1539 void
1540 MatchEvent (int mode)
1541 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542         int dummy;
1543         if(matchMode) { // already in match mode: switch it off
1544             abortMatch = TRUE;
1545             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546             return;
1547         }
1548 //      if(gameMode != BeginningOfGame) {
1549 //          DisplayError(_("You can only start a match from the initial position."), 0);
1550 //          return;
1551 //      }
1552         abortMatch = FALSE;
1553         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554         /* Set up machine vs. machine match */
1555         nextGame = 0;
1556         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557         if(appData.tourneyFile[0]) {
1558             ReserveGame(-1, 0);
1559             if(nextGame > appData.matchGames) {
1560                 char buf[MSG_SIZ];
1561                 if(strchr(appData.results, '*') == NULL) {
1562                     FILE *f;
1563                     appData.tourneyCycles++;
1564                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565                         fclose(f);
1566                         NextTourneyGame(-1, &dummy);
1567                         ReserveGame(-1, 0);
1568                         if(nextGame <= appData.matchGames) {
1569                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570                             matchMode = mode;
1571                             ScheduleDelayedEvent(NextMatchGame, 10000);
1572                             return;
1573                         }
1574                     }
1575                 }
1576                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577                 DisplayError(buf, 0);
1578                 appData.tourneyFile[0] = 0;
1579                 return;
1580             }
1581         } else
1582         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1583             DisplayFatalError(_("Can't have a match with no chess programs"),
1584                               0, 2);
1585             return;
1586         }
1587         matchMode = mode;
1588         matchGame = roundNr = 1;
1589         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1590         NextMatchGame();
1591 }
1592
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594
1595 void
1596 InitBackEnd3 P((void))
1597 {
1598     GameMode initialMode;
1599     char buf[MSG_SIZ];
1600     int err, len;
1601
1602     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1603        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1604         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1605        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1606        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607         char c, *q = first.variants, *p = strchr(q, ',');
1608         if(p) *p = NULLCHAR;
1609         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610             int w, h, s;
1611             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614             Reset(TRUE, FALSE);         // and re-initialize
1615         }
1616         if(p) *p = ',';
1617     }
1618
1619     InitChessProgram(&first, startedFromSetupPosition);
1620
1621     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1622         free(programVersion);
1623         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626     }
1627
1628     if (appData.icsActive) {
1629 #ifdef WIN32
1630         /* [DM] Make a console window if needed [HGM] merged ifs */
1631         ConsoleCreate();
1632 #endif
1633         err = establish();
1634         if (err != 0)
1635           {
1636             if (*appData.icsCommPort != NULLCHAR)
1637               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638                              appData.icsCommPort);
1639             else
1640               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641                         appData.icsHost, appData.icsPort);
1642
1643             if( (len >= MSG_SIZ) && appData.debugMode )
1644               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645
1646             DisplayFatalError(buf, err, 1);
1647             return;
1648         }
1649         SetICSMode();
1650         telnetISR =
1651           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652         fromUserISR =
1653           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656     } else if (appData.noChessProgram) {
1657         SetNCPMode();
1658     } else {
1659         SetGNUMode();
1660     }
1661
1662     if (*appData.cmailGameName != NULLCHAR) {
1663         SetCmailMode();
1664         OpenLoopback(&cmailPR);
1665         cmailISR =
1666           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1667     }
1668
1669     ThawUI();
1670     DisplayMessage("", "");
1671     if (StrCaseCmp(appData.initialMode, "") == 0) {
1672       initialMode = BeginningOfGame;
1673       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677         ModeHighlight();
1678       }
1679     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680       initialMode = TwoMachinesPlay;
1681     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682       initialMode = AnalyzeFile;
1683     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684       initialMode = AnalyzeMode;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686       initialMode = MachinePlaysWhite;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688       initialMode = MachinePlaysBlack;
1689     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690       initialMode = EditGame;
1691     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692       initialMode = EditPosition;
1693     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694       initialMode = Training;
1695     } else {
1696       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697       if( (len >= MSG_SIZ) && appData.debugMode )
1698         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699
1700       DisplayFatalError(buf, 0, 2);
1701       return;
1702     }
1703
1704     if (appData.matchMode) {
1705         if(appData.tourneyFile[0]) { // start tourney from command line
1706             FILE *f;
1707             if(f = fopen(appData.tourneyFile, "r")) {
1708                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709                 fclose(f);
1710                 appData.clockMode = TRUE;
1711                 SetGNUMode();
1712             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713         }
1714         MatchEvent(TRUE);
1715     } else if (*appData.cmailGameName != NULLCHAR) {
1716         /* Set up cmail mode */
1717         ReloadCmailMsgEvent(TRUE);
1718     } else {
1719         /* Set up other modes */
1720         if (initialMode == AnalyzeFile) {
1721           if (*appData.loadGameFile == NULLCHAR) {
1722             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1723             return;
1724           }
1725         }
1726         if (*appData.loadGameFile != NULLCHAR) {
1727             (void) LoadGameFromFile(appData.loadGameFile,
1728                                     appData.loadGameIndex,
1729                                     appData.loadGameFile, TRUE);
1730         } else if (*appData.loadPositionFile != NULLCHAR) {
1731             (void) LoadPositionFromFile(appData.loadPositionFile,
1732                                         appData.loadPositionIndex,
1733                                         appData.loadPositionFile);
1734             /* [HGM] try to make self-starting even after FEN load */
1735             /* to allow automatic setup of fairy variants with wtm */
1736             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737                 gameMode = BeginningOfGame;
1738                 setboardSpoiledMachineBlack = 1;
1739             }
1740             /* [HGM] loadPos: make that every new game uses the setup */
1741             /* from file as long as we do not switch variant          */
1742             if(!blackPlaysFirst) {
1743                 startedFromPositionFile = TRUE;
1744                 CopyBoard(filePosition, boards[0]);
1745                 CopyBoard(initialPosition, boards[0]);
1746             }
1747         } else if(*appData.fen != NULLCHAR) {
1748             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749                 startedFromPositionFile = TRUE;
1750                 Reset(TRUE, TRUE);
1751             }
1752         }
1753         if (initialMode == AnalyzeMode) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1756             return;
1757           }
1758           if (appData.icsActive) {
1759             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1760             return;
1761           }
1762           AnalyzeModeEvent();
1763         } else if (initialMode == AnalyzeFile) {
1764           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765           ShowThinkingEvent();
1766           AnalyzeFileEvent();
1767           AnalysisPeriodicEvent(1);
1768         } else if (initialMode == MachinePlaysWhite) {
1769           if (appData.noChessProgram) {
1770             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1771                               0, 2);
1772             return;
1773           }
1774           if (appData.icsActive) {
1775             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1776                               0, 2);
1777             return;
1778           }
1779           MachineWhiteEvent();
1780         } else if (initialMode == MachinePlaysBlack) {
1781           if (appData.noChessProgram) {
1782             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1783                               0, 2);
1784             return;
1785           }
1786           if (appData.icsActive) {
1787             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1788                               0, 2);
1789             return;
1790           }
1791           MachineBlackEvent();
1792         } else if (initialMode == TwoMachinesPlay) {
1793           if (appData.noChessProgram) {
1794             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1795                               0, 2);
1796             return;
1797           }
1798           if (appData.icsActive) {
1799             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1800                               0, 2);
1801             return;
1802           }
1803           TwoMachinesEvent();
1804         } else if (initialMode == EditGame) {
1805           EditGameEvent();
1806         } else if (initialMode == EditPosition) {
1807           EditPositionEvent();
1808         } else if (initialMode == Training) {
1809           if (*appData.loadGameFile == NULLCHAR) {
1810             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811             return;
1812           }
1813           TrainingEvent();
1814         }
1815     }
1816 }
1817
1818 void
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 {
1821     DisplayBook(current+1);
1822
1823     MoveHistorySet( movelist, first, last, current, pvInfoList );
1824
1825     EvalGraphSet( first, last, current, pvInfoList );
1826
1827     MakeEngineOutputTitle();
1828 }
1829
1830 /*
1831  * Establish will establish a contact to a remote host.port.
1832  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833  *  used to talk to the host.
1834  * Returns 0 if okay, error code if not.
1835  */
1836 int
1837 establish ()
1838 {
1839     char buf[MSG_SIZ];
1840
1841     if (*appData.icsCommPort != NULLCHAR) {
1842         /* Talk to the host through a serial comm port */
1843         return OpenCommPort(appData.icsCommPort, &icsPR);
1844
1845     } else if (*appData.gateway != NULLCHAR) {
1846         if (*appData.remoteShell == NULLCHAR) {
1847             /* Use the rcmd protocol to run telnet program on a gateway host */
1848             snprintf(buf, sizeof(buf), "%s %s %s",
1849                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1850             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1851
1852         } else {
1853             /* Use the rsh program to run telnet program on a gateway host */
1854             if (*appData.remoteUser == NULLCHAR) {
1855                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856                         appData.gateway, appData.telnetProgram,
1857                         appData.icsHost, appData.icsPort);
1858             } else {
1859                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860                         appData.remoteShell, appData.gateway,
1861                         appData.remoteUser, appData.telnetProgram,
1862                         appData.icsHost, appData.icsPort);
1863             }
1864             return StartChildProcess(buf, "", &icsPR);
1865
1866         }
1867     } else if (appData.useTelnet) {
1868         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1869
1870     } else {
1871         /* TCP socket interface differs somewhat between
1872            Unix and NT; handle details in the front end.
1873            */
1874         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1875     }
1876 }
1877
1878 void
1879 EscapeExpand (char *p, char *q)
1880 {       // [HGM] initstring: routine to shape up string arguments
1881         while(*p++ = *q++) if(p[-1] == '\\')
1882             switch(*q++) {
1883                 case 'n': p[-1] = '\n'; break;
1884                 case 'r': p[-1] = '\r'; break;
1885                 case 't': p[-1] = '\t'; break;
1886                 case '\\': p[-1] = '\\'; break;
1887                 case 0: *p = 0; return;
1888                 default: p[-1] = q[-1]; break;
1889             }
1890 }
1891
1892 void
1893 show_bytes (FILE *fp, char *buf, int count)
1894 {
1895     while (count--) {
1896         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897             fprintf(fp, "\\%03o", *buf & 0xff);
1898         } else {
1899             putc(*buf, fp);
1900         }
1901         buf++;
1902     }
1903     fflush(fp);
1904 }
1905
1906 /* Returns an errno value */
1907 int
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 {
1910     char buf[8192], *p, *q, *buflim;
1911     int left, newcount, outcount;
1912
1913     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914         *appData.gateway != NULLCHAR) {
1915         if (appData.debugMode) {
1916             fprintf(debugFP, ">ICS: ");
1917             show_bytes(debugFP, message, count);
1918             fprintf(debugFP, "\n");
1919         }
1920         return OutputToProcess(pr, message, count, outError);
1921     }
1922
1923     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1924     p = message;
1925     q = buf;
1926     left = count;
1927     newcount = 0;
1928     while (left) {
1929         if (q >= buflim) {
1930             if (appData.debugMode) {
1931                 fprintf(debugFP, ">ICS: ");
1932                 show_bytes(debugFP, buf, newcount);
1933                 fprintf(debugFP, "\n");
1934             }
1935             outcount = OutputToProcess(pr, buf, newcount, outError);
1936             if (outcount < newcount) return -1; /* to be sure */
1937             q = buf;
1938             newcount = 0;
1939         }
1940         if (*p == '\n') {
1941             *q++ = '\r';
1942             newcount++;
1943         } else if (((unsigned char) *p) == TN_IAC) {
1944             *q++ = (char) TN_IAC;
1945             newcount ++;
1946         }
1947         *q++ = *p++;
1948         newcount++;
1949         left--;
1950     }
1951     if (appData.debugMode) {
1952         fprintf(debugFP, ">ICS: ");
1953         show_bytes(debugFP, buf, newcount);
1954         fprintf(debugFP, "\n");
1955     }
1956     outcount = OutputToProcess(pr, buf, newcount, outError);
1957     if (outcount < newcount) return -1; /* to be sure */
1958     return count;
1959 }
1960
1961 void
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 {
1964     int outError, outCount;
1965     static int gotEof = 0;
1966     static FILE *ini;
1967
1968     /* Pass data read from player on to ICS */
1969     if (count > 0) {
1970         gotEof = 0;
1971         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972         if (outCount < count) {
1973             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974         }
1975         if(have_sent_ICS_logon == 2) {
1976           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977             fprintf(ini, "%s", message);
1978             have_sent_ICS_logon = 3;
1979           } else
1980             have_sent_ICS_logon = 1;
1981         } else if(have_sent_ICS_logon == 3) {
1982             fprintf(ini, "%s", message);
1983             fclose(ini);
1984           have_sent_ICS_logon = 1;
1985         }
1986     } else if (count < 0) {
1987         RemoveInputSource(isr);
1988         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989     } else if (gotEof++ > 0) {
1990         RemoveInputSource(isr);
1991         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1992     }
1993 }
1994
1995 void
1996 KeepAlive ()
1997 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000     SendToICS("date\n");
2001     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2002 }
2003
2004 /* added routine for printf style output to ics */
2005 void
2006 ics_printf (char *format, ...)
2007 {
2008     char buffer[MSG_SIZ];
2009     va_list args;
2010
2011     va_start(args, format);
2012     vsnprintf(buffer, sizeof(buffer), format, args);
2013     buffer[sizeof(buffer)-1] = '\0';
2014     SendToICS(buffer);
2015     va_end(args);
2016 }
2017
2018 void
2019 SendToICS (char *s)
2020 {
2021     int count, outCount, outError;
2022
2023     if (icsPR == NoProc) return;
2024
2025     count = strlen(s);
2026     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 /* This is used for sending logon scripts to the ICS. Sending
2033    without a delay causes problems when using timestamp on ICC
2034    (at least on my machine). */
2035 void
2036 SendToICSDelayed (char *s, long msdelay)
2037 {
2038     int count, outCount, outError;
2039
2040     if (icsPR == NoProc) return;
2041
2042     count = strlen(s);
2043     if (appData.debugMode) {
2044         fprintf(debugFP, ">ICS: ");
2045         show_bytes(debugFP, s, count);
2046         fprintf(debugFP, "\n");
2047     }
2048     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049                                       msdelay);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055
2056 /* Remove all highlighting escape sequences in s
2057    Also deletes any suffix starting with '('
2058    */
2059 char *
2060 StripHighlightAndTitle (char *s)
2061 {
2062     static char retbuf[MSG_SIZ];
2063     char *p = retbuf;
2064
2065     while (*s != NULLCHAR) {
2066         while (*s == '\033') {
2067             while (*s != NULLCHAR && !isalpha(*s)) s++;
2068             if (*s != NULLCHAR) s++;
2069         }
2070         while (*s != NULLCHAR && *s != '\033') {
2071             if (*s == '(' || *s == '[') {
2072                 *p = NULLCHAR;
2073                 return retbuf;
2074             }
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 /* Remove all highlighting escape sequences in s */
2083 char *
2084 StripHighlight (char *s)
2085 {
2086     static char retbuf[MSG_SIZ];
2087     char *p = retbuf;
2088
2089     while (*s != NULLCHAR) {
2090         while (*s == '\033') {
2091             while (*s != NULLCHAR && !isalpha(*s)) s++;
2092             if (*s != NULLCHAR) s++;
2093         }
2094         while (*s != NULLCHAR && *s != '\033') {
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2104 char *
2105 VariantName (VariantClass v)
2106 {
2107     if(v == VariantUnknown || *engineVariant) return engineVariant;
2108     return variantNames[v];
2109 }
2110
2111
2112 /* Identify a variant from the strings the chess servers use or the
2113    PGN Variant tag names we use. */
2114 VariantClass
2115 StringToVariant (char *e)
2116 {
2117     char *p;
2118     int wnum = -1;
2119     VariantClass v = VariantNormal;
2120     int i, found = FALSE;
2121     char buf[MSG_SIZ], c;
2122     int len;
2123
2124     if (!e) return v;
2125
2126     /* [HGM] skip over optional board-size prefixes */
2127     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129         while( *e++ != '_');
2130     }
2131
2132     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2133         v = VariantNormal;
2134         found = TRUE;
2135     } else
2136     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137       if (p = StrCaseStr(e, variantNames[i])) {
2138         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139         v = (VariantClass) i;
2140         found = TRUE;
2141         break;
2142       }
2143     }
2144
2145     if (!found) {
2146       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147           || StrCaseStr(e, "wild/fr")
2148           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149         v = VariantFischeRandom;
2150       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151                  (i = 1, p = StrCaseStr(e, "w"))) {
2152         p += i;
2153         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2154         if (isdigit(*p)) {
2155           wnum = atoi(p);
2156         } else {
2157           wnum = -1;
2158         }
2159         switch (wnum) {
2160         case 0: /* FICS only, actually */
2161         case 1:
2162           /* Castling legal even if K starts on d-file */
2163           v = VariantWildCastle;
2164           break;
2165         case 2:
2166         case 3:
2167         case 4:
2168           /* Castling illegal even if K & R happen to start in
2169              normal positions. */
2170           v = VariantNoCastle;
2171           break;
2172         case 5:
2173         case 7:
2174         case 8:
2175         case 10:
2176         case 11:
2177         case 12:
2178         case 13:
2179         case 14:
2180         case 15:
2181         case 18:
2182         case 19:
2183           /* Castling legal iff K & R start in normal positions */
2184           v = VariantNormal;
2185           break;
2186         case 6:
2187         case 20:
2188         case 21:
2189           /* Special wilds for position setup; unclear what to do here */
2190           v = VariantLoadable;
2191           break;
2192         case 9:
2193           /* Bizarre ICC game */
2194           v = VariantTwoKings;
2195           break;
2196         case 16:
2197           v = VariantKriegspiel;
2198           break;
2199         case 17:
2200           v = VariantLosers;
2201           break;
2202         case 22:
2203           v = VariantFischeRandom;
2204           break;
2205         case 23:
2206           v = VariantCrazyhouse;
2207           break;
2208         case 24:
2209           v = VariantBughouse;
2210           break;
2211         case 25:
2212           v = Variant3Check;
2213           break;
2214         case 26:
2215           /* Not quite the same as FICS suicide! */
2216           v = VariantGiveaway;
2217           break;
2218         case 27:
2219           v = VariantAtomic;
2220           break;
2221         case 28:
2222           v = VariantShatranj;
2223           break;
2224
2225         /* Temporary names for future ICC types.  The name *will* change in
2226            the next xboard/WinBoard release after ICC defines it. */
2227         case 29:
2228           v = Variant29;
2229           break;
2230         case 30:
2231           v = Variant30;
2232           break;
2233         case 31:
2234           v = Variant31;
2235           break;
2236         case 32:
2237           v = Variant32;
2238           break;
2239         case 33:
2240           v = Variant33;
2241           break;
2242         case 34:
2243           v = Variant34;
2244           break;
2245         case 35:
2246           v = Variant35;
2247           break;
2248         case 36:
2249           v = Variant36;
2250           break;
2251         case 37:
2252           v = VariantShogi;
2253           break;
2254         case 38:
2255           v = VariantXiangqi;
2256           break;
2257         case 39:
2258           v = VariantCourier;
2259           break;
2260         case 40:
2261           v = VariantGothic;
2262           break;
2263         case 41:
2264           v = VariantCapablanca;
2265           break;
2266         case 42:
2267           v = VariantKnightmate;
2268           break;
2269         case 43:
2270           v = VariantFairy;
2271           break;
2272         case 44:
2273           v = VariantCylinder;
2274           break;
2275         case 45:
2276           v = VariantFalcon;
2277           break;
2278         case 46:
2279           v = VariantCapaRandom;
2280           break;
2281         case 47:
2282           v = VariantBerolina;
2283           break;
2284         case 48:
2285           v = VariantJanus;
2286           break;
2287         case 49:
2288           v = VariantSuper;
2289           break;
2290         case 50:
2291           v = VariantGreat;
2292           break;
2293         case -1:
2294           /* Found "wild" or "w" in the string but no number;
2295              must assume it's normal chess. */
2296           v = VariantNormal;
2297           break;
2298         default:
2299           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300           if( (len >= MSG_SIZ) && appData.debugMode )
2301             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302
2303           DisplayError(buf, 0);
2304           v = VariantUnknown;
2305           break;
2306         }
2307       }
2308     }
2309     if (appData.debugMode) {
2310       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311               e, wnum, VariantName(v));
2312     }
2313     return v;
2314 }
2315
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2318
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320    advance *index beyond it, and set leftover_start to the new value of
2321    *index; else return FALSE.  If pattern contains the character '*', it
2322    matches any sequence of characters not containing '\r', '\n', or the
2323    character following the '*' (if any), and the matched sequence(s) are
2324    copied into star_match.
2325    */
2326 int
2327 looking_at ( char *buf, int *index, char *pattern)
2328 {
2329     char *bufp = &buf[*index], *patternp = pattern;
2330     int star_count = 0;
2331     char *matchp = star_match[0];
2332
2333     for (;;) {
2334         if (*patternp == NULLCHAR) {
2335             *index = leftover_start = bufp - buf;
2336             *matchp = NULLCHAR;
2337             return TRUE;
2338         }
2339         if (*bufp == NULLCHAR) return FALSE;
2340         if (*patternp == '*') {
2341             if (*bufp == *(patternp + 1)) {
2342                 *matchp = NULLCHAR;
2343                 matchp = star_match[++star_count];
2344                 patternp += 2;
2345                 bufp++;
2346                 continue;
2347             } else if (*bufp == '\n' || *bufp == '\r') {
2348                 patternp++;
2349                 if (*patternp == NULLCHAR)
2350                   continue;
2351                 else
2352                   return FALSE;
2353             } else {
2354                 *matchp++ = *bufp++;
2355                 continue;
2356             }
2357         }
2358         if (*patternp != *bufp) return FALSE;
2359         patternp++;
2360         bufp++;
2361     }
2362 }
2363
2364 void
2365 SendToPlayer (char *data, int length)
2366 {
2367     int error, outCount;
2368     outCount = OutputToProcess(NoProc, data, length, &error);
2369     if (outCount < length) {
2370         DisplayFatalError(_("Error writing to display"), error, 1);
2371     }
2372 }
2373
2374 void
2375 PackHolding (char packed[], char *holding)
2376 {
2377     char *p = holding;
2378     char *q = packed;
2379     int runlength = 0;
2380     int curr = 9999;
2381     do {
2382         if (*p == curr) {
2383             runlength++;
2384         } else {
2385             switch (runlength) {
2386               case 0:
2387                 break;
2388               case 1:
2389                 *q++ = curr;
2390                 break;
2391               case 2:
2392                 *q++ = curr;
2393                 *q++ = curr;
2394                 break;
2395               default:
2396                 sprintf(q, "%d", runlength);
2397                 while (*q) q++;
2398                 *q++ = curr;
2399                 break;
2400             }
2401             runlength = 1;
2402             curr = *p;
2403         }
2404     } while (*p++);
2405     *q = NULLCHAR;
2406 }
2407
2408 /* Telnet protocol requests from the front end */
2409 void
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2411 {
2412     unsigned char msg[3];
2413     int outCount, outError;
2414
2415     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416
2417     if (appData.debugMode) {
2418         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2419         switch (ddww) {
2420           case TN_DO:
2421             ddwwStr = "DO";
2422             break;
2423           case TN_DONT:
2424             ddwwStr = "DONT";
2425             break;
2426           case TN_WILL:
2427             ddwwStr = "WILL";
2428             break;
2429           case TN_WONT:
2430             ddwwStr = "WONT";
2431             break;
2432           default:
2433             ddwwStr = buf1;
2434             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435             break;
2436         }
2437         switch (option) {
2438           case TN_ECHO:
2439             optionStr = "ECHO";
2440             break;
2441           default:
2442             optionStr = buf2;
2443             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2444             break;
2445         }
2446         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2447     }
2448     msg[0] = TN_IAC;
2449     msg[1] = ddww;
2450     msg[2] = option;
2451     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452     if (outCount < 3) {
2453         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2454     }
2455 }
2456
2457 void
2458 DoEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DO, TN_ECHO);
2462 }
2463
2464 void
2465 DontEcho ()
2466 {
2467     if (!appData.icsActive) return;
2468     TelnetRequest(TN_DONT, TN_ECHO);
2469 }
2470
2471 void
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 {
2474     /* put the holdings sent to us by the server on the board holdings area */
2475     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2476     char p;
2477     ChessSquare piece;
2478
2479     if(gameInfo.holdingsWidth < 2)  return;
2480     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481         return; // prevent overwriting by pre-board holdings
2482
2483     if( (int)lowestPiece >= BlackPawn ) {
2484         holdingsColumn = 0;
2485         countsColumn = 1;
2486         holdingsStartRow = BOARD_HEIGHT-1;
2487         direction = -1;
2488     } else {
2489         holdingsColumn = BOARD_WIDTH-1;
2490         countsColumn = BOARD_WIDTH-2;
2491         holdingsStartRow = 0;
2492         direction = 1;
2493     }
2494
2495     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496         board[i][holdingsColumn] = EmptySquare;
2497         board[i][countsColumn]   = (ChessSquare) 0;
2498     }
2499     while( (p=*holdings++) != NULLCHAR ) {
2500         piece = CharToPiece( ToUpper(p) );
2501         if(piece == EmptySquare) continue;
2502         /*j = (int) piece - (int) WhitePawn;*/
2503         j = PieceToNumber(piece);
2504         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505         if(j < 0) continue;               /* should not happen */
2506         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508         board[holdingsStartRow+j*direction][countsColumn]++;
2509     }
2510 }
2511
2512
2513 void
2514 VariantSwitch (Board board, VariantClass newVariant)
2515 {
2516    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517    static Board oldBoard;
2518
2519    startedFromPositionFile = FALSE;
2520    if(gameInfo.variant == newVariant) return;
2521
2522    /* [HGM] This routine is called each time an assignment is made to
2523     * gameInfo.variant during a game, to make sure the board sizes
2524     * are set to match the new variant. If that means adding or deleting
2525     * holdings, we shift the playing board accordingly
2526     * This kludge is needed because in ICS observe mode, we get boards
2527     * of an ongoing game without knowing the variant, and learn about the
2528     * latter only later. This can be because of the move list we requested,
2529     * in which case the game history is refilled from the beginning anyway,
2530     * but also when receiving holdings of a crazyhouse game. In the latter
2531     * case we want to add those holdings to the already received position.
2532     */
2533
2534
2535    if (appData.debugMode) {
2536      fprintf(debugFP, "Switch board from %s to %s\n",
2537              VariantName(gameInfo.variant), VariantName(newVariant));
2538      setbuf(debugFP, NULL);
2539    }
2540    shuffleOpenings = 0;       /* [HGM] shuffle */
2541    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2542    switch(newVariant)
2543      {
2544      case VariantShogi:
2545        newWidth = 9;  newHeight = 9;
2546        gameInfo.holdingsSize = 7;
2547      case VariantBughouse:
2548      case VariantCrazyhouse:
2549        newHoldingsWidth = 2; break;
2550      case VariantGreat:
2551        newWidth = 10;
2552      case VariantSuper:
2553        newHoldingsWidth = 2;
2554        gameInfo.holdingsSize = 8;
2555        break;
2556      case VariantGothic:
2557      case VariantCapablanca:
2558      case VariantCapaRandom:
2559        newWidth = 10;
2560      default:
2561        newHoldingsWidth = gameInfo.holdingsSize = 0;
2562      };
2563
2564    if(newWidth  != gameInfo.boardWidth  ||
2565       newHeight != gameInfo.boardHeight ||
2566       newHoldingsWidth != gameInfo.holdingsWidth ) {
2567
2568      /* shift position to new playing area, if needed */
2569      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574        for(i=0; i<newHeight; i++) {
2575          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577        }
2578      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583      }
2584      board[HOLDINGS_SET] = 0;
2585      gameInfo.boardWidth  = newWidth;
2586      gameInfo.boardHeight = newHeight;
2587      gameInfo.holdingsWidth = newHoldingsWidth;
2588      gameInfo.variant = newVariant;
2589      InitDrawingSizes(-2, 0);
2590    } else gameInfo.variant = newVariant;
2591    CopyBoard(oldBoard, board);   // remember correctly formatted board
2592      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2593    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2594 }
2595
2596 static int loggedOn = FALSE;
2597
2598 /*-- Game start info cache: --*/
2599 int gs_gamenum;
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\   ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2607
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2610
2611 // [HGM] seekgraph
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2615 #define SQUARE 0x80
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2624
2625 void
2626 PlotSeekAd (int i)
2627 {
2628         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630         if(r < minRating+100 && r >=0 ) r = minRating+100;
2631         if(r > maxRating) r = maxRating;
2632         if(tc < 1.f) tc = 1.f;
2633         if(tc > 95.f) tc = 95.f;
2634         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635         y = ((double)r - minRating)/(maxRating - minRating)
2636             * (h-vMargin-squareSize/8-1) + vMargin;
2637         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638         if(strstr(seekAdList[i], " u ")) color = 1;
2639         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640            !strstr(seekAdList[i], "bullet") &&
2641            !strstr(seekAdList[i], "blitz") &&
2642            !strstr(seekAdList[i], "standard") ) color = 2;
2643         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2645 }
2646
2647 void
2648 PlotSingleSeekAd (int i)
2649 {
2650         PlotSeekAd(i);
2651 }
2652
2653 void
2654 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2655 {
2656         char buf[MSG_SIZ], *ext = "";
2657         VariantClass v = StringToVariant(type);
2658         if(strstr(type, "wild")) {
2659             ext = type + 4; // append wild number
2660             if(v == VariantFischeRandom) type = "chess960"; else
2661             if(v == VariantLoadable) type = "setup"; else
2662             type = VariantName(v);
2663         }
2664         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670             seekNrList[nrOfSeekAds] = nr;
2671             zList[nrOfSeekAds] = 0;
2672             seekAdList[nrOfSeekAds++] = StrSave(buf);
2673             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2674         }
2675 }
2676
2677 void
2678 EraseSeekDot (int i)
2679 {
2680     int x = xList[i], y = yList[i], d=squareSize/4, k;
2681     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683     // now replot every dot that overlapped
2684     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685         int xx = xList[k], yy = yList[k];
2686         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687             DrawSeekDot(xx, yy, colorList[k]);
2688     }
2689 }
2690
2691 void
2692 RemoveSeekAd (int nr)
2693 {
2694         int i;
2695         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696             EraseSeekDot(i);
2697             if(seekAdList[i]) free(seekAdList[i]);
2698             seekAdList[i] = seekAdList[--nrOfSeekAds];
2699             seekNrList[i] = seekNrList[nrOfSeekAds];
2700             ratingList[i] = ratingList[nrOfSeekAds];
2701             colorList[i]  = colorList[nrOfSeekAds];
2702             tcList[i] = tcList[nrOfSeekAds];
2703             xList[i]  = xList[nrOfSeekAds];
2704             yList[i]  = yList[nrOfSeekAds];
2705             zList[i]  = zList[nrOfSeekAds];
2706             seekAdList[nrOfSeekAds] = NULL;
2707             break;
2708         }
2709 }
2710
2711 Boolean
2712 MatchSoughtLine (char *line)
2713 {
2714     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715     int nr, base, inc, u=0; char dummy;
2716
2717     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719        (u=1) &&
2720        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2722         // match: compact and save the line
2723         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2724         return TRUE;
2725     }
2726     return FALSE;
2727 }
2728
2729 int
2730 DrawSeekGraph ()
2731 {
2732     int i;
2733     if(!seekGraphUp) return FALSE;
2734     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2736
2737     DrawSeekBackground(0, 0, w, h);
2738     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742         yy = h-1-yy;
2743         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2744         if(i%500 == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2748         }
2749     }
2750     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751     for(i=1; i<100; i+=(i<10?1:5)) {
2752         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755             char buf[MSG_SIZ];
2756             snprintf(buf, MSG_SIZ, "%d", i);
2757             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2758         }
2759     }
2760     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2761     return TRUE;
2762 }
2763
2764 int
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 {
2767     static int lastDown = 0, displayed = 0, lastSecond;
2768     if(y < 0) return FALSE;
2769     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771         if(!seekGraphUp) return FALSE;
2772         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773         DrawPosition(TRUE, NULL);
2774         return TRUE;
2775     }
2776     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777         if(click == Release || moving) return FALSE;
2778         nrOfSeekAds = 0;
2779         soughtPending = TRUE;
2780         SendToICS(ics_prefix);
2781         SendToICS("sought\n"); // should this be "sought all"?
2782     } else { // issue challenge based on clicked ad
2783         int dist = 10000; int i, closest = 0, second = 0;
2784         for(i=0; i<nrOfSeekAds; i++) {
2785             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2786             if(d < dist) { dist = d; closest = i; }
2787             second += (d - zList[i] < 120); // count in-range ads
2788             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2789         }
2790         if(dist < 120) {
2791             char buf[MSG_SIZ];
2792             second = (second > 1);
2793             if(displayed != closest || second != lastSecond) {
2794                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795                 lastSecond = second; displayed = closest;
2796             }
2797             if(click == Press) {
2798                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2799                 lastDown = closest;
2800                 return TRUE;
2801             } // on press 'hit', only show info
2802             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804             SendToICS(ics_prefix);
2805             SendToICS(buf);
2806             return TRUE; // let incoming board of started game pop down the graph
2807         } else if(click == Release) { // release 'miss' is ignored
2808             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809             if(moving == 2) { // right up-click
2810                 nrOfSeekAds = 0; // refresh graph
2811                 soughtPending = TRUE;
2812                 SendToICS(ics_prefix);
2813                 SendToICS("sought\n"); // should this be "sought all"?
2814             }
2815             return TRUE;
2816         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817         // press miss or release hit 'pop down' seek graph
2818         seekGraphUp = FALSE;
2819         DrawPosition(TRUE, NULL);
2820     }
2821     return TRUE;
2822 }
2823
2824 void
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 {
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2836
2837     static int started = STARTED_NONE;
2838     static char parse[20000];
2839     static int parse_pos = 0;
2840     static char buf[BUF_SIZE + 1];
2841     static int firstTime = TRUE, intfSet = FALSE;
2842     static ColorClass prevColor = ColorNormal;
2843     static int savingComment = FALSE;
2844     static int cmatch = 0; // continuation sequence match
2845     char *bp;
2846     char str[MSG_SIZ];
2847     int i, oldi;
2848     int buf_len;
2849     int next_out;
2850     int tkind;
2851     int backup;    /* [DM] For zippy color lines */
2852     char *p;
2853     char talker[MSG_SIZ]; // [HGM] chat
2854     int channel, collective=0;
2855
2856     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857
2858     if (appData.debugMode) {
2859       if (!error) {
2860         fprintf(debugFP, "<ICS: ");
2861         show_bytes(debugFP, data, count);
2862         fprintf(debugFP, "\n");
2863       }
2864     }
2865
2866     if (appData.debugMode) { int f = forwardMostMove;
2867         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2870     }
2871     if (count > 0) {
2872         /* If last read ended with a partial line that we couldn't parse,
2873            prepend it to the new read and try again. */
2874         if (leftover_len > 0) {
2875             for (i=0; i<leftover_len; i++)
2876               buf[i] = buf[leftover_start + i];
2877         }
2878
2879     /* copy new characters into the buffer */
2880     bp = buf + leftover_len;
2881     buf_len=leftover_len;
2882     for (i=0; i<count; i++)
2883     {
2884         // ignore these
2885         if (data[i] == '\r')
2886             continue;
2887
2888         // join lines split by ICS?
2889         if (!appData.noJoin)
2890         {
2891             /*
2892                 Joining just consists of finding matches against the
2893                 continuation sequence, and discarding that sequence
2894                 if found instead of copying it.  So, until a match
2895                 fails, there's nothing to do since it might be the
2896                 complete sequence, and thus, something we don't want
2897                 copied.
2898             */
2899             if (data[i] == cont_seq[cmatch])
2900             {
2901                 cmatch++;
2902                 if (cmatch == strlen(cont_seq))
2903                 {
2904                     cmatch = 0; // complete match.  just reset the counter
2905
2906                     /*
2907                         it's possible for the ICS to not include the space
2908                         at the end of the last word, making our [correct]
2909                         join operation fuse two separate words.  the server
2910                         does this when the space occurs at the width setting.
2911                     */
2912                     if (!buf_len || buf[buf_len-1] != ' ')
2913                     {
2914                         *bp++ = ' ';
2915                         buf_len++;
2916                     }
2917                 }
2918                 continue;
2919             }
2920             else if (cmatch)
2921             {
2922                 /*
2923                     match failed, so we have to copy what matched before
2924                     falling through and copying this character.  In reality,
2925                     this will only ever be just the newline character, but
2926                     it doesn't hurt to be precise.
2927                 */
2928                 strncpy(bp, cont_seq, cmatch);
2929                 bp += cmatch;
2930                 buf_len += cmatch;
2931                 cmatch = 0;
2932             }
2933         }
2934
2935         // copy this char
2936         *bp++ = data[i];
2937         buf_len++;
2938     }
2939
2940         buf[buf_len] = NULLCHAR;
2941 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2942         next_out = 0;
2943         leftover_start = 0;
2944
2945         i = 0;
2946         while (i < buf_len) {
2947             /* Deal with part of the TELNET option negotiation
2948                protocol.  We refuse to do anything beyond the
2949                defaults, except that we allow the WILL ECHO option,
2950                which ICS uses to turn off password echoing when we are
2951                directly connected to it.  We reject this option
2952                if localLineEditing mode is on (always on in xboard)
2953                and we are talking to port 23, which might be a real
2954                telnet server that will try to keep WILL ECHO on permanently.
2955              */
2956             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958                 unsigned char option;
2959                 oldi = i;
2960                 switch ((unsigned char) buf[++i]) {
2961                   case TN_WILL:
2962                     if (appData.debugMode)
2963                       fprintf(debugFP, "\n<WILL ");
2964                     switch (option = (unsigned char) buf[++i]) {
2965                       case TN_ECHO:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "ECHO ");
2968                         /* Reply only if this is a change, according
2969                            to the protocol rules. */
2970                         if (remoteEchoOption) break;
2971                         if (appData.localLineEditing &&
2972                             atoi(appData.icsPort) == TN_PORT) {
2973                             TelnetRequest(TN_DONT, TN_ECHO);
2974                         } else {
2975                             EchoOff();
2976                             TelnetRequest(TN_DO, TN_ECHO);
2977                             remoteEchoOption = TRUE;
2978                         }
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", option);
2983                         /* Whatever this is, we don't want it. */
2984                         TelnetRequest(TN_DONT, option);
2985                         break;
2986                     }
2987                     break;
2988                   case TN_WONT:
2989                     if (appData.debugMode)
2990                       fprintf(debugFP, "\n<WONT ");
2991                     switch (option = (unsigned char) buf[++i]) {
2992                       case TN_ECHO:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "ECHO ");
2995                         /* Reply only if this is a change, according
2996                            to the protocol rules. */
2997                         if (!remoteEchoOption) break;
2998                         EchoOn();
2999                         TelnetRequest(TN_DONT, TN_ECHO);
3000                         remoteEchoOption = FALSE;
3001                         break;
3002                       default:
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", (unsigned char) option);
3005                         /* Whatever this is, it must already be turned
3006                            off, because we never agree to turn on
3007                            anything non-default, so according to the
3008                            protocol rules, we don't reply. */
3009                         break;
3010                     }
3011                     break;
3012                   case TN_DO:
3013                     if (appData.debugMode)
3014                       fprintf(debugFP, "\n<DO ");
3015                     switch (option = (unsigned char) buf[++i]) {
3016                       default:
3017                         /* Whatever this is, we refuse to do it. */
3018                         if (appData.debugMode)
3019                           fprintf(debugFP, "%d ", option);
3020                         TelnetRequest(TN_WONT, option);
3021                         break;
3022                     }
3023                     break;
3024                   case TN_DONT:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<DONT ");
3027                     switch (option = (unsigned char) buf[++i]) {
3028                       default:
3029                         if (appData.debugMode)
3030                           fprintf(debugFP, "%d ", option);
3031                         /* Whatever this is, we are already not doing
3032                            it, because we never agree to do anything
3033                            non-default, so according to the protocol
3034                            rules, we don't reply. */
3035                         break;
3036                     }
3037                     break;
3038                   case TN_IAC:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<IAC ");
3041                     /* Doubled IAC; pass it through */
3042                     i--;
3043                     break;
3044                   default:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047                     /* Drop all other telnet commands on the floor */
3048                     break;
3049                 }
3050                 if (oldi > next_out)
3051                   SendToPlayer(&buf[next_out], oldi - next_out);
3052                 if (++i > next_out)
3053                   next_out = i;
3054                 continue;
3055             }
3056
3057             /* OK, this at least will *usually* work */
3058             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3059                 loggedOn = TRUE;
3060             }
3061
3062             if (loggedOn && !intfSet) {
3063                 if (ics_type == ICS_ICC) {
3064                   snprintf(str, MSG_SIZ,
3065                           "/set-quietly interface %s\n/set-quietly style 12\n",
3066                           programVersion);
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3069                 } else if (ics_type == ICS_CHESSNET) {
3070                   snprintf(str, MSG_SIZ, "/style 12\n");
3071                 } else {
3072                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073                   strcat(str, programVersion);
3074                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 #ifdef WIN32
3078                   strcat(str, "$iset nohighlight 1\n");
3079 #endif
3080                   strcat(str, "$iset lock 1\n$style 12\n");
3081                 }
3082                 SendToICS(str);
3083                 NotifyFrontendLogin();
3084                 intfSet = TRUE;
3085             }
3086
3087             if (started == STARTED_COMMENT) {
3088                 /* Accumulate characters in comment */
3089                 parse[parse_pos++] = buf[i];
3090                 if (buf[i] == '\n') {
3091                     parse[parse_pos] = NULLCHAR;
3092                     if(chattingPartner>=0) {
3093                         char mess[MSG_SIZ];
3094                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095                         OutputChatMessage(chattingPartner, mess);
3096                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097                             int p;
3098                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101                                 OutputChatMessage(p, mess);
3102                                 break;
3103                             }
3104                         }
3105                         chattingPartner = -1;
3106                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3107                         collective = 0;
3108                     } else
3109                     if(!suppressKibitz) // [HGM] kibitz
3110                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112                         int nrDigit = 0, nrAlph = 0, j;
3113                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115                         parse[parse_pos] = NULLCHAR;
3116                         // try to be smart: if it does not look like search info, it should go to
3117                         // ICS interaction window after all, not to engine-output window.
3118                         for(j=0; j<parse_pos; j++) { // count letters and digits
3119                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3121                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3122                         }
3123                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124                             int depth=0; float score;
3125                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127                                 pvInfoList[forwardMostMove-1].depth = depth;
3128                                 pvInfoList[forwardMostMove-1].score = 100*score;
3129                             }
3130                             OutputKibitz(suppressKibitz, parse);
3131                         } else {
3132                             char tmp[MSG_SIZ];
3133                             if(gameMode == IcsObserving) // restore original ICS messages
3134                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136                             else
3137                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139                             SendToPlayer(tmp, strlen(tmp));
3140                         }
3141                         next_out = i+1; // [HGM] suppress printing in ICS window
3142                     }
3143                     started = STARTED_NONE;
3144                 } else {
3145                     /* Don't match patterns against characters in comment */
3146                     i++;
3147                     continue;
3148                 }
3149             }
3150             if (started == STARTED_CHATTER) {
3151                 if (buf[i] != '\n') {
3152                     /* Don't match patterns against characters in chatter */
3153                     i++;
3154                     continue;
3155                 }
3156                 started = STARTED_NONE;
3157                 if(suppressKibitz) next_out = i+1;
3158             }
3159
3160             /* Kludge to deal with rcmd protocol */
3161             if (firstTime && looking_at(buf, &i, "\001*")) {
3162                 DisplayFatalError(&buf[1], 0, 1);
3163                 continue;
3164             } else {
3165                 firstTime = FALSE;
3166             }
3167
3168             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3169                 ics_type = ICS_ICC;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176                 ics_type = ICS_FICS;
3177                 ics_prefix = "$";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183                 ics_type = ICS_CHESSNET;
3184                 ics_prefix = "/";
3185                 if (appData.debugMode)
3186                   fprintf(debugFP, "ics_type %d\n", ics_type);
3187                 continue;
3188             }
3189
3190             if (!loggedOn &&
3191                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3193                  looking_at(buf, &i, "will be \"*\""))) {
3194               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3195               continue;
3196             }
3197
3198             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199               char buf[MSG_SIZ];
3200               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201               DisplayIcsInteractionTitle(buf);
3202               have_set_title = TRUE;
3203             }
3204
3205             /* skip finger notes */
3206             if (started == STARTED_NONE &&
3207                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208                  (buf[i] == '1' && buf[i+1] == '0')) &&
3209                 buf[i+2] == ':' && buf[i+3] == ' ') {
3210               started = STARTED_CHATTER;
3211               i += 3;
3212               continue;
3213             }
3214
3215             oldi = i;
3216             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217             if(appData.seekGraph) {
3218                 if(soughtPending && MatchSoughtLine(buf+i)) {
3219                     i = strstr(buf+i, "rated") - buf;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     next_out = leftover_start = i;
3222                     started = STARTED_CHATTER;
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227                         && looking_at(buf, &i, "* ads displayed")) {
3228                     soughtPending = FALSE;
3229                     seekGraphUp = TRUE;
3230                     DrawSeekGraph();
3231                     continue;
3232                 }
3233                 if(appData.autoRefresh) {
3234                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235                         int s = (ics_type == ICS_ICC); // ICC format differs
3236                         if(seekGraphUp)
3237                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239                         looking_at(buf, &i, "*% "); // eat prompt
3240                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = i; // suppress
3243                         continue;
3244                     }
3245                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246                         char *p = star_match[0];
3247                         while(*p) {
3248                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3249                             while(*p && *p++ != ' '); // next
3250                         }
3251                         looking_at(buf, &i, "*% "); // eat prompt
3252                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = i;
3254                         continue;
3255                     }
3256                 }
3257             }
3258
3259             /* skip formula vars */
3260             if (started == STARTED_NONE &&
3261                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262               started = STARTED_CHATTER;
3263               i += 3;
3264               continue;
3265             }
3266
3267             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268             if (appData.autoKibitz && started == STARTED_NONE &&
3269                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3270                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3275                         suppressKibitz = TRUE;
3276                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = i;
3278                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279                                 && (gameMode == IcsPlayingWhite)) ||
3280                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3282                             started = STARTED_CHATTER; // own kibitz we simply discard
3283                         else {
3284                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285                             parse_pos = 0; parse[0] = NULLCHAR;
3286                             savingComment = TRUE;
3287                             suppressKibitz = gameMode != IcsObserving ? 2 :
3288                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3289                         }
3290                         continue;
3291                 } else
3292                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294                          && atoi(star_match[0])) {
3295                     // suppress the acknowledgements of our own autoKibitz
3296                     char *p;
3297                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299                     SendToPlayer(star_match[0], strlen(star_match[0]));
3300                     if(looking_at(buf, &i, "*% ")) // eat prompt
3301                         suppressKibitz = FALSE;
3302                     next_out = i;
3303                     continue;
3304                 }
3305             } // [HGM] kibitz: end of patch
3306
3307             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308
3309             // [HGM] chat: intercept tells by users for which we have an open chat window
3310             channel = -1;
3311             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312                                            looking_at(buf, &i, "* whispers:") ||
3313                                            looking_at(buf, &i, "* kibitzes:") ||
3314                                            looking_at(buf, &i, "* shouts:") ||
3315                                            looking_at(buf, &i, "* c-shouts:") ||
3316                                            looking_at(buf, &i, "--> * ") ||
3317                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321                 int p;
3322                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323                 chattingPartner = -1; collective = 0;
3324
3325                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329                     talker[0] = '['; strcat(talker, "] ");
3330                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331                     chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("kibitzes", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343                 for(p=0; p<MAX_CHAT; p++) {
3344                     collective = 1;
3345                     if(!strcmp("whispers", chatPartner[p])) {
3346                         talker[0] = '['; strcat(talker, "] ");
3347                         chattingPartner = p; break;
3348                     }
3349                 } else
3350                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351                   if(buf[i-8] == '-' && buf[i-3] == 't')
3352                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353                     collective = 1;
3354                     if(!strcmp("c-shouts", chatPartner[p])) {
3355                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                   if(chattingPartner < 0)
3360                   for(p=0; p<MAX_CHAT; p++) {
3361                     collective = 1;
3362                     if(!strcmp("shouts", chatPartner[p])) {
3363                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366                         chattingPartner = p; break;
3367                     }
3368                   }
3369                 }
3370                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372                     talker[0] = 0;
3373                     Colorize(ColorTell, FALSE);
3374                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375                     collective |= 2;
3376                     chattingPartner = p; break;
3377                 }
3378                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380                     started = STARTED_COMMENT;
3381                     parse_pos = 0; parse[0] = NULLCHAR;
3382                     savingComment = 3 + chattingPartner; // counts as TRUE
3383                     if(collective == 3) i = oldi; else {
3384                         suppressKibitz = TRUE;
3385                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3387                         continue;
3388                     }
3389                 }
3390             } // [HGM] chat: end of patch
3391
3392           backup = i;
3393             if (appData.zippyTalk || appData.zippyPlay) {
3394                 /* [DM] Backup address for color zippy lines */
3395 #if ZIPPY
3396                if (loggedOn == TRUE)
3397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3399                        ;
3400 #endif
3401             } // [DM] 'else { ' deleted
3402                 if (
3403                     /* Regular tells and says */
3404                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3405                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3406                     looking_at(buf, &i, "* says: ") ||
3407                     /* Don't color "message" or "messages" output */
3408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3409                     looking_at(buf, &i, "*. * at *:*: ") ||
3410                     looking_at(buf, &i, "--* (*:*): ") ||
3411                     /* Message notifications (same color as tells) */
3412                     looking_at(buf, &i, "* has left a message ") ||
3413                     looking_at(buf, &i, "* just sent you a message:\n") ||
3414                     /* Whispers and kibitzes */
3415                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3416                     looking_at(buf, &i, "* kibitzes: ") ||
3417                     /* Channel tells */
3418                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3419
3420                   if (tkind == 1 && strchr(star_match[0], ':')) {
3421                       /* Avoid "tells you:" spoofs in channels */
3422                      tkind = 3;
3423                   }
3424                   if (star_match[0][0] == NULLCHAR ||
3425                       strchr(star_match[0], ' ') ||
3426                       (tkind == 3 && strchr(star_match[1], ' '))) {
3427                     /* Reject bogus matches */
3428                     i = oldi;
3429                   } else {
3430                     if (appData.colorize) {
3431                       if (oldi > next_out) {
3432                         SendToPlayer(&buf[next_out], oldi - next_out);
3433                         next_out = oldi;
3434                       }
3435                       switch (tkind) {
3436                       case 1:
3437                         Colorize(ColorTell, FALSE);
3438                         curColor = ColorTell;
3439                         break;
3440                       case 2:
3441                         Colorize(ColorKibitz, FALSE);
3442                         curColor = ColorKibitz;
3443                         break;
3444                       case 3:
3445                         p = strrchr(star_match[1], '(');
3446                         if (p == NULL) {
3447                           p = star_match[1];
3448                         } else {
3449                           p++;
3450                         }
3451                         if (atoi(p) == 1) {
3452                           Colorize(ColorChannel1, FALSE);
3453                           curColor = ColorChannel1;
3454                         } else {
3455                           Colorize(ColorChannel, FALSE);
3456                           curColor = ColorChannel;
3457                         }
3458                         break;
3459                       case 5:
3460                         curColor = ColorNormal;
3461                         break;
3462                       }
3463                     }
3464                     if (started == STARTED_NONE && appData.autoComment &&
3465                         (gameMode == IcsObserving ||
3466                          gameMode == IcsPlayingWhite ||
3467                          gameMode == IcsPlayingBlack)) {
3468                       parse_pos = i - oldi;
3469                       memcpy(parse, &buf[oldi], parse_pos);
3470                       parse[parse_pos] = NULLCHAR;
3471                       started = STARTED_COMMENT;
3472                       savingComment = TRUE;
3473                     } else if(collective != 3) {
3474                       started = STARTED_CHATTER;
3475                       savingComment = FALSE;
3476                     }
3477                     loggedOn = TRUE;
3478                     continue;
3479                   }
3480                 }
3481
3482                 if (looking_at(buf, &i, "* s-shouts: ") ||
3483                     looking_at(buf, &i, "* c-shouts: ")) {
3484                     if (appData.colorize) {
3485                         if (oldi > next_out) {
3486                             SendToPlayer(&buf[next_out], oldi - next_out);
3487                             next_out = oldi;
3488                         }
3489                         Colorize(ColorSShout, FALSE);
3490                         curColor = ColorSShout;
3491                     }
3492                     loggedOn = TRUE;
3493                     started = STARTED_CHATTER;
3494                     continue;
3495                 }
3496
3497                 if (looking_at(buf, &i, "--->")) {
3498                     loggedOn = TRUE;
3499                     continue;
3500                 }
3501
3502                 if (looking_at(buf, &i, "* shouts: ") ||
3503                     looking_at(buf, &i, "--> ")) {
3504                     if (appData.colorize) {
3505                         if (oldi > next_out) {
3506                             SendToPlayer(&buf[next_out], oldi - next_out);
3507                             next_out = oldi;
3508                         }
3509                         Colorize(ColorShout, FALSE);
3510                         curColor = ColorShout;
3511                     }
3512                     loggedOn = TRUE;
3513                     started = STARTED_CHATTER;
3514                     continue;
3515                 }
3516
3517                 if (looking_at( buf, &i, "Challenge:")) {
3518                     if (appData.colorize) {
3519                         if (oldi > next_out) {
3520                             SendToPlayer(&buf[next_out], oldi - next_out);
3521                             next_out = oldi;
3522                         }
3523                         Colorize(ColorChallenge, FALSE);
3524                         curColor = ColorChallenge;
3525                     }
3526                     loggedOn = TRUE;
3527                     continue;
3528                 }
3529
3530                 if (looking_at(buf, &i, "* offers you") ||
3531                     looking_at(buf, &i, "* offers to be") ||
3532                     looking_at(buf, &i, "* would like to") ||
3533                     looking_at(buf, &i, "* requests to") ||
3534                     looking_at(buf, &i, "Your opponent offers") ||
3535                     looking_at(buf, &i, "Your opponent requests")) {
3536
3537                     if (appData.colorize) {
3538                         if (oldi > next_out) {
3539                             SendToPlayer(&buf[next_out], oldi - next_out);
3540                             next_out = oldi;
3541                         }
3542                         Colorize(ColorRequest, FALSE);
3543                         curColor = ColorRequest;
3544                     }
3545                     continue;
3546                 }
3547
3548                 if (looking_at(buf, &i, "* (*) seeking")) {
3549                     if (appData.colorize) {
3550                         if (oldi > next_out) {
3551                             SendToPlayer(&buf[next_out], oldi - next_out);
3552                             next_out = oldi;
3553                         }
3554                         Colorize(ColorSeek, FALSE);
3555                         curColor = ColorSeek;
3556                     }
3557                     continue;
3558             }
3559
3560           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3561
3562             if (looking_at(buf, &i, "\\   ")) {
3563                 if (prevColor != ColorNormal) {
3564                     if (oldi > next_out) {
3565                         SendToPlayer(&buf[next_out], oldi - next_out);
3566                         next_out = oldi;
3567                     }
3568                     Colorize(prevColor, TRUE);
3569                     curColor = prevColor;
3570                 }
3571                 if (savingComment) {
3572                     parse_pos = i - oldi;
3573                     memcpy(parse, &buf[oldi], parse_pos);
3574                     parse[parse_pos] = NULLCHAR;
3575                     started = STARTED_COMMENT;
3576                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3577                         chattingPartner = savingComment - 3; // kludge to remember the box
3578                 } else {
3579                     started = STARTED_CHATTER;
3580                 }
3581                 continue;
3582             }
3583
3584             if (looking_at(buf, &i, "Black Strength :") ||
3585                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3586                 looking_at(buf, &i, "<10>") ||
3587                 looking_at(buf, &i, "#@#")) {
3588                 /* Wrong board style */
3589                 loggedOn = TRUE;
3590                 SendToICS(ics_prefix);
3591                 SendToICS("set style 12\n");
3592                 SendToICS(ics_prefix);
3593                 SendToICS("refresh\n");
3594                 continue;
3595             }
3596
3597             if (looking_at(buf, &i, "login:")) {
3598               if (!have_sent_ICS_logon) {
3599                 if(ICSInitScript())
3600                   have_sent_ICS_logon = 1;
3601                 else // no init script was found
3602                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3603               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3605               }
3606                 continue;
3607             }
3608
3609             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3610                 (looking_at(buf, &i, "\n<12> ") ||
3611                  looking_at(buf, &i, "<12> "))) {
3612                 loggedOn = TRUE;
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_BOARD;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3623                 looking_at(buf, &i, "<b1> ")) {
3624                 if (oldi > next_out) {
3625                     SendToPlayer(&buf[next_out], oldi - next_out);
3626                 }
3627                 next_out = i;
3628                 started = STARTED_HOLDINGS;
3629                 parse_pos = 0;
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3634                 loggedOn = TRUE;
3635                 /* Header for a move list -- first line */
3636
3637                 switch (ics_getting_history) {
3638                   case H_FALSE:
3639                     switch (gameMode) {
3640                       case IcsIdle:
3641                       case BeginningOfGame:
3642                         /* User typed "moves" or "oldmoves" while we
3643                            were idle.  Pretend we asked for these
3644                            moves and soak them up so user can step
3645                            through them and/or save them.
3646                            */
3647                         Reset(FALSE, TRUE);
3648                         gameMode = IcsObserving;
3649                         ModeHighlight();
3650                         ics_gamenum = -1;
3651                         ics_getting_history = H_GOT_UNREQ_HEADER;
3652                         break;
3653                       case EditGame: /*?*/
3654                       case EditPosition: /*?*/
3655                         /* Should above feature work in these modes too? */
3656                         /* For now it doesn't */
3657                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3658                         break;
3659                       default:
3660                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3661                         break;
3662                     }
3663                     break;
3664                   case H_REQUESTED:
3665                     /* Is this the right one? */
3666                     if (gameInfo.white && gameInfo.black &&
3667                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3668                         strcmp(gameInfo.black, star_match[2]) == 0) {
3669                         /* All is well */
3670                         ics_getting_history = H_GOT_REQ_HEADER;
3671                     }
3672                     break;
3673                   case H_GOT_REQ_HEADER:
3674                   case H_GOT_UNREQ_HEADER:
3675                   case H_GOT_UNWANTED_HEADER:
3676                   case H_GETTING_MOVES:
3677                     /* Should not happen */
3678                     DisplayError(_("Error gathering move list: two headers"), 0);
3679                     ics_getting_history = H_FALSE;
3680                     break;
3681                 }
3682
3683                 /* Save player ratings into gameInfo if needed */
3684                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3685                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3686                     (gameInfo.whiteRating == -1 ||
3687                      gameInfo.blackRating == -1)) {
3688
3689                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3690                     gameInfo.blackRating = string_to_rating(star_match[3]);
3691                     if (appData.debugMode)
3692                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3693                               gameInfo.whiteRating, gameInfo.blackRating);
3694                 }
3695                 continue;
3696             }
3697
3698             if (looking_at(buf, &i,
3699               "* * match, initial time: * minute*, increment: * second")) {
3700                 /* Header for a move list -- second line */
3701                 /* Initial board will follow if this is a wild game */
3702                 if (gameInfo.event != NULL) free(gameInfo.event);
3703                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3704                 gameInfo.event = StrSave(str);
3705                 /* [HGM] we switched variant. Translate boards if needed. */
3706                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3707                 continue;
3708             }
3709
3710             if (looking_at(buf, &i, "Move  ")) {
3711                 /* Beginning of a move list */
3712                 switch (ics_getting_history) {
3713                   case H_FALSE:
3714                     /* Normally should not happen */
3715                     /* Maybe user hit reset while we were parsing */
3716                     break;
3717                   case H_REQUESTED:
3718                     /* Happens if we are ignoring a move list that is not
3719                      * the one we just requested.  Common if the user
3720                      * tries to observe two games without turning off
3721                      * getMoveList */
3722                     break;
3723                   case H_GETTING_MOVES:
3724                     /* Should not happen */
3725                     DisplayError(_("Error gathering move list: nested"), 0);
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                   case H_GOT_REQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES;
3731                     parse_pos = 0;
3732                     if (oldi > next_out) {
3733                         SendToPlayer(&buf[next_out], oldi - next_out);
3734                     }
3735                     break;
3736                   case H_GOT_UNREQ_HEADER:
3737                     ics_getting_history = H_GETTING_MOVES;
3738                     started = STARTED_MOVES_NOHIDE;
3739                     parse_pos = 0;
3740                     break;
3741                   case H_GOT_UNWANTED_HEADER:
3742                     ics_getting_history = H_FALSE;
3743                     break;
3744                 }
3745                 continue;
3746             }
3747
3748             if (looking_at(buf, &i, "% ") ||
3749                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3750                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3751                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3752                     soughtPending = FALSE;
3753                     seekGraphUp = TRUE;
3754                     DrawSeekGraph();
3755                 }
3756                 if(suppressKibitz) next_out = i;
3757                 savingComment = FALSE;
3758                 suppressKibitz = 0;
3759                 switch (started) {
3760                   case STARTED_MOVES:
3761                   case STARTED_MOVES_NOHIDE:
3762                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3763                     parse[parse_pos + i - oldi] = NULLCHAR;
3764                     ParseGameHistory(parse);
3765 #if ZIPPY
3766                     if (appData.zippyPlay && first.initDone) {
3767                         FeedMovesToProgram(&first, forwardMostMove);
3768                         if (gameMode == IcsPlayingWhite) {
3769                             if (WhiteOnMove(forwardMostMove)) {
3770                                 if (first.sendTime) {
3771                                   if (first.useColors) {
3772                                     SendToProgram("black\n", &first);
3773                                   }
3774                                   SendTimeRemaining(&first, TRUE);
3775                                 }
3776                                 if (first.useColors) {
3777                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3778                                 }
3779                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3780                                 first.maybeThinking = TRUE;
3781                             } else {
3782                                 if (first.usePlayother) {
3783                                   if (first.sendTime) {
3784                                     SendTimeRemaining(&first, TRUE);
3785                                   }
3786                                   SendToProgram("playother\n", &first);
3787                                   firstMove = FALSE;
3788                                 } else {
3789                                   firstMove = TRUE;
3790                                 }
3791                             }
3792                         } else if (gameMode == IcsPlayingBlack) {
3793                             if (!WhiteOnMove(forwardMostMove)) {
3794                                 if (first.sendTime) {
3795                                   if (first.useColors) {
3796                                     SendToProgram("white\n", &first);
3797                                   }
3798                                   SendTimeRemaining(&first, FALSE);
3799                                 }
3800                                 if (first.useColors) {
3801                                   SendToProgram("black\n", &first);
3802                                 }
3803                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3804                                 first.maybeThinking = TRUE;
3805                             } else {
3806                                 if (first.usePlayother) {
3807                                   if (first.sendTime) {
3808                                     SendTimeRemaining(&first, FALSE);
3809                                   }
3810                                   SendToProgram("playother\n", &first);
3811                                   firstMove = FALSE;
3812                                 } else {
3813                                   firstMove = TRUE;
3814                                 }
3815                             }
3816                         }
3817                     }
3818 #endif
3819                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3820                         /* Moves came from oldmoves or moves command
3821                            while we weren't doing anything else.
3822                            */
3823                         currentMove = forwardMostMove;
3824                         ClearHighlights();/*!!could figure this out*/
3825                         flipView = appData.flipView;
3826                         DrawPosition(TRUE, boards[currentMove]);
3827                         DisplayBothClocks();
3828                         snprintf(str, MSG_SIZ, "%s %s %s",
3829                                 gameInfo.white, _("vs."),  gameInfo.black);
3830                         DisplayTitle(str);
3831                         gameMode = IcsIdle;
3832                     } else {
3833                         /* Moves were history of an active game */
3834                         if (gameInfo.resultDetails != NULL) {
3835                             free(gameInfo.resultDetails);
3836                             gameInfo.resultDetails = NULL;
3837                         }
3838                     }
3839                     HistorySet(parseList, backwardMostMove,
3840                                forwardMostMove, currentMove-1);
3841                     DisplayMove(currentMove - 1);
3842                     if (started == STARTED_MOVES) next_out = i;
3843                     started = STARTED_NONE;
3844                     ics_getting_history = H_FALSE;
3845                     break;
3846
3847                   case STARTED_OBSERVE:
3848                     started = STARTED_NONE;
3849                     SendToICS(ics_prefix);
3850                     SendToICS("refresh\n");
3851                     break;
3852
3853                   default:
3854                     break;
3855                 }
3856                 if(bookHit) { // [HGM] book: simulate book reply
3857                     static char bookMove[MSG_SIZ]; // a bit generous?
3858
3859                     programStats.nodes = programStats.depth = programStats.time =
3860                     programStats.score = programStats.got_only_move = 0;
3861                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3862
3863                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3864                     strcat(bookMove, bookHit);
3865                     HandleMachineMove(bookMove, &first);
3866                 }
3867                 continue;
3868             }
3869
3870             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3871                  started == STARTED_HOLDINGS ||
3872                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3873                 /* Accumulate characters in move list or board */
3874                 parse[parse_pos++] = buf[i];
3875             }
3876
3877             /* Start of game messages.  Mostly we detect start of game
3878                when the first board image arrives.  On some versions
3879                of the ICS, though, we need to do a "refresh" after starting
3880                to observe in order to get the current board right away. */
3881             if (looking_at(buf, &i, "Adding game * to observation list")) {
3882                 started = STARTED_OBSERVE;
3883                 continue;
3884             }
3885
3886             /* Handle auto-observe */
3887             if (appData.autoObserve &&
3888                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3889                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3890                 char *player;
3891                 /* Choose the player that was highlighted, if any. */
3892                 if (star_match[0][0] == '\033' ||
3893                     star_match[1][0] != '\033') {
3894                     player = star_match[0];
3895                 } else {
3896                     player = star_match[2];
3897                 }
3898                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3899                         ics_prefix, StripHighlightAndTitle(player));
3900                 SendToICS(str);
3901
3902                 /* Save ratings from notify string */
3903                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3904                 player1Rating = string_to_rating(star_match[1]);
3905                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3906                 player2Rating = string_to_rating(star_match[3]);
3907
3908                 if (appData.debugMode)
3909                   fprintf(debugFP,
3910                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3911                           player1Name, player1Rating,
3912                           player2Name, player2Rating);
3913
3914                 continue;
3915             }
3916
3917             /* Deal with automatic examine mode after a game,
3918                and with IcsObserving -> IcsExamining transition */
3919             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3920                 looking_at(buf, &i, "has made you an examiner of game *")) {
3921
3922                 int gamenum = atoi(star_match[0]);
3923                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3924                     gamenum == ics_gamenum) {
3925                     /* We were already playing or observing this game;
3926                        no need to refetch history */
3927                     gameMode = IcsExamining;
3928                     if (pausing) {
3929                         pauseExamForwardMostMove = forwardMostMove;
3930                     } else if (currentMove < forwardMostMove) {
3931                         ForwardInner(forwardMostMove);
3932                     }
3933                 } else {
3934                     /* I don't think this case really can happen */
3935                     SendToICS(ics_prefix);
3936                     SendToICS("refresh\n");
3937                 }
3938                 continue;
3939             }
3940
3941             /* Error messages */
3942 //          if (ics_user_moved) {
3943             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3944                 if (looking_at(buf, &i, "Illegal move") ||
3945                     looking_at(buf, &i, "Not a legal move") ||
3946                     looking_at(buf, &i, "Your king is in check") ||
3947                     looking_at(buf, &i, "It isn't your turn") ||
3948                     looking_at(buf, &i, "It is not your move")) {
3949                     /* Illegal move */
3950                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3951                         currentMove = forwardMostMove-1;
3952                         DisplayMove(currentMove - 1); /* before DMError */
3953                         DrawPosition(FALSE, boards[currentMove]);
3954                         SwitchClocks(forwardMostMove-1); // [HGM] race
3955                         DisplayBothClocks();
3956                     }
3957                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3958                     ics_user_moved = 0;
3959                     continue;
3960                 }
3961             }
3962
3963             if (looking_at(buf, &i, "still have time") ||
3964                 looking_at(buf, &i, "not out of time") ||
3965                 looking_at(buf, &i, "either player is out of time") ||
3966                 looking_at(buf, &i, "has timeseal; checking")) {
3967                 /* We must have called his flag a little too soon */
3968                 whiteFlag = blackFlag = FALSE;
3969                 continue;
3970             }
3971
3972             if (looking_at(buf, &i, "added * seconds to") ||
3973                 looking_at(buf, &i, "seconds were added to")) {
3974                 /* Update the clocks */
3975                 SendToICS(ics_prefix);
3976                 SendToICS("refresh\n");
3977                 continue;
3978             }
3979
3980             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3981                 ics_clock_paused = TRUE;
3982                 StopClocks();
3983                 continue;
3984             }
3985
3986             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3987                 ics_clock_paused = FALSE;
3988                 StartClocks();
3989                 continue;
3990             }
3991
3992             /* Grab player ratings from the Creating: message.
3993                Note we have to check for the special case when
3994                the ICS inserts things like [white] or [black]. */
3995             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3996                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3997                 /* star_matches:
3998                    0    player 1 name (not necessarily white)
3999                    1    player 1 rating
4000                    2    empty, white, or black (IGNORED)
4001                    3    player 2 name (not necessarily black)
4002                    4    player 2 rating
4003
4004                    The names/ratings are sorted out when the game
4005                    actually starts (below).
4006                 */
4007                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4008                 player1Rating = string_to_rating(star_match[1]);
4009                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4010                 player2Rating = string_to_rating(star_match[4]);
4011
4012                 if (appData.debugMode)
4013                   fprintf(debugFP,
4014                           "Ratings from 'Creating:' %s %d, %s %d\n",
4015                           player1Name, player1Rating,
4016                           player2Name, player2Rating);
4017
4018                 continue;
4019             }
4020
4021             /* Improved generic start/end-of-game messages */
4022             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4023                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4024                 /* If tkind == 0: */
4025                 /* star_match[0] is the game number */
4026                 /*           [1] is the white player's name */
4027                 /*           [2] is the black player's name */
4028                 /* For end-of-game: */
4029                 /*           [3] is the reason for the game end */
4030                 /*           [4] is a PGN end game-token, preceded by " " */
4031                 /* For start-of-game: */
4032                 /*           [3] begins with "Creating" or "Continuing" */
4033                 /*           [4] is " *" or empty (don't care). */
4034                 int gamenum = atoi(star_match[0]);
4035                 char *whitename, *blackname, *why, *endtoken;
4036                 ChessMove endtype = EndOfFile;
4037
4038                 if (tkind == 0) {
4039                   whitename = star_match[1];
4040                   blackname = star_match[2];
4041                   why = star_match[3];
4042                   endtoken = star_match[4];
4043                 } else {
4044                   whitename = star_match[1];
4045                   blackname = star_match[3];
4046                   why = star_match[5];
4047                   endtoken = star_match[6];
4048                 }
4049
4050                 /* Game start messages */
4051                 if (strncmp(why, "Creating ", 9) == 0 ||
4052                     strncmp(why, "Continuing ", 11) == 0) {
4053                     gs_gamenum = gamenum;
4054                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4055                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4056                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4057 #if ZIPPY
4058                     if (appData.zippyPlay) {
4059                         ZippyGameStart(whitename, blackname);
4060                     }
4061 #endif /*ZIPPY*/
4062                     partnerBoardValid = FALSE; // [HGM] bughouse
4063                     continue;
4064                 }
4065
4066                 /* Game end messages */
4067                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4068                     ics_gamenum != gamenum) {
4069                     continue;
4070                 }
4071                 while (endtoken[0] == ' ') endtoken++;
4072                 switch (endtoken[0]) {
4073                   case '*':
4074                   default:
4075                     endtype = GameUnfinished;
4076                     break;
4077                   case '0':
4078                     endtype = BlackWins;
4079                     break;
4080                   case '1':
4081                     if (endtoken[1] == '/')
4082                       endtype = GameIsDrawn;
4083                     else
4084                       endtype = WhiteWins;
4085                     break;
4086                 }
4087                 GameEnds(endtype, why, GE_ICS);
4088 #if ZIPPY
4089                 if (appData.zippyPlay && first.initDone) {
4090                     ZippyGameEnd(endtype, why);
4091                     if (first.pr == NoProc) {
4092                       /* Start the next process early so that we'll
4093                          be ready for the next challenge */
4094                       StartChessProgram(&first);
4095                     }
4096                     /* Send "new" early, in case this command takes
4097                        a long time to finish, so that we'll be ready
4098                        for the next challenge. */
4099                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4100                     Reset(TRUE, TRUE);
4101                 }
4102 #endif /*ZIPPY*/
4103                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4104                 continue;
4105             }
4106
4107             if (looking_at(buf, &i, "Removing game * from observation") ||
4108                 looking_at(buf, &i, "no longer observing game *") ||
4109                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4110                 if (gameMode == IcsObserving &&
4111                     atoi(star_match[0]) == ics_gamenum)
4112                   {
4113                       /* icsEngineAnalyze */
4114                       if (appData.icsEngineAnalyze) {
4115                             ExitAnalyzeMode();
4116                             ModeHighlight();
4117                       }
4118                       StopClocks();
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             if (looking_at(buf, &i, "no longer examining game *")) {
4127                 if (gameMode == IcsExamining &&
4128                     atoi(star_match[0]) == ics_gamenum)
4129                   {
4130                       gameMode = IcsIdle;
4131                       ics_gamenum = -1;
4132                       ics_user_moved = FALSE;
4133                   }
4134                 continue;
4135             }
4136
4137             /* Advance leftover_start past any newlines we find,
4138                so only partial lines can get reparsed */
4139             if (looking_at(buf, &i, "\n")) {
4140                 prevColor = curColor;
4141                 if (curColor != ColorNormal) {
4142                     if (oldi > next_out) {
4143                         SendToPlayer(&buf[next_out], oldi - next_out);
4144                         next_out = oldi;
4145                     }
4146                     Colorize(ColorNormal, FALSE);
4147                     curColor = ColorNormal;
4148                 }
4149                 if (started == STARTED_BOARD) {
4150                     started = STARTED_NONE;
4151                     parse[parse_pos] = NULLCHAR;
4152                     ParseBoard12(parse);
4153                     ics_user_moved = 0;
4154
4155                     /* Send premove here */
4156                     if (appData.premove) {
4157                       char str[MSG_SIZ];
4158                       if (currentMove == 0 &&
4159                           gameMode == IcsPlayingWhite &&
4160                           appData.premoveWhite) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (currentMove == 1 &&
4166                                  gameMode == IcsPlayingBlack &&
4167                                  appData.premoveBlack) {
4168                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                         SendToICS(str);
4172                       } else if (gotPremove) {
4173                         int oldFMM = forwardMostMove;
4174                         gotPremove = 0;
4175                         ClearPremoveHighlights();
4176                         if (appData.debugMode)
4177                           fprintf(debugFP, "Sending premove:\n");
4178                           UserMoveEvent(premoveFromX, premoveFromY,
4179                                         premoveToX, premoveToY,
4180                                         premovePromoChar);
4181                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4182                           if(moveList[oldFMM-1][1] != '@')
4183                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4184                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4185                           else // (drop)
4186                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                         }
4188                       }
4189                     }
4190
4191                     /* Usually suppress following prompt */
4192                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4193                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4194                         if (looking_at(buf, &i, "*% ")) {
4195                             savingComment = FALSE;
4196                             suppressKibitz = 0;
4197                         }
4198                     }
4199                     next_out = i;
4200                 } else if (started == STARTED_HOLDINGS) {
4201                     int gamenum;
4202                     char new_piece[MSG_SIZ];
4203                     started = STARTED_NONE;
4204                     parse[parse_pos] = NULLCHAR;
4205                     if (appData.debugMode)
4206                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4207                                                         parse, currentMove);
4208                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4209                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4210                         if (gameInfo.variant == VariantNormal) {
4211                           /* [HGM] We seem to switch variant during a game!
4212                            * Presumably no holdings were displayed, so we have
4213                            * to move the position two files to the right to
4214                            * create room for them!
4215                            */
4216                           VariantClass newVariant;
4217                           switch(gameInfo.boardWidth) { // base guess on board width
4218                                 case 9:  newVariant = VariantShogi; break;
4219                                 case 10: newVariant = VariantGreat; break;
4220                                 default: newVariant = VariantCrazyhouse; break;
4221                           }
4222                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4223                           /* Get a move list just to see the header, which
4224                              will tell us whether this is really bug or zh */
4225                           if (ics_getting_history == H_FALSE) {
4226                             ics_getting_history = H_REQUESTED;
4227                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4228                             SendToICS(str);
4229                           }
4230                         }
4231                         new_piece[0] = NULLCHAR;
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to board holdings area */
4238                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4239                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4240                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4241 #if ZIPPY
4242                         if (appData.zippyPlay && first.initDone) {
4243                             ZippyHoldings(white_holding, black_holding,
4244                                           new_piece);
4245                         }
4246 #endif /*ZIPPY*/
4247                         if (tinyLayout || smallLayout) {
4248                             char wh[16], bh[16];
4249                             PackHolding(wh, white_holding);
4250                             PackHolding(bh, black_holding);
4251                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4252                                     gameInfo.white, gameInfo.black);
4253                         } else {
4254                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4255                                     gameInfo.white, white_holding, _("vs."),
4256                                     gameInfo.black, black_holding);
4257                         }
4258                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4259                         DrawPosition(FALSE, boards[currentMove]);
4260                         DisplayTitle(str);
4261                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4262                         sscanf(parse, "game %d white [%s black [%s <- %s",
4263                                &gamenum, white_holding, black_holding,
4264                                new_piece);
4265                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4266                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4267                         /* [HGM] copy holdings to partner-board holdings area */
4268                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4269                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4270                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4271                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4273                       }
4274                     }
4275                     /* Suppress following prompt */
4276                     if (looking_at(buf, &i, "*% ")) {
4277                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4278                         savingComment = FALSE;
4279                         suppressKibitz = 0;
4280                     }
4281                     next_out = i;
4282                 }
4283                 continue;
4284             }
4285
4286             i++;                /* skip unparsed character and loop back */
4287         }
4288
4289         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4290 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4291 //          SendToPlayer(&buf[next_out], i - next_out);
4292             started != STARTED_HOLDINGS && leftover_start > next_out) {
4293             SendToPlayer(&buf[next_out], leftover_start - next_out);
4294             next_out = i;
4295         }
4296
4297         leftover_len = buf_len - leftover_start;
4298         /* if buffer ends with something we couldn't parse,
4299            reparse it after appending the next read */
4300
4301     } else if (count == 0) {
4302         RemoveInputSource(isr);
4303         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4304     } else {
4305         DisplayFatalError(_("Error reading from ICS"), error, 1);
4306     }
4307 }
4308
4309
4310 /* Board style 12 looks like this:
4311
4312    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4313
4314  * The "<12> " is stripped before it gets to this routine.  The two
4315  * trailing 0's (flip state and clock ticking) are later addition, and
4316  * some chess servers may not have them, or may have only the first.
4317  * Additional trailing fields may be added in the future.
4318  */
4319
4320 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4321
4322 #define RELATION_OBSERVING_PLAYED    0
4323 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4324 #define RELATION_PLAYING_MYMOVE      1
4325 #define RELATION_PLAYING_NOTMYMOVE  -1
4326 #define RELATION_EXAMINING           2
4327 #define RELATION_ISOLATED_BOARD     -3
4328 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4329
4330 void
4331 ParseBoard12 (char *string)
4332 {
4333 #if ZIPPY
4334     int i, takeback;
4335     char *bookHit = NULL; // [HGM] book
4336 #endif
4337     GameMode newGameMode;
4338     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4339     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4340     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4341     char to_play, board_chars[200];
4342     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4343     char black[32], white[32];
4344     Board board;
4345     int prevMove = currentMove;
4346     int ticking = 2;
4347     ChessMove moveType;
4348     int fromX, fromY, toX, toY;
4349     char promoChar;
4350     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4351     Boolean weird = FALSE, reqFlag = FALSE;
4352
4353     fromX = fromY = toX = toY = -1;
4354
4355     newGame = FALSE;
4356
4357     if (appData.debugMode)
4358       fprintf(debugFP, "Parsing board: %s\n", string);
4359
4360     move_str[0] = NULLCHAR;
4361     elapsed_time[0] = NULLCHAR;
4362     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4363         int  i = 0, j;
4364         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4365             if(string[i] == ' ') { ranks++; files = 0; }
4366             else files++;
4367             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4368             i++;
4369         }
4370         for(j = 0; j <i; j++) board_chars[j] = string[j];
4371         board_chars[i] = '\0';
4372         string += i + 1;
4373     }
4374     n = sscanf(string, PATTERN, &to_play, &double_push,
4375                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4376                &gamenum, white, black, &relation, &basetime, &increment,
4377                &white_stren, &black_stren, &white_time, &black_time,
4378                &moveNum, str, elapsed_time, move_str, &ics_flip,
4379                &ticking);
4380
4381     if (n < 21) {
4382         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4383         DisplayError(str, 0);
4384         return;
4385     }
4386
4387     /* Convert the move number to internal form */
4388     moveNum = (moveNum - 1) * 2;
4389     if (to_play == 'B') moveNum++;
4390     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4391       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4392                         0, 1);
4393       return;
4394     }
4395
4396     switch (relation) {
4397       case RELATION_OBSERVING_PLAYED:
4398       case RELATION_OBSERVING_STATIC:
4399         if (gamenum == -1) {
4400             /* Old ICC buglet */
4401             relation = RELATION_OBSERVING_STATIC;
4402         }
4403         newGameMode = IcsObserving;
4404         break;
4405       case RELATION_PLAYING_MYMOVE:
4406       case RELATION_PLAYING_NOTMYMOVE:
4407         newGameMode =
4408           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4409             IcsPlayingWhite : IcsPlayingBlack;
4410         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4411         break;
4412       case RELATION_EXAMINING:
4413         newGameMode = IcsExamining;
4414         break;
4415       case RELATION_ISOLATED_BOARD:
4416       default:
4417         /* Just display this board.  If user was doing something else,
4418            we will forget about it until the next board comes. */
4419         newGameMode = IcsIdle;
4420         break;
4421       case RELATION_STARTING_POSITION:
4422         newGameMode = gameMode;
4423         break;
4424     }
4425
4426     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4427         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4428          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4429       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4430       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4431       static int lastBgGame = -1;
4432       char *toSqr;
4433       for (k = 0; k < ranks; k++) {
4434         for (j = 0; j < files; j++)
4435           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436         if(gameInfo.holdingsWidth > 1) {
4437              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439         }
4440       }
4441       CopyBoard(partnerBoard, board);
4442       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4443         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4444         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4445       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4446       if(toSqr = strchr(str, '-')) {
4447         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4448         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4449       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4450       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4451       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4452       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4453       if(twoBoards) {
4454           DisplayWhiteClock(white_time*fac, to_play == 'W');
4455           DisplayBlackClock(black_time*fac, to_play != 'W');
4456           activePartner = to_play;
4457           if(gamenum != lastBgGame) {
4458               char buf[MSG_SIZ];
4459               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4460               DisplayTitle(buf);
4461           }
4462           lastBgGame = gamenum;
4463           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4464                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4465       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4466                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4467       if(!twoBoards) DisplayMessage(partnerStatus, "");
4468         partnerBoardValid = TRUE;
4469       return;
4470     }
4471
4472     if(appData.dualBoard && appData.bgObserve) {
4473         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4474             SendToICS(ics_prefix), SendToICS("pobserve\n");
4475         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4476             char buf[MSG_SIZ];
4477             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4478             SendToICS(buf);
4479         }
4480     }
4481
4482     /* Modify behavior for initial board display on move listing
4483        of wild games.
4484        */
4485     switch (ics_getting_history) {
4486       case H_FALSE:
4487       case H_REQUESTED:
4488         break;
4489       case H_GOT_REQ_HEADER:
4490       case H_GOT_UNREQ_HEADER:
4491         /* This is the initial position of the current game */
4492         gamenum = ics_gamenum;
4493         moveNum = 0;            /* old ICS bug workaround */
4494         if (to_play == 'B') {
4495           startedFromSetupPosition = TRUE;
4496           blackPlaysFirst = TRUE;
4497           moveNum = 1;
4498           if (forwardMostMove == 0) forwardMostMove = 1;
4499           if (backwardMostMove == 0) backwardMostMove = 1;
4500           if (currentMove == 0) currentMove = 1;
4501         }
4502         newGameMode = gameMode;
4503         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4504         break;
4505       case H_GOT_UNWANTED_HEADER:
4506         /* This is an initial board that we don't want */
4507         return;
4508       case H_GETTING_MOVES:
4509         /* Should not happen */
4510         DisplayError(_("Error gathering move list: extra board"), 0);
4511         ics_getting_history = H_FALSE;
4512         return;
4513     }
4514
4515    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4516                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4517                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4518      /* [HGM] We seem to have switched variant unexpectedly
4519       * Try to guess new variant from board size
4520       */
4521           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4522           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4523           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4524           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4525           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4526           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4527           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4528           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4529           /* Get a move list just to see the header, which
4530              will tell us whether this is really bug or zh */
4531           if (ics_getting_history == H_FALSE) {
4532             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535           }
4536     }
4537
4538     /* Take action if this is the first board of a new game, or of a
4539        different game than is currently being displayed.  */
4540     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4541         relation == RELATION_ISOLATED_BOARD) {
4542
4543         /* Forget the old game and get the history (if any) of the new one */
4544         if (gameMode != BeginningOfGame) {
4545           Reset(TRUE, TRUE);
4546         }
4547         newGame = TRUE;
4548         if (appData.autoRaiseBoard) BoardToTop();
4549         prevMove = -3;
4550         if (gamenum == -1) {
4551             newGameMode = IcsIdle;
4552         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4553                    appData.getMoveList && !reqFlag) {
4554             /* Need to get game history */
4555             ics_getting_history = H_REQUESTED;
4556             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4557             SendToICS(str);
4558         }
4559
4560         /* Initially flip the board to have black on the bottom if playing
4561            black or if the ICS flip flag is set, but let the user change
4562            it with the Flip View button. */
4563         flipView = appData.autoFlipView ?
4564           (newGameMode == IcsPlayingBlack) || ics_flip :
4565           appData.flipView;
4566
4567         /* Done with values from previous mode; copy in new ones */
4568         gameMode = newGameMode;
4569         ModeHighlight();
4570         ics_gamenum = gamenum;
4571         if (gamenum == gs_gamenum) {
4572             int klen = strlen(gs_kind);
4573             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4574             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4575             gameInfo.event = StrSave(str);
4576         } else {
4577             gameInfo.event = StrSave("ICS game");
4578         }
4579         gameInfo.site = StrSave(appData.icsHost);
4580         gameInfo.date = PGNDate();
4581         gameInfo.round = StrSave("-");
4582         gameInfo.white = StrSave(white);
4583         gameInfo.black = StrSave(black);
4584         timeControl = basetime * 60 * 1000;
4585         timeControl_2 = 0;
4586         timeIncrement = increment * 1000;
4587         movesPerSession = 0;
4588         gameInfo.timeControl = TimeControlTagValue();
4589         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4590   if (appData.debugMode) {
4591     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4592     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4593     setbuf(debugFP, NULL);
4594   }
4595
4596         gameInfo.outOfBook = NULL;
4597
4598         /* Do we have the ratings? */
4599         if (strcmp(player1Name, white) == 0 &&
4600             strcmp(player2Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player1Rating, player2Rating);
4604             gameInfo.whiteRating = player1Rating;
4605             gameInfo.blackRating = player2Rating;
4606         } else if (strcmp(player2Name, white) == 0 &&
4607                    strcmp(player1Name, black) == 0) {
4608             if (appData.debugMode)
4609               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4610                       player2Rating, player1Rating);
4611             gameInfo.whiteRating = player2Rating;
4612             gameInfo.blackRating = player1Rating;
4613         }
4614         player1Name[0] = player2Name[0] = NULLCHAR;
4615
4616         /* Silence shouts if requested */
4617         if (appData.quietPlay &&
4618             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4619             SendToICS(ics_prefix);
4620             SendToICS("set shout 0\n");
4621         }
4622     }
4623
4624     /* Deal with midgame name changes */
4625     if (!newGame) {
4626         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4627             if (gameInfo.white) free(gameInfo.white);
4628             gameInfo.white = StrSave(white);
4629         }
4630         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4631             if (gameInfo.black) free(gameInfo.black);
4632             gameInfo.black = StrSave(black);
4633         }
4634     }
4635
4636     /* Throw away game result if anything actually changes in examine mode */
4637     if (gameMode == IcsExamining && !newGame) {
4638         gameInfo.result = GameUnfinished;
4639         if (gameInfo.resultDetails != NULL) {
4640             free(gameInfo.resultDetails);
4641             gameInfo.resultDetails = NULL;
4642         }
4643     }
4644
4645     /* In pausing && IcsExamining mode, we ignore boards coming
4646        in if they are in a different variation than we are. */
4647     if (pauseExamInvalid) return;
4648     if (pausing && gameMode == IcsExamining) {
4649         if (moveNum <= pauseExamForwardMostMove) {
4650             pauseExamInvalid = TRUE;
4651             forwardMostMove = pauseExamForwardMostMove;
4652             return;
4653         }
4654     }
4655
4656   if (appData.debugMode) {
4657     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4658   }
4659     /* Parse the board */
4660     for (k = 0; k < ranks; k++) {
4661       for (j = 0; j < files; j++)
4662         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4663       if(gameInfo.holdingsWidth > 1) {
4664            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4665            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4666       }
4667     }
4668     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4669       board[5][BOARD_RGHT+1] = WhiteAngel;
4670       board[6][BOARD_RGHT+1] = WhiteMarshall;
4671       board[1][0] = BlackMarshall;
4672       board[2][0] = BlackAngel;
4673       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4674     }
4675     CopyBoard(boards[moveNum], board);
4676     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4677     if (moveNum == 0) {
4678         startedFromSetupPosition =
4679           !CompareBoards(board, initialPosition);
4680         if(startedFromSetupPosition)
4681             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4682     }
4683
4684     /* [HGM] Set castling rights. Take the outermost Rooks,
4685        to make it also work for FRC opening positions. Note that board12
4686        is really defective for later FRC positions, as it has no way to
4687        indicate which Rook can castle if they are on the same side of King.
4688        For the initial position we grant rights to the outermost Rooks,
4689        and remember thos rights, and we then copy them on positions
4690        later in an FRC game. This means WB might not recognize castlings with
4691        Rooks that have moved back to their original position as illegal,
4692        but in ICS mode that is not its job anyway.
4693     */
4694     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4695     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4696
4697         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4698             if(board[0][i] == WhiteRook) j = i;
4699         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4700         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4701             if(board[0][i] == WhiteRook) j = i;
4702         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4703         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4705         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4706         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4707             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4708         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4709
4710         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4711         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4715             if(board[BOARD_HEIGHT-1][k] == bKing)
4716                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4717         if(gameInfo.variant == VariantTwoKings) {
4718             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4719             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4720             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4721         }
4722     } else { int r;
4723         r = boards[moveNum][CASTLING][0] = initialRights[0];
4724         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4725         r = boards[moveNum][CASTLING][1] = initialRights[1];
4726         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4727         r = boards[moveNum][CASTLING][3] = initialRights[3];
4728         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4729         r = boards[moveNum][CASTLING][4] = initialRights[4];
4730         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4731         /* wildcastle kludge: always assume King has rights */
4732         r = boards[moveNum][CASTLING][2] = initialRights[2];
4733         r = boards[moveNum][CASTLING][5] = initialRights[5];
4734     }
4735     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4736     boards[moveNum][EP_STATUS] = EP_NONE;
4737     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4738     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4739     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4740
4741
4742     if (ics_getting_history == H_GOT_REQ_HEADER ||
4743         ics_getting_history == H_GOT_UNREQ_HEADER) {
4744         /* This was an initial position from a move list, not
4745            the current position */
4746         return;
4747     }
4748
4749     /* Update currentMove and known move number limits */
4750     newMove = newGame || moveNum > forwardMostMove;
4751
4752     if (newGame) {
4753         forwardMostMove = backwardMostMove = currentMove = moveNum;
4754         if (gameMode == IcsExamining && moveNum == 0) {
4755           /* Workaround for ICS limitation: we are not told the wild
4756              type when starting to examine a game.  But if we ask for
4757              the move list, the move list header will tell us */
4758             ics_getting_history = H_REQUESTED;
4759             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4760             SendToICS(str);
4761         }
4762     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4763                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4764 #if ZIPPY
4765         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4766         /* [HGM] applied this also to an engine that is silently watching        */
4767         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4768             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4769             gameInfo.variant == currentlyInitializedVariant) {
4770           takeback = forwardMostMove - moveNum;
4771           for (i = 0; i < takeback; i++) {
4772             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4773             SendToProgram("undo\n", &first);
4774           }
4775         }
4776 #endif
4777
4778         forwardMostMove = moveNum;
4779         if (!pausing || currentMove > forwardMostMove)
4780           currentMove = forwardMostMove;
4781     } else {
4782         /* New part of history that is not contiguous with old part */
4783         if (pausing && gameMode == IcsExamining) {
4784             pauseExamInvalid = TRUE;
4785             forwardMostMove = pauseExamForwardMostMove;
4786             return;
4787         }
4788         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4789 #if ZIPPY
4790             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4791                 // [HGM] when we will receive the move list we now request, it will be
4792                 // fed to the engine from the first move on. So if the engine is not
4793                 // in the initial position now, bring it there.
4794                 InitChessProgram(&first, 0);
4795             }
4796 #endif
4797             ics_getting_history = H_REQUESTED;
4798             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4799             SendToICS(str);
4800         }
4801         forwardMostMove = backwardMostMove = currentMove = moveNum;
4802     }
4803
4804     /* Update the clocks */
4805     if (strchr(elapsed_time, '.')) {
4806       /* Time is in ms */
4807       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4808       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4809     } else {
4810       /* Time is in seconds */
4811       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4812       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4813     }
4814
4815
4816 #if ZIPPY
4817     if (appData.zippyPlay && newGame &&
4818         gameMode != IcsObserving && gameMode != IcsIdle &&
4819         gameMode != IcsExamining)
4820       ZippyFirstBoard(moveNum, basetime, increment);
4821 #endif
4822
4823     /* Put the move on the move list, first converting
4824        to canonical algebraic form. */
4825     if (moveNum > 0) {
4826   if (appData.debugMode) {
4827     int f = forwardMostMove;
4828     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4829             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4830             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4831     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4832     fprintf(debugFP, "moveNum = %d\n", moveNum);
4833     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4834     setbuf(debugFP, NULL);
4835   }
4836         if (moveNum <= backwardMostMove) {
4837             /* We don't know what the board looked like before
4838                this move.  Punt. */
4839           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4840             strcat(parseList[moveNum - 1], " ");
4841             strcat(parseList[moveNum - 1], elapsed_time);
4842             moveList[moveNum - 1][0] = NULLCHAR;
4843         } else if (strcmp(move_str, "none") == 0) {
4844             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4845             /* Again, we don't know what the board looked like;
4846                this is really the start of the game. */
4847             parseList[moveNum - 1][0] = NULLCHAR;
4848             moveList[moveNum - 1][0] = NULLCHAR;
4849             backwardMostMove = moveNum;
4850             startedFromSetupPosition = TRUE;
4851             fromX = fromY = toX = toY = -1;
4852         } else {
4853           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4854           //                 So we parse the long-algebraic move string in stead of the SAN move
4855           int valid; char buf[MSG_SIZ], *prom;
4856
4857           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4858                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4859           // str looks something like "Q/a1-a2"; kill the slash
4860           if(str[1] == '/')
4861             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4862           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4863           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4864                 strcat(buf, prom); // long move lacks promo specification!
4865           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4866                 if(appData.debugMode)
4867                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4868                 safeStrCpy(move_str, buf, MSG_SIZ);
4869           }
4870           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4871                                 &fromX, &fromY, &toX, &toY, &promoChar)
4872                || ParseOneMove(buf, moveNum - 1, &moveType,
4873                                 &fromX, &fromY, &toX, &toY, &promoChar);
4874           // end of long SAN patch
4875           if (valid) {
4876             (void) CoordsToAlgebraic(boards[moveNum - 1],
4877                                      PosFlags(moveNum - 1),
4878                                      fromY, fromX, toY, toX, promoChar,
4879                                      parseList[moveNum-1]);
4880             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4881               case MT_NONE:
4882               case MT_STALEMATE:
4883               default:
4884                 break;
4885               case MT_CHECK:
4886                 if(!IS_SHOGI(gameInfo.variant))
4887                     strcat(parseList[moveNum - 1], "+");
4888                 break;
4889               case MT_CHECKMATE:
4890               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4891                 strcat(parseList[moveNum - 1], "#");
4892                 break;
4893             }
4894             strcat(parseList[moveNum - 1], " ");
4895             strcat(parseList[moveNum - 1], elapsed_time);
4896             /* currentMoveString is set as a side-effect of ParseOneMove */
4897             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4898             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4899             strcat(moveList[moveNum - 1], "\n");
4900
4901             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4902                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4903               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4904                 ChessSquare old, new = boards[moveNum][k][j];
4905                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4906                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4907                   if(old == new) continue;
4908                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4909                   else if(new == WhiteWazir || new == BlackWazir) {
4910                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4911                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4912                       else boards[moveNum][k][j] = old; // preserve type of Gold
4913                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4914                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4915               }
4916           } else {
4917             /* Move from ICS was illegal!?  Punt. */
4918             if (appData.debugMode) {
4919               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4920               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4921             }
4922             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4923             strcat(parseList[moveNum - 1], " ");
4924             strcat(parseList[moveNum - 1], elapsed_time);
4925             moveList[moveNum - 1][0] = NULLCHAR;
4926             fromX = fromY = toX = toY = -1;
4927           }
4928         }
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4931     setbuf(debugFP, NULL);
4932   }
4933
4934 #if ZIPPY
4935         /* Send move to chess program (BEFORE animating it). */
4936         if (appData.zippyPlay && !newGame && newMove &&
4937            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4938
4939             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4940                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4941                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4942                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4943                             move_str);
4944                     DisplayError(str, 0);
4945                 } else {
4946                     if (first.sendTime) {
4947                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4948                     }
4949                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4950                     if (firstMove && !bookHit) {
4951                         firstMove = FALSE;
4952                         if (first.useColors) {
4953                           SendToProgram(gameMode == IcsPlayingWhite ?
4954                                         "white\ngo\n" :
4955                                         "black\ngo\n", &first);
4956                         } else {
4957                           SendToProgram("go\n", &first);
4958                         }
4959                         first.maybeThinking = TRUE;
4960                     }
4961                 }
4962             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4963               if (moveList[moveNum - 1][0] == NULLCHAR) {
4964                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4965                 DisplayError(str, 0);
4966               } else {
4967                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4968                 SendMoveToProgram(moveNum - 1, &first);
4969               }
4970             }
4971         }
4972 #endif
4973     }
4974
4975     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4976         /* If move comes from a remote source, animate it.  If it
4977            isn't remote, it will have already been animated. */
4978         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4979             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4980         }
4981         if (!pausing && appData.highlightLastMove) {
4982             SetHighlights(fromX, fromY, toX, toY);
4983         }
4984     }
4985
4986     /* Start the clocks */
4987     whiteFlag = blackFlag = FALSE;
4988     appData.clockMode = !(basetime == 0 && increment == 0);
4989     if (ticking == 0) {
4990       ics_clock_paused = TRUE;
4991       StopClocks();
4992     } else if (ticking == 1) {
4993       ics_clock_paused = FALSE;
4994     }
4995     if (gameMode == IcsIdle ||
4996         relation == RELATION_OBSERVING_STATIC ||
4997         relation == RELATION_EXAMINING ||
4998         ics_clock_paused)
4999       DisplayBothClocks();
5000     else
5001       StartClocks();
5002
5003     /* Display opponents and material strengths */
5004     if (gameInfo.variant != VariantBughouse &&
5005         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5006         if (tinyLayout || smallLayout) {
5007             if(gameInfo.variant == VariantNormal)
5008               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5009                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5010                     basetime, increment);
5011             else
5012               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5013                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5014                     basetime, increment, (int) gameInfo.variant);
5015         } else {
5016             if(gameInfo.variant == VariantNormal)
5017               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5018                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5019                     basetime, increment);
5020             else
5021               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5022                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023                     basetime, increment, VariantName(gameInfo.variant));
5024         }
5025         DisplayTitle(str);
5026   if (appData.debugMode) {
5027     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5028   }
5029     }
5030
5031
5032     /* Display the board */
5033     if (!pausing && !appData.noGUI) {
5034
5035       if (appData.premove)
5036           if (!gotPremove ||
5037              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5038              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5039               ClearPremoveHighlights();
5040
5041       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5042         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5043       DrawPosition(j, boards[currentMove]);
5044
5045       DisplayMove(moveNum - 1);
5046       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5047             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5048               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5049         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5050       }
5051     }
5052
5053     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5054 #if ZIPPY
5055     if(bookHit) { // [HGM] book: simulate book reply
5056         static char bookMove[MSG_SIZ]; // a bit generous?
5057
5058         programStats.nodes = programStats.depth = programStats.time =
5059         programStats.score = programStats.got_only_move = 0;
5060         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5061
5062         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5063         strcat(bookMove, bookHit);
5064         HandleMachineMove(bookMove, &first);
5065     }
5066 #endif
5067 }
5068
5069 void
5070 GetMoveListEvent ()
5071 {
5072     char buf[MSG_SIZ];
5073     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5074         ics_getting_history = H_REQUESTED;
5075         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5076         SendToICS(buf);
5077     }
5078 }
5079
5080 void
5081 SendToBoth (char *msg)
5082 {   // to make it easy to keep two engines in step in dual analysis
5083     SendToProgram(msg, &first);
5084     if(second.analyzing) SendToProgram(msg, &second);
5085 }
5086
5087 void
5088 AnalysisPeriodicEvent (int force)
5089 {
5090     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5091          && !force) || !appData.periodicUpdates)
5092       return;
5093
5094     /* Send . command to Crafty to collect stats */
5095     SendToBoth(".\n");
5096
5097     /* Don't send another until we get a response (this makes
5098        us stop sending to old Crafty's which don't understand
5099        the "." command (sending illegal cmds resets node count & time,
5100        which looks bad)) */
5101     programStats.ok_to_send = 0;
5102 }
5103
5104 void
5105 ics_update_width (int new_width)
5106 {
5107         ics_printf("set width %d\n", new_width);
5108 }
5109
5110 void
5111 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5112 {
5113     char buf[MSG_SIZ];
5114
5115     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5116         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5117             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5118             SendToProgram(buf, cps);
5119             return;
5120         }
5121         // null move in variant where engine does not understand it (for analysis purposes)
5122         SendBoard(cps, moveNum + 1); // send position after move in stead.
5123         return;
5124     }
5125     if (cps->useUsermove) {
5126       SendToProgram("usermove ", cps);
5127     }
5128     if (cps->useSAN) {
5129       char *space;
5130       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5131         int len = space - parseList[moveNum];
5132         memcpy(buf, parseList[moveNum], len);
5133         buf[len++] = '\n';
5134         buf[len] = NULLCHAR;
5135       } else {
5136         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5137       }
5138       SendToProgram(buf, cps);
5139     } else {
5140       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5141         AlphaRank(moveList[moveNum], 4);
5142         SendToProgram(moveList[moveNum], cps);
5143         AlphaRank(moveList[moveNum], 4); // and back
5144       } else
5145       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5146        * the engine. It would be nice to have a better way to identify castle
5147        * moves here. */
5148       if(appData.fischerCastling && cps->useOOCastle) {
5149         int fromX = moveList[moveNum][0] - AAA;
5150         int fromY = moveList[moveNum][1] - ONE;
5151         int toX = moveList[moveNum][2] - AAA;
5152         int toY = moveList[moveNum][3] - ONE;
5153         if((boards[moveNum][fromY][fromX] == WhiteKing
5154             && boards[moveNum][toY][toX] == WhiteRook)
5155            || (boards[moveNum][fromY][fromX] == BlackKing
5156                && boards[moveNum][toY][toX] == BlackRook)) {
5157           if(toX > fromX) SendToProgram("O-O\n", cps);
5158           else SendToProgram("O-O-O\n", cps);
5159         }
5160         else SendToProgram(moveList[moveNum], cps);
5161       } else
5162       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5163         char *m = moveList[moveNum];
5164         static char c[2];
5165         *c = m[7]; // promoChar
5166         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5168                                                m[2], m[3] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5171         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5172           *c = m[9];
5173           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5174                                                m[7], m[8] - '0',
5175                                                m[7], m[8] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[2], m[3] - '0', c);
5179         } else
5180           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184           SendToProgram(buf, cps);
5185       } else
5186       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5187         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5188           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5189           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5190                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5191         } else
5192           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5193                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5194         SendToProgram(buf, cps);
5195       }
5196       else SendToProgram(moveList[moveNum], cps);
5197       /* End of additions by Tord */
5198     }
5199
5200     /* [HGM] setting up the opening has brought engine in force mode! */
5201     /*       Send 'go' if we are in a mode where machine should play. */
5202     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5203         (gameMode == TwoMachinesPlay   ||
5204 #if ZIPPY
5205          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5206 #endif
5207          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5208         SendToProgram("go\n", cps);
5209   if (appData.debugMode) {
5210     fprintf(debugFP, "(extra)\n");
5211   }
5212     }
5213     setboardSpoiledMachineBlack = 0;
5214 }
5215
5216 void
5217 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5218 {
5219     char user_move[MSG_SIZ];
5220     char suffix[4];
5221
5222     if(gameInfo.variant == VariantSChess && promoChar) {
5223         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5224         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5225     } else suffix[0] = NULLCHAR;
5226
5227     switch (moveType) {
5228       default:
5229         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5230                 (int)moveType, fromX, fromY, toX, toY);
5231         DisplayError(user_move + strlen("say "), 0);
5232         break;
5233       case WhiteKingSideCastle:
5234       case BlackKingSideCastle:
5235       case WhiteQueenSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* PUSH Fabien */
5238       case WhiteHSideCastleFR:
5239       case BlackHSideCastleFR:
5240       /* POP Fabien */
5241         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5242         break;
5243       case WhiteQueenSideCastle:
5244       case BlackQueenSideCastle:
5245       case WhiteKingSideCastleWild:
5246       case BlackKingSideCastleWild:
5247       /* PUSH Fabien */
5248       case WhiteASideCastleFR:
5249       case BlackASideCastleFR:
5250       /* POP Fabien */
5251         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5252         break;
5253       case WhiteNonPromotion:
5254       case BlackNonPromotion:
5255         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5256         break;
5257       case WhitePromotion:
5258       case BlackPromotion:
5259         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5260            gameInfo.variant == VariantMakruk)
5261           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5262                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5263                 PieceToChar(WhiteFerz));
5264         else if(gameInfo.variant == VariantGreat)
5265           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5266                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267                 PieceToChar(WhiteMan));
5268         else
5269           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5271                 promoChar);
5272         break;
5273       case WhiteDrop:
5274       case BlackDrop:
5275       drop:
5276         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5277                  ToUpper(PieceToChar((ChessSquare) fromX)),
5278                  AAA + toX, ONE + toY);
5279         break;
5280       case IllegalMove:  /* could be a variant we don't quite understand */
5281         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5282       case NormalMove:
5283       case WhiteCapturesEnPassant:
5284       case BlackCapturesEnPassant:
5285         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5286                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5287         break;
5288     }
5289     SendToICS(user_move);
5290     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5291         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5292 }
5293
5294 void
5295 UploadGameEvent ()
5296 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5297     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5298     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5299     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5300       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5301       return;
5302     }
5303     if(gameMode != IcsExamining) { // is this ever not the case?
5304         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5305
5306         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5307           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5308         } else { // on FICS we must first go to general examine mode
5309           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5310         }
5311         if(gameInfo.variant != VariantNormal) {
5312             // try figure out wild number, as xboard names are not always valid on ICS
5313             for(i=1; i<=36; i++) {
5314               snprintf(buf, MSG_SIZ, "wild/%d", i);
5315                 if(StringToVariant(buf) == gameInfo.variant) break;
5316             }
5317             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5318             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5319             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5320         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5321         SendToICS(ics_prefix);
5322         SendToICS(buf);
5323         if(startedFromSetupPosition || backwardMostMove != 0) {
5324           fen = PositionToFEN(backwardMostMove, NULL, 1);
5325           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5326             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5327             SendToICS(buf);
5328           } else { // FICS: everything has to set by separate bsetup commands
5329             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5330             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5331             SendToICS(buf);
5332             if(!WhiteOnMove(backwardMostMove)) {
5333                 SendToICS("bsetup tomove black\n");
5334             }
5335             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5336             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5337             SendToICS(buf);
5338             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5339             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5340             SendToICS(buf);
5341             i = boards[backwardMostMove][EP_STATUS];
5342             if(i >= 0) { // set e.p.
5343               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5344                 SendToICS(buf);
5345             }
5346             bsetup++;
5347           }
5348         }
5349       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5350             SendToICS("bsetup done\n"); // switch to normal examining.
5351     }
5352     for(i = backwardMostMove; i<last; i++) {
5353         char buf[20];
5354         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5355         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5356             int len = strlen(moveList[i]);
5357             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5358             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5359         }
5360         SendToICS(buf);
5361     }
5362     SendToICS(ics_prefix);
5363     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5364 }
5365
5366 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5367 int legNr = 1;
5368
5369 void
5370 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5371 {
5372     if (rf == DROP_RANK) {
5373       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5374       sprintf(move, "%c@%c%c\n",
5375                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5376     } else {
5377         if (promoChar == 'x' || promoChar == NULLCHAR) {
5378           sprintf(move, "%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5383           }
5384         } else {
5385             sprintf(move, "%c%c%c%c%c\n",
5386                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5387           if(killX >= 0 && killY >= 0) {
5388             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5389             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5390           }
5391         }
5392     }
5393 }
5394
5395 void
5396 ProcessICSInitScript (FILE *f)
5397 {
5398     char buf[MSG_SIZ];
5399
5400     while (fgets(buf, MSG_SIZ, f)) {
5401         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5402     }
5403
5404     fclose(f);
5405 }
5406
5407
5408 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5409 int dragging;
5410 static ClickType lastClickType;
5411
5412 int
5413 PieceInString (char *s, ChessSquare piece)
5414 {
5415   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5416   while((p = strchr(s, ID))) {
5417     if(!suffix || p[1] == suffix) return TRUE;
5418     s = p;
5419   }
5420   return FALSE;
5421 }
5422
5423 int
5424 Partner (ChessSquare *p)
5425 { // change piece into promotion partner if one shogi-promotes to the other
5426   ChessSquare partner = promoPartner[*p];
5427   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5428   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5429   *p = partner;
5430   return 1;
5431 }
5432
5433 void
5434 Sweep (int step)
5435 {
5436     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5437     static int toggleFlag;
5438     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5439     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5440     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5441     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5442     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5443     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5444     do {
5445         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5446         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5447         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5448         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5449         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5450         if(!step) step = -1;
5451     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5452             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5453             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5454             promoSweep == pawn ||
5455             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5456             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5457     if(toX >= 0) {
5458         int victim = boards[currentMove][toY][toX];
5459         boards[currentMove][toY][toX] = promoSweep;
5460         DrawPosition(FALSE, boards[currentMove]);
5461         boards[currentMove][toY][toX] = victim;
5462     } else
5463     ChangeDragPiece(promoSweep);
5464 }
5465
5466 int
5467 PromoScroll (int x, int y)
5468 {
5469   int step = 0;
5470
5471   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5472   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5473   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5474   if(!step) return FALSE;
5475   lastX = x; lastY = y;
5476   if((promoSweep < BlackPawn) == flipView) step = -step;
5477   if(step > 0) selectFlag = 1;
5478   if(!selectFlag) Sweep(step);
5479   return FALSE;
5480 }
5481
5482 void
5483 NextPiece (int step)
5484 {
5485     ChessSquare piece = boards[currentMove][toY][toX];
5486     do {
5487         pieceSweep -= step;
5488         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5489         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5490         if(!step) step = -1;
5491     } while(PieceToChar(pieceSweep) == '.');
5492     boards[currentMove][toY][toX] = pieceSweep;
5493     DrawPosition(FALSE, boards[currentMove]);
5494     boards[currentMove][toY][toX] = piece;
5495 }
5496 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5497 void
5498 AlphaRank (char *move, int n)
5499 {
5500 //    char *p = move, c; int x, y;
5501
5502     if (appData.debugMode) {
5503         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5504     }
5505
5506     if(move[1]=='*' &&
5507        move[2]>='0' && move[2]<='9' &&
5508        move[3]>='a' && move[3]<='x'    ) {
5509         move[1] = '@';
5510         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5511         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5512     } else
5513     if(move[0]>='0' && move[0]<='9' &&
5514        move[1]>='a' && move[1]<='x' &&
5515        move[2]>='0' && move[2]<='9' &&
5516        move[3]>='a' && move[3]<='x'    ) {
5517         /* input move, Shogi -> normal */
5518         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5519         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5520         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5522     } else
5523     if(move[1]=='@' &&
5524        move[3]>='0' && move[3]<='9' &&
5525        move[2]>='a' && move[2]<='x'    ) {
5526         move[1] = '*';
5527         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5528         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5529     } else
5530     if(
5531        move[0]>='a' && move[0]<='x' &&
5532        move[3]>='0' && move[3]<='9' &&
5533        move[2]>='a' && move[2]<='x'    ) {
5534          /* output move, normal -> Shogi */
5535         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5536         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5537         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5538         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5539         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5540     }
5541     if (appData.debugMode) {
5542         fprintf(debugFP, "   out = '%s'\n", move);
5543     }
5544 }
5545
5546 char yy_textstr[8000];
5547
5548 /* Parser for moves from gnuchess, ICS, or user typein box */
5549 Boolean
5550 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5551 {
5552     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5553
5554     switch (*moveType) {
5555       case WhitePromotion:
5556       case BlackPromotion:
5557       case WhiteNonPromotion:
5558       case BlackNonPromotion:
5559       case NormalMove:
5560       case FirstLeg:
5561       case WhiteCapturesEnPassant:
5562       case BlackCapturesEnPassant:
5563       case WhiteKingSideCastle:
5564       case WhiteQueenSideCastle:
5565       case BlackKingSideCastle:
5566       case BlackQueenSideCastle:
5567       case WhiteKingSideCastleWild:
5568       case WhiteQueenSideCastleWild:
5569       case BlackKingSideCastleWild:
5570       case BlackQueenSideCastleWild:
5571       /* Code added by Tord: */
5572       case WhiteHSideCastleFR:
5573       case WhiteASideCastleFR:
5574       case BlackHSideCastleFR:
5575       case BlackASideCastleFR:
5576       /* End of code added by Tord */
5577       case IllegalMove:         /* bug or odd chess variant */
5578         if(currentMoveString[1] == '@') { // illegal drop
5579           *fromX = WhiteOnMove(moveNum) ?
5580             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5581             (int) CharToPiece(ToLower(currentMoveString[0]));
5582           goto drop;
5583         }
5584         *fromX = currentMoveString[0] - AAA;
5585         *fromY = currentMoveString[1] - ONE;
5586         *toX = currentMoveString[2] - AAA;
5587         *toY = currentMoveString[3] - ONE;
5588         *promoChar = currentMoveString[4];
5589         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5590         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5591             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5592     if (appData.debugMode) {
5593         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5594     }
5595             *fromX = *fromY = *toX = *toY = 0;
5596             return FALSE;
5597         }
5598         if (appData.testLegality) {
5599           return (*moveType != IllegalMove);
5600         } else {
5601           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5602                          // [HGM] lion: if this is a double move we are less critical
5603                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5604         }
5605
5606       case WhiteDrop:
5607       case BlackDrop:
5608         *fromX = *moveType == WhiteDrop ?
5609           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5610           (int) CharToPiece(ToLower(currentMoveString[0]));
5611       drop:
5612         *fromY = DROP_RANK;
5613         *toX = currentMoveString[2] - AAA;
5614         *toY = currentMoveString[3] - ONE;
5615         *promoChar = NULLCHAR;
5616         return TRUE;
5617
5618       case AmbiguousMove:
5619       case ImpossibleMove:
5620       case EndOfFile:
5621       case ElapsedTime:
5622       case Comment:
5623       case PGNTag:
5624       case NAG:
5625       case WhiteWins:
5626       case BlackWins:
5627       case GameIsDrawn:
5628       default:
5629     if (appData.debugMode) {
5630         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5631     }
5632         /* bug? */
5633         *fromX = *fromY = *toX = *toY = 0;
5634         *promoChar = NULLCHAR;
5635         return FALSE;
5636     }
5637 }
5638
5639 Boolean pushed = FALSE;
5640 char *lastParseAttempt;
5641
5642 void
5643 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5644 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5645   int fromX, fromY, toX, toY; char promoChar;
5646   ChessMove moveType;
5647   Boolean valid;
5648   int nr = 0;
5649
5650   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5651   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5652     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5653     pushed = TRUE;
5654   }
5655   endPV = forwardMostMove;
5656   do {
5657     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5658     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5659     lastParseAttempt = pv;
5660     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5661     if(!valid && nr == 0 &&
5662        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5663         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5664         // Hande case where played move is different from leading PV move
5665         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5666         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5667         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5668         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5669           endPV += 2; // if position different, keep this
5670           moveList[endPV-1][0] = fromX + AAA;
5671           moveList[endPV-1][1] = fromY + ONE;
5672           moveList[endPV-1][2] = toX + AAA;
5673           moveList[endPV-1][3] = toY + ONE;
5674           parseList[endPV-1][0] = NULLCHAR;
5675           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5676         }
5677       }
5678     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5679     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5680     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5681     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5682         valid++; // allow comments in PV
5683         continue;
5684     }
5685     nr++;
5686     if(endPV+1 > framePtr) break; // no space, truncate
5687     if(!valid) break;
5688     endPV++;
5689     CopyBoard(boards[endPV], boards[endPV-1]);
5690     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5691     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5692     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5693     CoordsToAlgebraic(boards[endPV - 1],
5694                              PosFlags(endPV - 1),
5695                              fromY, fromX, toY, toX, promoChar,
5696                              parseList[endPV - 1]);
5697   } while(valid);
5698   if(atEnd == 2) return; // used hidden, for PV conversion
5699   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5700   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5701   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5702                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5703   DrawPosition(TRUE, boards[currentMove]);
5704 }
5705
5706 int
5707 MultiPV (ChessProgramState *cps, int kind)
5708 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5709         int i;
5710         for(i=0; i<cps->nrOptions; i++) {
5711             char *s = cps->option[i].name;
5712             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5713             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5714                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5715         }
5716         return -1;
5717 }
5718
5719 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5720 static int multi, pv_margin;
5721 static ChessProgramState *activeCps;
5722
5723 Boolean
5724 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5725 {
5726         int startPV, lineStart, origIndex = index;
5727         char *p, buf2[MSG_SIZ];
5728         ChessProgramState *cps = (pane ? &second : &first);
5729
5730         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5731         lastX = x; lastY = y;
5732         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5733         lineStart = startPV = index;
5734         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5735         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5736         index = startPV;
5737         do{ while(buf[index] && buf[index] != '\n') index++;
5738         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5739         buf[index] = 0;
5740         if(lineStart == 0 && gameMode == AnalyzeMode) {
5741             int n = 0;
5742             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5743             if(n == 0) { // click not on "fewer" or "more"
5744                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5745                     pv_margin = cps->option[multi].value;
5746                     activeCps = cps; // non-null signals margin adjustment
5747                 }
5748             } else if((multi = MultiPV(cps, 1)) >= 0) {
5749                 n += cps->option[multi].value; if(n < 1) n = 1;
5750                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5751                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5752                 cps->option[multi].value = n;
5753                 *start = *end = 0;
5754                 return FALSE;
5755             }
5756         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5757                 ExcludeClick(origIndex - lineStart);
5758                 return FALSE;
5759         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5760                 Collapse(origIndex - lineStart);
5761                 return FALSE;
5762         }
5763         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5764         *start = startPV; *end = index-1;
5765         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5766         return TRUE;
5767 }
5768
5769 char *
5770 PvToSAN (char *pv)
5771 {
5772         static char buf[10*MSG_SIZ];
5773         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5774         *buf = NULLCHAR;
5775         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5776         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5777         for(i = forwardMostMove; i<endPV; i++){
5778             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5779             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5780             k += strlen(buf+k);
5781         }
5782         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5783         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5784         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5785         endPV = savedEnd;
5786         return buf;
5787 }
5788
5789 Boolean
5790 LoadPV (int x, int y)
5791 { // called on right mouse click to load PV
5792   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5793   lastX = x; lastY = y;
5794   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5795   extendGame = FALSE;
5796   return TRUE;
5797 }
5798
5799 void
5800 UnLoadPV ()
5801 {
5802   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5803   if(activeCps) {
5804     if(pv_margin != activeCps->option[multi].value) {
5805       char buf[MSG_SIZ];
5806       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5807       SendToProgram(buf, activeCps);
5808       activeCps->option[multi].value = pv_margin;
5809     }
5810     activeCps = NULL;
5811     return;
5812   }
5813   if(endPV < 0) return;
5814   if(appData.autoCopyPV) CopyFENToClipboard();
5815   endPV = -1;
5816   if(extendGame && currentMove > forwardMostMove) {
5817         Boolean saveAnimate = appData.animate;
5818         if(pushed) {
5819             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5820                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5821             } else storedGames--; // abandon shelved tail of original game
5822         }
5823         pushed = FALSE;
5824         forwardMostMove = currentMove;
5825         currentMove = oldFMM;
5826         appData.animate = FALSE;
5827         ToNrEvent(forwardMostMove);
5828         appData.animate = saveAnimate;
5829   }
5830   currentMove = forwardMostMove;
5831   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5832   ClearPremoveHighlights();
5833   DrawPosition(TRUE, boards[currentMove]);
5834 }
5835
5836 void
5837 MovePV (int x, int y, int h)
5838 { // step through PV based on mouse coordinates (called on mouse move)
5839   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5840
5841   if(activeCps) { // adjusting engine's multi-pv margin
5842     if(x > lastX) pv_margin++; else
5843     if(x < lastX) pv_margin -= (pv_margin > 0);
5844     if(x != lastX) {
5845       char buf[MSG_SIZ];
5846       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5847       DisplayMessage(buf, "");
5848     }
5849     lastX = x;
5850     return;
5851   }
5852   // we must somehow check if right button is still down (might be released off board!)
5853   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5854   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5855   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5856   if(!step) return;
5857   lastX = x; lastY = y;
5858
5859   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5860   if(endPV < 0) return;
5861   if(y < margin) step = 1; else
5862   if(y > h - margin) step = -1;
5863   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5864   currentMove += step;
5865   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5866   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5867                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5868   DrawPosition(FALSE, boards[currentMove]);
5869 }
5870
5871
5872 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5873 // All positions will have equal probability, but the current method will not provide a unique
5874 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5875 #define DARK 1
5876 #define LITE 2
5877 #define ANY 3
5878
5879 int squaresLeft[4];
5880 int piecesLeft[(int)BlackPawn];
5881 int seed, nrOfShuffles;
5882
5883 void
5884 GetPositionNumber ()
5885 {       // sets global variable seed
5886         int i;
5887
5888         seed = appData.defaultFrcPosition;
5889         if(seed < 0) { // randomize based on time for negative FRC position numbers
5890                 for(i=0; i<50; i++) seed += random();
5891                 seed = random() ^ random() >> 8 ^ random() << 8;
5892                 if(seed<0) seed = -seed;
5893         }
5894 }
5895
5896 int
5897 put (Board board, int pieceType, int rank, int n, int shade)
5898 // put the piece on the (n-1)-th empty squares of the given shade
5899 {
5900         int i;
5901
5902         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5903                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5904                         board[rank][i] = (ChessSquare) pieceType;
5905                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5906                         squaresLeft[ANY]--;
5907                         piecesLeft[pieceType]--;
5908                         return i;
5909                 }
5910         }
5911         return -1;
5912 }
5913
5914
5915 void
5916 AddOnePiece (Board board, int pieceType, int rank, int shade)
5917 // calculate where the next piece goes, (any empty square), and put it there
5918 {
5919         int i;
5920
5921         i = seed % squaresLeft[shade];
5922         nrOfShuffles *= squaresLeft[shade];
5923         seed /= squaresLeft[shade];
5924         put(board, pieceType, rank, i, shade);
5925 }
5926
5927 void
5928 AddTwoPieces (Board board, int pieceType, int rank)
5929 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5930 {
5931         int i, n=squaresLeft[ANY], j=n-1, k;
5932
5933         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5934         i = seed % k;  // pick one
5935         nrOfShuffles *= k;
5936         seed /= k;
5937         while(i >= j) i -= j--;
5938         j = n - 1 - j; i += j;
5939         put(board, pieceType, rank, j, ANY);
5940         put(board, pieceType, rank, i, ANY);
5941 }
5942
5943 void
5944 SetUpShuffle (Board board, int number)
5945 {
5946         int i, p, first=1;
5947
5948         GetPositionNumber(); nrOfShuffles = 1;
5949
5950         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5951         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5952         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5953
5954         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5955
5956         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5957             p = (int) board[0][i];
5958             if(p < (int) BlackPawn) piecesLeft[p] ++;
5959             board[0][i] = EmptySquare;
5960         }
5961
5962         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5963             // shuffles restricted to allow normal castling put KRR first
5964             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5965                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5966             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5967                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5968             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5969                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5970             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5971                 put(board, WhiteRook, 0, 0, ANY);
5972             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5973         }
5974
5975         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5976             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5977             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5978                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5979                 while(piecesLeft[p] >= 2) {
5980                     AddOnePiece(board, p, 0, LITE);
5981                     AddOnePiece(board, p, 0, DARK);
5982                 }
5983                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5984             }
5985
5986         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5987             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5988             // but we leave King and Rooks for last, to possibly obey FRC restriction
5989             if(p == (int)WhiteRook) continue;
5990             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5991             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5992         }
5993
5994         // now everything is placed, except perhaps King (Unicorn) and Rooks
5995
5996         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5997             // Last King gets castling rights
5998             while(piecesLeft[(int)WhiteUnicorn]) {
5999                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6000                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6001             }
6002
6003             while(piecesLeft[(int)WhiteKing]) {
6004                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008
6009         } else {
6010             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6011             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6012         }
6013
6014         // Only Rooks can be left; simply place them all
6015         while(piecesLeft[(int)WhiteRook]) {
6016                 i = put(board, WhiteRook, 0, 0, ANY);
6017                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6018                         if(first) {
6019                                 first=0;
6020                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6021                         }
6022                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6023                 }
6024         }
6025         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6026             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6027         }
6028
6029         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6030 }
6031
6032 int
6033 ptclen (const char *s, char *escapes)
6034 {
6035     int n = 0;
6036     if(!*escapes) return strlen(s);
6037     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6038     return n;
6039 }
6040
6041 int
6042 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6043 /* [HGM] moved here from winboard.c because of its general usefulness */
6044 /*       Basically a safe strcpy that uses the last character as King */
6045 {
6046     int result = FALSE; int NrPieces;
6047     unsigned char partner[EmptySquare];
6048
6049     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6050                     && NrPieces >= 12 && !(NrPieces&1)) {
6051         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6052
6053         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6054         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6055             char *p, c=0;
6056             if(map[j] == '/') offs = WhitePBishop - i, j++;
6057             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6058             table[i+offs] = map[j++];
6059             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6060             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6061             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6062         }
6063         table[(int) WhiteKing]  = map[j++];
6064         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6065             char *p, c=0;
6066             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6067             i = WHITE_TO_BLACK ii;
6068             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069             table[i+offs] = map[j++];
6070             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6073         }
6074         table[(int) BlackKing]  = map[j++];
6075
6076
6077         if(*escapes) { // set up promotion pairing
6078             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6079             // pieceToChar entirely filled, so we can look up specified partners
6080             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6081                 int c = table[i];
6082                 if(c == '^' || c == '-') { // has specified partner
6083                     int p;
6084                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6085                     if(c == '^') table[i] = '+';
6086                     if(p < EmptySquare) {
6087                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6088                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6089                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6090                     }
6091                 } else if(c == '*') {
6092                     table[i] = partner[i];
6093                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6094                 }
6095             }
6096         }
6097
6098         result = TRUE;
6099     }
6100
6101     return result;
6102 }
6103
6104 int
6105 SetCharTable (unsigned char *table, const char * map)
6106 {
6107     return SetCharTableEsc(table, map, "");
6108 }
6109
6110 void
6111 Prelude (Board board)
6112 {       // [HGM] superchess: random selection of exo-pieces
6113         int i, j, k; ChessSquare p;
6114         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6115
6116         GetPositionNumber(); // use FRC position number
6117
6118         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6119             SetCharTable(pieceToChar, appData.pieceToCharTable);
6120             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6121                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6122         }
6123
6124         j = seed%4;                 seed /= 4;
6125         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6126         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6127         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6128         j = seed%3 + (seed%3 >= j); seed /= 3;
6129         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6131         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6132         j = seed%3;                 seed /= 3;
6133         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6134         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6135         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6136         j = seed%2 + (seed%2 >= j); seed /= 2;
6137         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6139         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6140         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6141         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6142         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6143         put(board, exoPieces[0],    0, 0, ANY);
6144         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6145 }
6146
6147 void
6148 InitPosition (int redraw)
6149 {
6150     ChessSquare (* pieces)[BOARD_FILES];
6151     int i, j, pawnRow=1, pieceRows=1, overrule,
6152     oldx = gameInfo.boardWidth,
6153     oldy = gameInfo.boardHeight,
6154     oldh = gameInfo.holdingsWidth;
6155     static int oldv;
6156
6157     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6158
6159     /* [AS] Initialize pv info list [HGM] and game status */
6160     {
6161         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6162             pvInfoList[i].depth = 0;
6163             boards[i][EP_STATUS] = EP_NONE;
6164             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6165         }
6166
6167         initialRulePlies = 0; /* 50-move counter start */
6168
6169         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6170         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6171     }
6172
6173
6174     /* [HGM] logic here is completely changed. In stead of full positions */
6175     /* the initialized data only consist of the two backranks. The switch */
6176     /* selects which one we will use, which is than copied to the Board   */
6177     /* initialPosition, which for the rest is initialized by Pawns and    */
6178     /* empty squares. This initial position is then copied to boards[0],  */
6179     /* possibly after shuffling, so that it remains available.            */
6180
6181     gameInfo.holdingsWidth = 0; /* default board sizes */
6182     gameInfo.boardWidth    = 8;
6183     gameInfo.boardHeight   = 8;
6184     gameInfo.holdingsSize  = 0;
6185     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6186     for(i=0; i<BOARD_FILES-6; i++)
6187       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6188     initialPosition[EP_STATUS] = EP_NONE;
6189     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6190     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6191     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6192          SetCharTable(pieceNickName, appData.pieceNickNames);
6193     else SetCharTable(pieceNickName, "............");
6194     pieces = FIDEArray;
6195
6196     switch (gameInfo.variant) {
6197     case VariantFischeRandom:
6198       shuffleOpenings = TRUE;
6199       appData.fischerCastling = TRUE;
6200     default:
6201       break;
6202     case VariantShatranj:
6203       pieces = ShatranjArray;
6204       nrCastlingRights = 0;
6205       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6206       break;
6207     case VariantMakruk:
6208       pieces = makrukArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6211       break;
6212     case VariantASEAN:
6213       pieces = aseanArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6216       break;
6217     case VariantTwoKings:
6218       pieces = twoKingsArray;
6219       break;
6220     case VariantGrand:
6221       pieces = GrandArray;
6222       nrCastlingRights = 0;
6223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224       gameInfo.boardWidth = 10;
6225       gameInfo.boardHeight = 10;
6226       gameInfo.holdingsSize = 7;
6227       break;
6228     case VariantCapaRandom:
6229       shuffleOpenings = TRUE;
6230       appData.fischerCastling = TRUE;
6231     case VariantCapablanca:
6232       pieces = CapablancaArray;
6233       gameInfo.boardWidth = 10;
6234       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235       break;
6236     case VariantGothic:
6237       pieces = GothicArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantSChess:
6242       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6243       gameInfo.holdingsSize = 7;
6244       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6245       break;
6246     case VariantJanus:
6247       pieces = JanusArray;
6248       gameInfo.boardWidth = 10;
6249       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6250       nrCastlingRights = 6;
6251         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6252         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6253         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6254         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6255         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6256         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6257       break;
6258     case VariantFalcon:
6259       pieces = FalconArray;
6260       gameInfo.boardWidth = 10;
6261       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6262       break;
6263     case VariantXiangqi:
6264       pieces = XiangqiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 10;
6267       nrCastlingRights = 0;
6268       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6269       break;
6270     case VariantShogi:
6271       pieces = ShogiArray;
6272       gameInfo.boardWidth  = 9;
6273       gameInfo.boardHeight = 9;
6274       gameInfo.holdingsSize = 7;
6275       nrCastlingRights = 0;
6276       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6277       break;
6278     case VariantChu:
6279       pieces = ChuArray; pieceRows = 3;
6280       gameInfo.boardWidth  = 12;
6281       gameInfo.boardHeight = 12;
6282       nrCastlingRights = 0;
6283       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6284                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6285       break;
6286     case VariantCourier:
6287       pieces = CourierArray;
6288       gameInfo.boardWidth  = 12;
6289       nrCastlingRights = 0;
6290       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6291       break;
6292     case VariantKnightmate:
6293       pieces = KnightmateArray;
6294       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6295       break;
6296     case VariantSpartan:
6297       pieces = SpartanArray;
6298       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6299       break;
6300     case VariantLion:
6301       pieces = lionArray;
6302       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6303       break;
6304     case VariantChuChess:
6305       pieces = ChuChessArray;
6306       gameInfo.boardWidth = 10;
6307       gameInfo.boardHeight = 10;
6308       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6309       break;
6310     case VariantFairy:
6311       pieces = fairyArray;
6312       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6313       break;
6314     case VariantGreat:
6315       pieces = GreatArray;
6316       gameInfo.boardWidth = 10;
6317       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6318       gameInfo.holdingsSize = 8;
6319       break;
6320     case VariantSuper:
6321       pieces = FIDEArray;
6322       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6323       gameInfo.holdingsSize = 8;
6324       startedFromSetupPosition = TRUE;
6325       break;
6326     case VariantCrazyhouse:
6327     case VariantBughouse:
6328       pieces = FIDEArray;
6329       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6330       gameInfo.holdingsSize = 5;
6331       break;
6332     case VariantWildCastle:
6333       pieces = FIDEArray;
6334       /* !!?shuffle with kings guaranteed to be on d or e file */
6335       shuffleOpenings = 1;
6336       break;
6337     case VariantNoCastle:
6338       pieces = FIDEArray;
6339       nrCastlingRights = 0;
6340       /* !!?unconstrained back-rank shuffle */
6341       shuffleOpenings = 1;
6342       break;
6343     }
6344
6345     overrule = 0;
6346     if(appData.NrFiles >= 0) {
6347         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6348         gameInfo.boardWidth = appData.NrFiles;
6349     }
6350     if(appData.NrRanks >= 0) {
6351         gameInfo.boardHeight = appData.NrRanks;
6352     }
6353     if(appData.holdingsSize >= 0) {
6354         i = appData.holdingsSize;
6355         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6356         gameInfo.holdingsSize = i;
6357     }
6358     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6359     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6360         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6361
6362     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6363     if(pawnRow < 1) pawnRow = 1;
6364     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6365        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6366     if(gameInfo.variant == VariantChu) pawnRow = 3;
6367
6368     /* User pieceToChar list overrules defaults */
6369     if(appData.pieceToCharTable != NULL)
6370         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6371
6372     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6373
6374         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6375             s = (ChessSquare) 0; /* account holding counts in guard band */
6376         for( i=0; i<BOARD_HEIGHT; i++ )
6377             initialPosition[i][j] = s;
6378
6379         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6380         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6381         initialPosition[pawnRow][j] = WhitePawn;
6382         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6383         if(gameInfo.variant == VariantXiangqi) {
6384             if(j&1) {
6385                 initialPosition[pawnRow][j] =
6386                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6387                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6388                    initialPosition[2][j] = WhiteCannon;
6389                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6390                 }
6391             }
6392         }
6393         if(gameInfo.variant == VariantChu) {
6394              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6395                initialPosition[pawnRow+1][j] = WhiteCobra,
6396                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6397              for(i=1; i<pieceRows; i++) {
6398                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6399                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6400              }
6401         }
6402         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6403             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6404                initialPosition[0][j] = WhiteRook;
6405                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6406             }
6407         }
6408         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6409     }
6410     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6411     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6412
6413             j=BOARD_LEFT+1;
6414             initialPosition[1][j] = WhiteBishop;
6415             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6416             j=BOARD_RGHT-2;
6417             initialPosition[1][j] = WhiteRook;
6418             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6419     }
6420
6421     if( nrCastlingRights == -1) {
6422         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6423         /*       This sets default castling rights from none to normal corners   */
6424         /* Variants with other castling rights must set them themselves above    */
6425         nrCastlingRights = 6;
6426
6427         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6428         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6429         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6430         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6431         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6432         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6433      }
6434
6435      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6436      if(gameInfo.variant == VariantGreat) { // promotion commoners
6437         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6438         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6439         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6440         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6441      }
6442      if( gameInfo.variant == VariantSChess ) {
6443       initialPosition[1][0] = BlackMarshall;
6444       initialPosition[2][0] = BlackAngel;
6445       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6446       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6447       initialPosition[1][1] = initialPosition[2][1] =
6448       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6449      }
6450   if (appData.debugMode) {
6451     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6452   }
6453     if(shuffleOpenings) {
6454         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6455         startedFromSetupPosition = TRUE;
6456     }
6457     if(startedFromPositionFile) {
6458       /* [HGM] loadPos: use PositionFile for every new game */
6459       CopyBoard(initialPosition, filePosition);
6460       for(i=0; i<nrCastlingRights; i++)
6461           initialRights[i] = filePosition[CASTLING][i];
6462       startedFromSetupPosition = TRUE;
6463     }
6464     if(*appData.men) LoadPieceDesc(appData.men);
6465
6466     CopyBoard(boards[0], initialPosition);
6467
6468     if(oldx != gameInfo.boardWidth ||
6469        oldy != gameInfo.boardHeight ||
6470        oldv != gameInfo.variant ||
6471        oldh != gameInfo.holdingsWidth
6472                                          )
6473             InitDrawingSizes(-2 ,0);
6474
6475     oldv = gameInfo.variant;
6476     if (redraw)
6477       DrawPosition(TRUE, boards[currentMove]);
6478 }
6479
6480 void
6481 SendBoard (ChessProgramState *cps, int moveNum)
6482 {
6483     char message[MSG_SIZ];
6484
6485     if (cps->useSetboard) {
6486       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6487       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6488       SendToProgram(message, cps);
6489       free(fen);
6490
6491     } else {
6492       ChessSquare *bp;
6493       int i, j, left=0, right=BOARD_WIDTH;
6494       /* Kludge to set black to move, avoiding the troublesome and now
6495        * deprecated "black" command.
6496        */
6497       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6498         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6499
6500       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6501
6502       SendToProgram("edit\n", cps);
6503       SendToProgram("#\n", cps);
6504       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6505         bp = &boards[moveNum][i][left];
6506         for (j = left; j < right; j++, bp++) {
6507           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6508           if ((int) *bp < (int) BlackPawn) {
6509             if(j == BOARD_RGHT+1)
6510                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6511             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6512             if(message[0] == '+' || message[0] == '~') {
6513               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6514                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6515                         AAA + j, ONE + i - '0');
6516             }
6517             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6518                 message[1] = BOARD_RGHT   - 1 - j + '1';
6519                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6520             }
6521             SendToProgram(message, cps);
6522           }
6523         }
6524       }
6525
6526       SendToProgram("c\n", cps);
6527       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6528         bp = &boards[moveNum][i][left];
6529         for (j = left; j < right; j++, bp++) {
6530           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6531           if (((int) *bp != (int) EmptySquare)
6532               && ((int) *bp >= (int) BlackPawn)) {
6533             if(j == BOARD_LEFT-2)
6534                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6535             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6536                     AAA + j, ONE + i - '0');
6537             if(message[0] == '+' || message[0] == '~') {
6538               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6539                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6540                         AAA + j, ONE + i - '0');
6541             }
6542             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6543                 message[1] = BOARD_RGHT   - 1 - j + '1';
6544                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6545             }
6546             SendToProgram(message, cps);
6547           }
6548         }
6549       }
6550
6551       SendToProgram(".\n", cps);
6552     }
6553     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6554 }
6555
6556 char exclusionHeader[MSG_SIZ];
6557 int exCnt, excludePtr;
6558 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6559 static Exclusion excluTab[200];
6560 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6561
6562 static void
6563 WriteMap (int s)
6564 {
6565     int j;
6566     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6567     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6568 }
6569
6570 static void
6571 ClearMap ()
6572 {
6573     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6574     excludePtr = 24; exCnt = 0;
6575     WriteMap(0);
6576 }
6577
6578 static void
6579 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6580 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6581     char buf[2*MOVE_LEN], *p;
6582     Exclusion *e = excluTab;
6583     int i;
6584     for(i=0; i<exCnt; i++)
6585         if(e[i].ff == fromX && e[i].fr == fromY &&
6586            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6587     if(i == exCnt) { // was not in exclude list; add it
6588         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6589         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6590             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6591             return; // abort
6592         }
6593         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6594         excludePtr++; e[i].mark = excludePtr++;
6595         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6596         exCnt++;
6597     }
6598     exclusionHeader[e[i].mark] = state;
6599 }
6600
6601 static int
6602 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6603 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6604     char buf[MSG_SIZ];
6605     int j, k;
6606     ChessMove moveType;
6607     if((signed char)promoChar == -1) { // kludge to indicate best move
6608         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6609             return 1; // if unparsable, abort
6610     }
6611     // update exclusion map (resolving toggle by consulting existing state)
6612     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6613     j = k%8; k >>= 3;
6614     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6615     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6616          excludeMap[k] |=   1<<j;
6617     else excludeMap[k] &= ~(1<<j);
6618     // update header
6619     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6620     // inform engine
6621     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6622     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6623     SendToBoth(buf);
6624     return (state == '+');
6625 }
6626
6627 static void
6628 ExcludeClick (int index)
6629 {
6630     int i, j;
6631     Exclusion *e = excluTab;
6632     if(index < 25) { // none, best or tail clicked
6633         if(index < 13) { // none: include all
6634             WriteMap(0); // clear map
6635             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6636             SendToBoth("include all\n"); // and inform engine
6637         } else if(index > 18) { // tail
6638             if(exclusionHeader[19] == '-') { // tail was excluded
6639                 SendToBoth("include all\n");
6640                 WriteMap(0); // clear map completely
6641                 // now re-exclude selected moves
6642                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6643                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6644             } else { // tail was included or in mixed state
6645                 SendToBoth("exclude all\n");
6646                 WriteMap(0xFF); // fill map completely
6647                 // now re-include selected moves
6648                 j = 0; // count them
6649                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6650                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6651                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6652             }
6653         } else { // best
6654             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6655         }
6656     } else {
6657         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6658             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6659             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6660             break;
6661         }
6662     }
6663 }
6664
6665 ChessSquare
6666 DefaultPromoChoice (int white)
6667 {
6668     ChessSquare result;
6669     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6670        gameInfo.variant == VariantMakruk)
6671         result = WhiteFerz; // no choice
6672     else if(gameInfo.variant == VariantASEAN)
6673         result = WhiteRook; // no choice
6674     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6675         result= WhiteKing; // in Suicide Q is the last thing we want
6676     else if(gameInfo.variant == VariantSpartan)
6677         result = white ? WhiteQueen : WhiteAngel;
6678     else result = WhiteQueen;
6679     if(!white) result = WHITE_TO_BLACK result;
6680     return result;
6681 }
6682
6683 static int autoQueen; // [HGM] oneclick
6684
6685 int
6686 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6687 {
6688     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6689     /* [HGM] add Shogi promotions */
6690     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6691     ChessSquare piece, partner;
6692     ChessMove moveType;
6693     Boolean premove;
6694
6695     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6696     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6697
6698     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6699       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6700         return FALSE;
6701
6702     piece = boards[currentMove][fromY][fromX];
6703     if(gameInfo.variant == VariantChu) {
6704         promotionZoneSize = BOARD_HEIGHT/3;
6705         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6706     } else if(gameInfo.variant == VariantShogi) {
6707         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6708         highestPromotingPiece = (int)WhiteAlfil;
6709     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6710         promotionZoneSize = 3;
6711     }
6712
6713     // Treat Lance as Pawn when it is not representing Amazon or Lance
6714     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6715         if(piece == WhiteLance) piece = WhitePawn; else
6716         if(piece == BlackLance) piece = BlackPawn;
6717     }
6718
6719     // next weed out all moves that do not touch the promotion zone at all
6720     if((int)piece >= BlackPawn) {
6721         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6722              return FALSE;
6723         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6724         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6725     } else {
6726         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6727            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6728         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6729              return FALSE;
6730     }
6731
6732     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6733
6734     // weed out mandatory Shogi promotions
6735     if(gameInfo.variant == VariantShogi) {
6736         if(piece >= BlackPawn) {
6737             if(toY == 0 && piece == BlackPawn ||
6738                toY == 0 && piece == BlackQueen ||
6739                toY <= 1 && piece == BlackKnight) {
6740                 *promoChoice = '+';
6741                 return FALSE;
6742             }
6743         } else {
6744             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6745                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6746                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6747                 *promoChoice = '+';
6748                 return FALSE;
6749             }
6750         }
6751     }
6752
6753     // weed out obviously illegal Pawn moves
6754     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6755         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6756         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6757         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6758         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6759         // note we are not allowed to test for valid (non-)capture, due to premove
6760     }
6761
6762     // we either have a choice what to promote to, or (in Shogi) whether to promote
6763     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6764        gameInfo.variant == VariantMakruk) {
6765         ChessSquare p=BlackFerz;  // no choice
6766         while(p < EmptySquare) {  //but make sure we use piece that exists
6767             *promoChoice = PieceToChar(p++);
6768             if(*promoChoice != '.') break;
6769         }
6770         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6771     }
6772     // no sense asking what we must promote to if it is going to explode...
6773     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6774         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6775         return FALSE;
6776     }
6777     // give caller the default choice even if we will not make it
6778     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6779     partner = piece; // pieces can promote if the pieceToCharTable says so
6780     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6781     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6782     if(        sweepSelect && gameInfo.variant != VariantGreat
6783                            && gameInfo.variant != VariantGrand
6784                            && gameInfo.variant != VariantSuper) return FALSE;
6785     if(autoQueen) return FALSE; // predetermined
6786
6787     // suppress promotion popup on illegal moves that are not premoves
6788     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6789               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6790     if(appData.testLegality && !premove) {
6791         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6792                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6793         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6794         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6795             return FALSE;
6796     }
6797
6798     return TRUE;
6799 }
6800
6801 int
6802 InPalace (int row, int column)
6803 {   /* [HGM] for Xiangqi */
6804     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6805          column < (BOARD_WIDTH + 4)/2 &&
6806          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6807     return FALSE;
6808 }
6809
6810 int
6811 PieceForSquare (int x, int y)
6812 {
6813   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6814      return -1;
6815   else
6816      return boards[currentMove][y][x];
6817 }
6818
6819 int
6820 OKToStartUserMove (int x, int y)
6821 {
6822     ChessSquare from_piece;
6823     int white_piece;
6824
6825     if (matchMode) return FALSE;
6826     if (gameMode == EditPosition) return TRUE;
6827
6828     if (x >= 0 && y >= 0)
6829       from_piece = boards[currentMove][y][x];
6830     else
6831       from_piece = EmptySquare;
6832
6833     if (from_piece == EmptySquare) return FALSE;
6834
6835     white_piece = (int)from_piece >= (int)WhitePawn &&
6836       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6837
6838     switch (gameMode) {
6839       case AnalyzeFile:
6840       case TwoMachinesPlay:
6841       case EndOfGame:
6842         return FALSE;
6843
6844       case IcsObserving:
6845       case IcsIdle:
6846         return FALSE;
6847
6848       case MachinePlaysWhite:
6849       case IcsPlayingBlack:
6850         if (appData.zippyPlay) return FALSE;
6851         if (white_piece) {
6852             DisplayMoveError(_("You are playing Black"));
6853             return FALSE;
6854         }
6855         break;
6856
6857       case MachinePlaysBlack:
6858       case IcsPlayingWhite:
6859         if (appData.zippyPlay) return FALSE;
6860         if (!white_piece) {
6861             DisplayMoveError(_("You are playing White"));
6862             return FALSE;
6863         }
6864         break;
6865
6866       case PlayFromGameFile:
6867             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6868       case EditGame:
6869       case AnalyzeMode:
6870         if (!white_piece && WhiteOnMove(currentMove)) {
6871             DisplayMoveError(_("It is White's turn"));
6872             return FALSE;
6873         }
6874         if (white_piece && !WhiteOnMove(currentMove)) {
6875             DisplayMoveError(_("It is Black's turn"));
6876             return FALSE;
6877         }
6878         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6879             /* Editing correspondence game history */
6880             /* Could disallow this or prompt for confirmation */
6881             cmailOldMove = -1;
6882         }
6883         break;
6884
6885       case BeginningOfGame:
6886         if (appData.icsActive) return FALSE;
6887         if (!appData.noChessProgram) {
6888             if (!white_piece) {
6889                 DisplayMoveError(_("You are playing White"));
6890                 return FALSE;
6891             }
6892         }
6893         break;
6894
6895       case Training:
6896         if (!white_piece && WhiteOnMove(currentMove)) {
6897             DisplayMoveError(_("It is White's turn"));
6898             return FALSE;
6899         }
6900         if (white_piece && !WhiteOnMove(currentMove)) {
6901             DisplayMoveError(_("It is Black's turn"));
6902             return FALSE;
6903         }
6904         break;
6905
6906       default:
6907       case IcsExamining:
6908         break;
6909     }
6910     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6911         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6912         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6913         && gameMode != AnalyzeFile && gameMode != Training) {
6914         DisplayMoveError(_("Displayed position is not current"));
6915         return FALSE;
6916     }
6917     return TRUE;
6918 }
6919
6920 Boolean
6921 OnlyMove (int *x, int *y, Boolean captures)
6922 {
6923     DisambiguateClosure cl;
6924     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6925     switch(gameMode) {
6926       case MachinePlaysBlack:
6927       case IcsPlayingWhite:
6928       case BeginningOfGame:
6929         if(!WhiteOnMove(currentMove)) return FALSE;
6930         break;
6931       case MachinePlaysWhite:
6932       case IcsPlayingBlack:
6933         if(WhiteOnMove(currentMove)) return FALSE;
6934         break;
6935       case EditGame:
6936         break;
6937       default:
6938         return FALSE;
6939     }
6940     cl.pieceIn = EmptySquare;
6941     cl.rfIn = *y;
6942     cl.ffIn = *x;
6943     cl.rtIn = -1;
6944     cl.ftIn = -1;
6945     cl.promoCharIn = NULLCHAR;
6946     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6947     if( cl.kind == NormalMove ||
6948         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6949         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6950         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6951       fromX = cl.ff;
6952       fromY = cl.rf;
6953       *x = cl.ft;
6954       *y = cl.rt;
6955       return TRUE;
6956     }
6957     if(cl.kind != ImpossibleMove) return FALSE;
6958     cl.pieceIn = EmptySquare;
6959     cl.rfIn = -1;
6960     cl.ffIn = -1;
6961     cl.rtIn = *y;
6962     cl.ftIn = *x;
6963     cl.promoCharIn = NULLCHAR;
6964     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6965     if( cl.kind == NormalMove ||
6966         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6967         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6968         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6969       fromX = cl.ff;
6970       fromY = cl.rf;
6971       *x = cl.ft;
6972       *y = cl.rt;
6973       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6974       return TRUE;
6975     }
6976     return FALSE;
6977 }
6978
6979 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6980 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6981 int lastLoadGameUseList = FALSE;
6982 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6983 ChessMove lastLoadGameStart = EndOfFile;
6984 int doubleClick;
6985 Boolean addToBookFlag;
6986 static Board rightsBoard, nullBoard;
6987
6988 void
6989 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6990 {
6991     ChessMove moveType;
6992     ChessSquare pup;
6993     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6994
6995     /* Check if the user is playing in turn.  This is complicated because we
6996        let the user "pick up" a piece before it is his turn.  So the piece he
6997        tried to pick up may have been captured by the time he puts it down!
6998        Therefore we use the color the user is supposed to be playing in this
6999        test, not the color of the piece that is currently on the starting
7000        square---except in EditGame mode, where the user is playing both
7001        sides; fortunately there the capture race can't happen.  (It can
7002        now happen in IcsExamining mode, but that's just too bad.  The user
7003        will get a somewhat confusing message in that case.)
7004        */
7005
7006     switch (gameMode) {
7007       case AnalyzeFile:
7008       case TwoMachinesPlay:
7009       case EndOfGame:
7010       case IcsObserving:
7011       case IcsIdle:
7012         /* We switched into a game mode where moves are not accepted,
7013            perhaps while the mouse button was down. */
7014         return;
7015
7016       case MachinePlaysWhite:
7017         /* User is moving for Black */
7018         if (WhiteOnMove(currentMove)) {
7019             DisplayMoveError(_("It is White's turn"));
7020             return;
7021         }
7022         break;
7023
7024       case MachinePlaysBlack:
7025         /* User is moving for White */
7026         if (!WhiteOnMove(currentMove)) {
7027             DisplayMoveError(_("It is Black's turn"));
7028             return;
7029         }
7030         break;
7031
7032       case PlayFromGameFile:
7033             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7034       case EditGame:
7035       case IcsExamining:
7036       case BeginningOfGame:
7037       case AnalyzeMode:
7038       case Training:
7039         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7040         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7041             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7042             /* User is moving for Black */
7043             if (WhiteOnMove(currentMove)) {
7044                 DisplayMoveError(_("It is White's turn"));
7045                 return;
7046             }
7047         } else {
7048             /* User is moving for White */
7049             if (!WhiteOnMove(currentMove)) {
7050                 DisplayMoveError(_("It is Black's turn"));
7051                 return;
7052             }
7053         }
7054         break;
7055
7056       case IcsPlayingBlack:
7057         /* User is moving for Black */
7058         if (WhiteOnMove(currentMove)) {
7059             if (!appData.premove) {
7060                 DisplayMoveError(_("It is White's turn"));
7061             } else if (toX >= 0 && toY >= 0) {
7062                 premoveToX = toX;
7063                 premoveToY = toY;
7064                 premoveFromX = fromX;
7065                 premoveFromY = fromY;
7066                 premovePromoChar = promoChar;
7067                 gotPremove = 1;
7068                 if (appData.debugMode)
7069                     fprintf(debugFP, "Got premove: fromX %d,"
7070                             "fromY %d, toX %d, toY %d\n",
7071                             fromX, fromY, toX, toY);
7072             }
7073             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7074             return;
7075         }
7076         break;
7077
7078       case IcsPlayingWhite:
7079         /* User is moving for White */
7080         if (!WhiteOnMove(currentMove)) {
7081             if (!appData.premove) {
7082                 DisplayMoveError(_("It is Black's turn"));
7083             } else if (toX >= 0 && toY >= 0) {
7084                 premoveToX = toX;
7085                 premoveToY = toY;
7086                 premoveFromX = fromX;
7087                 premoveFromY = fromY;
7088                 premovePromoChar = promoChar;
7089                 gotPremove = 1;
7090                 if (appData.debugMode)
7091                     fprintf(debugFP, "Got premove: fromX %d,"
7092                             "fromY %d, toX %d, toY %d\n",
7093                             fromX, fromY, toX, toY);
7094             }
7095             DrawPosition(TRUE, boards[currentMove]);
7096             return;
7097         }
7098         break;
7099
7100       default:
7101         break;
7102
7103       case EditPosition:
7104         /* EditPosition, empty square, or different color piece;
7105            click-click move is possible */
7106         if (toX == -2 || toY == -2) {
7107             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7108             DrawPosition(FALSE, boards[currentMove]);
7109             return;
7110         } else if (toX >= 0 && toY >= 0) {
7111             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7112                 ChessSquare p = boards[0][rf][ff];
7113                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7114                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7115                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook) {
7116                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7117                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7118                     gatingPiece = p;
7119                 }
7120             }
7121             boards[0][toY][toX] = boards[0][fromY][fromX];
7122             rightsBoard[toY][toX] = 0;  // revoke rights on moving
7123             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7124                 if(boards[0][fromY][0] != EmptySquare) {
7125                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7126                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7127                 }
7128             } else
7129             if(fromX == BOARD_RGHT+1) {
7130                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7131                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7132                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7133                 }
7134             } else
7135             boards[0][fromY][fromX] = gatingPiece;
7136             ClearHighlights();
7137             DrawPosition(FALSE, boards[currentMove]);
7138             return;
7139         }
7140         return;
7141     }
7142
7143     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7144     pup = boards[currentMove][toY][toX];
7145
7146     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7147     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7148          if( pup != EmptySquare ) return;
7149          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7150            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7151                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7152            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7153            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7154            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7155            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7156          fromY = DROP_RANK;
7157     }
7158
7159     /* [HGM] always test for legality, to get promotion info */
7160     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7161                                          fromY, fromX, toY, toX, promoChar);
7162
7163     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7164
7165     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7166
7167     /* [HGM] but possibly ignore an IllegalMove result */
7168     if (appData.testLegality) {
7169         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7170             DisplayMoveError(_("Illegal move"));
7171             return;
7172         }
7173     }
7174
7175     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7176         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7177              ClearPremoveHighlights(); // was included
7178         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7179         DrawPosition(FALSE, NULL);
7180         return;
7181     }
7182
7183     if(addToBookFlag) { // adding moves to book
7184         char buf[MSG_SIZ], move[MSG_SIZ];
7185         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7186         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7187                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7188         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7189         AddBookMove(buf);
7190         addToBookFlag = FALSE;
7191         ClearHighlights();
7192         return;
7193     }
7194
7195     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7196 }
7197
7198 /* Common tail of UserMoveEvent and DropMenuEvent */
7199 int
7200 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7201 {
7202     char *bookHit = 0;
7203
7204     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7205         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7206         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7207         if(WhiteOnMove(currentMove)) {
7208             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7209         } else {
7210             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7211         }
7212     }
7213
7214     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7215        move type in caller when we know the move is a legal promotion */
7216     if(moveType == NormalMove && promoChar)
7217         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7218
7219     /* [HGM] <popupFix> The following if has been moved here from
7220        UserMoveEvent(). Because it seemed to belong here (why not allow
7221        piece drops in training games?), and because it can only be
7222        performed after it is known to what we promote. */
7223     if (gameMode == Training) {
7224       /* compare the move played on the board to the next move in the
7225        * game. If they match, display the move and the opponent's response.
7226        * If they don't match, display an error message.
7227        */
7228       int saveAnimate;
7229       Board testBoard;
7230       CopyBoard(testBoard, boards[currentMove]);
7231       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7232
7233       if (CompareBoards(testBoard, boards[currentMove+1])) {
7234         ForwardInner(currentMove+1);
7235
7236         /* Autoplay the opponent's response.
7237          * if appData.animate was TRUE when Training mode was entered,
7238          * the response will be animated.
7239          */
7240         saveAnimate = appData.animate;
7241         appData.animate = animateTraining;
7242         ForwardInner(currentMove+1);
7243         appData.animate = saveAnimate;
7244
7245         /* check for the end of the game */
7246         if (currentMove >= forwardMostMove) {
7247           gameMode = PlayFromGameFile;
7248           ModeHighlight();
7249           SetTrainingModeOff();
7250           DisplayInformation(_("End of game"));
7251         }
7252       } else {
7253         DisplayError(_("Incorrect move"), 0);
7254       }
7255       return 1;
7256     }
7257
7258   /* Ok, now we know that the move is good, so we can kill
7259      the previous line in Analysis Mode */
7260   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7261                                 && currentMove < forwardMostMove) {
7262     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7263     else forwardMostMove = currentMove;
7264   }
7265
7266   ClearMap();
7267
7268   /* If we need the chess program but it's dead, restart it */
7269   ResurrectChessProgram();
7270
7271   /* A user move restarts a paused game*/
7272   if (pausing)
7273     PauseEvent();
7274
7275   thinkOutput[0] = NULLCHAR;
7276
7277   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7278
7279   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7280     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7281     return 1;
7282   }
7283
7284   if (gameMode == BeginningOfGame) {
7285     if (appData.noChessProgram) {
7286       gameMode = EditGame;
7287       SetGameInfo();
7288     } else {
7289       char buf[MSG_SIZ];
7290       gameMode = MachinePlaysBlack;
7291       StartClocks();
7292       SetGameInfo();
7293       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7294       DisplayTitle(buf);
7295       if (first.sendName) {
7296         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7297         SendToProgram(buf, &first);
7298       }
7299       StartClocks();
7300     }
7301     ModeHighlight();
7302   }
7303
7304   /* Relay move to ICS or chess engine */
7305   if (appData.icsActive) {
7306     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7307         gameMode == IcsExamining) {
7308       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7309         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7310         SendToICS("draw ");
7311         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7312       }
7313       // also send plain move, in case ICS does not understand atomic claims
7314       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7315       ics_user_moved = 1;
7316     }
7317   } else {
7318     if (first.sendTime && (gameMode == BeginningOfGame ||
7319                            gameMode == MachinePlaysWhite ||
7320                            gameMode == MachinePlaysBlack)) {
7321       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7322     }
7323     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7324          // [HGM] book: if program might be playing, let it use book
7325         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7326         first.maybeThinking = TRUE;
7327     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7328         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7329         SendBoard(&first, currentMove+1);
7330         if(second.analyzing) {
7331             if(!second.useSetboard) SendToProgram("undo\n", &second);
7332             SendBoard(&second, currentMove+1);
7333         }
7334     } else {
7335         SendMoveToProgram(forwardMostMove-1, &first);
7336         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7337     }
7338     if (currentMove == cmailOldMove + 1) {
7339       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7340     }
7341   }
7342
7343   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7344
7345   switch (gameMode) {
7346   case EditGame:
7347     if(appData.testLegality)
7348     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7349     case MT_NONE:
7350     case MT_CHECK:
7351       break;
7352     case MT_CHECKMATE:
7353     case MT_STAINMATE:
7354       if (WhiteOnMove(currentMove)) {
7355         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7356       } else {
7357         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7358       }
7359       break;
7360     case MT_STALEMATE:
7361       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7362       break;
7363     }
7364     break;
7365
7366   case MachinePlaysBlack:
7367   case MachinePlaysWhite:
7368     /* disable certain menu options while machine is thinking */
7369     SetMachineThinkingEnables();
7370     break;
7371
7372   default:
7373     break;
7374   }
7375
7376   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7377   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7378
7379   if(bookHit) { // [HGM] book: simulate book reply
7380         static char bookMove[MSG_SIZ]; // a bit generous?
7381
7382         programStats.nodes = programStats.depth = programStats.time =
7383         programStats.score = programStats.got_only_move = 0;
7384         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7385
7386         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7387         strcat(bookMove, bookHit);
7388         HandleMachineMove(bookMove, &first);
7389   }
7390   return 1;
7391 }
7392
7393 void
7394 MarkByFEN(char *fen)
7395 {
7396         int r, f;
7397         if(!appData.markers || !appData.highlightDragging) return;
7398         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7399         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7400         while(*fen) {
7401             int s = 0;
7402             marker[r][f] = 0;
7403             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7404             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7405             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7406             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7407             if(*fen == 'T') marker[r][f++] = 0; else
7408             if(*fen == 'Y') marker[r][f++] = 1; else
7409             if(*fen == 'G') marker[r][f++] = 3; else
7410             if(*fen == 'B') marker[r][f++] = 4; else
7411             if(*fen == 'C') marker[r][f++] = 5; else
7412             if(*fen == 'M') marker[r][f++] = 6; else
7413             if(*fen == 'W') marker[r][f++] = 7; else
7414             if(*fen == 'D') marker[r][f++] = 8; else
7415             if(*fen == 'R') marker[r][f++] = 2; else {
7416                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7417               f += s; fen -= s>0;
7418             }
7419             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7420             if(r < 0) break;
7421             fen++;
7422         }
7423         DrawPosition(TRUE, NULL);
7424 }
7425
7426 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7427
7428 void
7429 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7430 {
7431     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7432     Markers *m = (Markers *) closure;
7433     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7434                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7435         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7436                          || kind == WhiteCapturesEnPassant
7437                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7438     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7439 }
7440
7441 static int hoverSavedValid;
7442
7443 void
7444 MarkTargetSquares (int clear)
7445 {
7446   int x, y, sum=0;
7447   if(clear) { // no reason to ever suppress clearing
7448     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7449     hoverSavedValid = 0;
7450     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7451   } else {
7452     int capt = 0;
7453     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7454        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7455     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7456     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7457       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7458       if(capt)
7459       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7460     }
7461   }
7462   DrawPosition(FALSE, NULL);
7463 }
7464
7465 int
7466 Explode (Board board, int fromX, int fromY, int toX, int toY)
7467 {
7468     if(gameInfo.variant == VariantAtomic &&
7469        (board[toY][toX] != EmptySquare ||                     // capture?
7470         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7471                          board[fromY][fromX] == BlackPawn   )
7472       )) {
7473         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7474         return TRUE;
7475     }
7476     return FALSE;
7477 }
7478
7479 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7480
7481 int
7482 CanPromote (ChessSquare piece, int y)
7483 {
7484         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7485         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7486         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7487         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7488            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7489           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7490            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7491         return (piece == BlackPawn && y <= zone ||
7492                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7493                 piece == BlackLance && y <= zone ||
7494                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7495 }
7496
7497 void
7498 HoverEvent (int xPix, int yPix, int x, int y)
7499 {
7500         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7501         int r, f;
7502         if(!first.highlight) return;
7503         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7504         if(x == oldX && y == oldY) return; // only do something if we enter new square
7505         oldFromX = fromX; oldFromY = fromY;
7506         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7507           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7508             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7509           hoverSavedValid = 1;
7510         } else if(oldX != x || oldY != y) {
7511           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7512           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7513           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7514             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7515           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7516             char buf[MSG_SIZ];
7517             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7518             SendToProgram(buf, &first);
7519           }
7520           oldX = x; oldY = y;
7521 //        SetHighlights(fromX, fromY, x, y);
7522         }
7523 }
7524
7525 void ReportClick(char *action, int x, int y)
7526 {
7527         char buf[MSG_SIZ]; // Inform engine of what user does
7528         int r, f;
7529         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7530           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7531             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7532         if(!first.highlight || gameMode == EditPosition) return;
7533         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7534         SendToProgram(buf, &first);
7535 }
7536
7537 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7538
7539 void
7540 LeftClick (ClickType clickType, int xPix, int yPix)
7541 {
7542     int x, y;
7543     Boolean saveAnimate;
7544     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7545     char promoChoice = NULLCHAR;
7546     ChessSquare piece;
7547     static TimeMark lastClickTime, prevClickTime;
7548
7549     if(flashing) return;
7550
7551     x = EventToSquare(xPix, BOARD_WIDTH);
7552     y = EventToSquare(yPix, BOARD_HEIGHT);
7553     if (!flipView && y >= 0) {
7554         y = BOARD_HEIGHT - 1 - y;
7555     }
7556     if (flipView && x >= 0) {
7557         x = BOARD_WIDTH - 1 - x;
7558     }
7559
7560     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7561         static int dummy;
7562         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7563         right = TRUE;
7564         return;
7565     }
7566
7567     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7568
7569     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7570
7571     if (clickType == Press) ErrorPopDown();
7572     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7573
7574     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7575         defaultPromoChoice = promoSweep;
7576         promoSweep = EmptySquare;   // terminate sweep
7577         promoDefaultAltered = TRUE;
7578         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7579     }
7580
7581     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7582         if(clickType == Release) return; // ignore upclick of click-click destination
7583         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7584         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7585         if(gameInfo.holdingsWidth &&
7586                 (WhiteOnMove(currentMove)
7587                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7588                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7589             // click in right holdings, for determining promotion piece
7590             ChessSquare p = boards[currentMove][y][x];
7591             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7592             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7593             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7594                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7595                 fromX = fromY = -1;
7596                 return;
7597             }
7598         }
7599         DrawPosition(FALSE, boards[currentMove]);
7600         return;
7601     }
7602
7603     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7604     if(clickType == Press
7605             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7606               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7607               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7608         return;
7609
7610     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7611         // could be static click on premove from-square: abort premove
7612         gotPremove = 0;
7613         ClearPremoveHighlights();
7614     }
7615
7616     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7617         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7618
7619     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7620         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7621                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7622         defaultPromoChoice = DefaultPromoChoice(side);
7623     }
7624
7625     autoQueen = appData.alwaysPromoteToQueen;
7626
7627     if (fromX == -1) {
7628       int originalY = y;
7629       gatingPiece = EmptySquare;
7630       if (clickType != Press) {
7631         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7632             DragPieceEnd(xPix, yPix); dragging = 0;
7633             DrawPosition(FALSE, NULL);
7634         }
7635         return;
7636       }
7637       doubleClick = FALSE;
7638       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7639         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7640       }
7641       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7642       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7643          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7644          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7645             /* First square */
7646             if (OKToStartUserMove(fromX, fromY)) {
7647                 second = 0;
7648                 ReportClick("lift", x, y);
7649                 MarkTargetSquares(0);
7650                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7651                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7652                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7653                     promoSweep = defaultPromoChoice;
7654                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7655                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7656                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7657                 }
7658                 if (appData.highlightDragging) {
7659                     SetHighlights(fromX, fromY, -1, -1);
7660                 } else {
7661                     ClearHighlights();
7662                 }
7663             } else fromX = fromY = -1;
7664             return;
7665         }
7666     }
7667
7668     /* fromX != -1 */
7669     if (clickType == Press && gameMode != EditPosition) {
7670         ChessSquare fromP;
7671         ChessSquare toP;
7672         int frc;
7673
7674         // ignore off-board to clicks
7675         if(y < 0 || x < 0) return;
7676
7677         /* Check if clicking again on the same color piece */
7678         fromP = boards[currentMove][fromY][fromX];
7679         toP = boards[currentMove][y][x];
7680         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7681         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7682             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7683            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7684              WhitePawn <= toP && toP <= WhiteKing &&
7685              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7686              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7687             (BlackPawn <= fromP && fromP <= BlackKing &&
7688              BlackPawn <= toP && toP <= BlackKing &&
7689              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7690              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7691             /* Clicked again on same color piece -- changed his mind */
7692             second = (x == fromX && y == fromY);
7693             killX = killY = kill2X = kill2Y = -1;
7694             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7695                 second = FALSE; // first double-click rather than scond click
7696                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7697             }
7698             promoDefaultAltered = FALSE;
7699            if(!second) MarkTargetSquares(1);
7700            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7701             if (appData.highlightDragging) {
7702                 SetHighlights(x, y, -1, -1);
7703             } else {
7704                 ClearHighlights();
7705             }
7706             if (OKToStartUserMove(x, y)) {
7707                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7708                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7709                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7710                  gatingPiece = boards[currentMove][fromY][fromX];
7711                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7712                 fromX = x;
7713                 fromY = y; dragging = 1;
7714                 if(!second) ReportClick("lift", x, y);
7715                 MarkTargetSquares(0);
7716                 DragPieceBegin(xPix, yPix, FALSE);
7717                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7718                     promoSweep = defaultPromoChoice;
7719                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7720                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7721                 }
7722             }
7723            }
7724            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7725            second = FALSE;
7726         }
7727         // ignore clicks on holdings
7728         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7729     }
7730
7731     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7732         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7733         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7734         return;
7735     }
7736
7737     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7738         DragPieceEnd(xPix, yPix); dragging = 0;
7739         if(clearFlag) {
7740             // a deferred attempt to click-click move an empty square on top of a piece
7741             boards[currentMove][y][x] = EmptySquare;
7742             ClearHighlights();
7743             DrawPosition(FALSE, boards[currentMove]);
7744             fromX = fromY = -1; clearFlag = 0;
7745             return;
7746         }
7747         if (appData.animateDragging) {
7748             /* Undo animation damage if any */
7749             DrawPosition(FALSE, NULL);
7750         }
7751         if (second) {
7752             /* Second up/down in same square; just abort move */
7753             second = 0;
7754             fromX = fromY = -1;
7755             gatingPiece = EmptySquare;
7756             ClearHighlights();
7757             gotPremove = 0;
7758             ClearPremoveHighlights();
7759             MarkTargetSquares(-1);
7760             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7761         } else {
7762             /* First upclick in same square; start click-click mode */
7763             SetHighlights(x, y, -1, -1);
7764         }
7765         return;
7766     }
7767
7768     clearFlag = 0;
7769
7770     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7771        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7772         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7773         DisplayMessage(_("only marked squares are legal"),"");
7774         DrawPosition(TRUE, NULL);
7775         return; // ignore to-click
7776     }
7777
7778     /* we now have a different from- and (possibly off-board) to-square */
7779     /* Completed move */
7780     if(!sweepSelecting) {
7781         toX = x;
7782         toY = y;
7783     }
7784
7785     piece = boards[currentMove][fromY][fromX];
7786
7787     saveAnimate = appData.animate;
7788     if (clickType == Press) {
7789         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7790         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7791             // must be Edit Position mode with empty-square selected
7792             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7793             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7794             return;
7795         }
7796         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7797             return;
7798         }
7799         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7800             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7801         } else
7802         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7803         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7804           if(appData.sweepSelect) {
7805             promoSweep = defaultPromoChoice;
7806             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7807             selectFlag = 0; lastX = xPix; lastY = yPix;
7808             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7809             saveFlash = appData.flashCount; appData.flashCount = 0;
7810             Sweep(0); // Pawn that is going to promote: preview promotion piece
7811             sweepSelecting = 1;
7812             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7813             MarkTargetSquares(1);
7814           }
7815           return; // promo popup appears on up-click
7816         }
7817         /* Finish clickclick move */
7818         if (appData.animate || appData.highlightLastMove) {
7819             SetHighlights(fromX, fromY, toX, toY);
7820         } else {
7821             ClearHighlights();
7822         }
7823         MarkTargetSquares(1);
7824     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7825         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7826         *promoRestrict = 0; appData.flashCount = saveFlash;
7827         if (appData.animate || appData.highlightLastMove) {
7828             SetHighlights(fromX, fromY, toX, toY);
7829         } else {
7830             ClearHighlights();
7831         }
7832         MarkTargetSquares(1);
7833     } else {
7834 #if 0
7835 // [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
7836         /* Finish drag move */
7837         if (appData.highlightLastMove) {
7838             SetHighlights(fromX, fromY, toX, toY);
7839         } else {
7840             ClearHighlights();
7841         }
7842 #endif
7843         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7844           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7845         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7846         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7847           dragging *= 2;            // flag button-less dragging if we are dragging
7848           MarkTargetSquares(1);
7849           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7850           else {
7851             kill2X = killX; kill2Y = killY;
7852             killX = x; killY = y;     // remember this square as intermediate
7853             ReportClick("put", x, y); // and inform engine
7854             ReportClick("lift", x, y);
7855             MarkTargetSquares(0);
7856             return;
7857           }
7858         }
7859         DragPieceEnd(xPix, yPix); dragging = 0;
7860         /* Don't animate move and drag both */
7861         appData.animate = FALSE;
7862         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7863     }
7864
7865     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7866     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7867         ChessSquare piece = boards[currentMove][fromY][fromX];
7868         if(gameMode == EditPosition && piece != EmptySquare &&
7869            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7870             int n;
7871
7872             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7873                 n = PieceToNumber(piece - (int)BlackPawn);
7874                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7875                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7876                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7877             } else
7878             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7879                 n = PieceToNumber(piece);
7880                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7881                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7882                 boards[currentMove][n][BOARD_WIDTH-2]++;
7883             }
7884             boards[currentMove][fromY][fromX] = EmptySquare;
7885         }
7886         ClearHighlights();
7887         fromX = fromY = -1;
7888         MarkTargetSquares(1);
7889         DrawPosition(TRUE, boards[currentMove]);
7890         return;
7891     }
7892
7893     // off-board moves should not be highlighted
7894     if(x < 0 || y < 0) ClearHighlights();
7895     else ReportClick("put", x, y);
7896
7897     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7898
7899     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7900
7901     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7902         SetHighlights(fromX, fromY, toX, toY);
7903         MarkTargetSquares(1);
7904         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7905             // [HGM] super: promotion to captured piece selected from holdings
7906             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7907             promotionChoice = TRUE;
7908             // kludge follows to temporarily execute move on display, without promoting yet
7909             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7910             boards[currentMove][toY][toX] = p;
7911             DrawPosition(FALSE, boards[currentMove]);
7912             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7913             boards[currentMove][toY][toX] = q;
7914             DisplayMessage("Click in holdings to choose piece", "");
7915             return;
7916         }
7917         PromotionPopUp(promoChoice);
7918     } else {
7919         int oldMove = currentMove;
7920         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7921         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7922         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7923         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7924         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7925            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7926             DrawPosition(TRUE, boards[currentMove]);
7927         fromX = fromY = -1;
7928         flashing = 0;
7929     }
7930     appData.animate = saveAnimate;
7931     if (appData.animate || appData.animateDragging) {
7932         /* Undo animation damage if needed */
7933 //      DrawPosition(FALSE, NULL);
7934     }
7935 }
7936
7937 int
7938 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7939 {   // front-end-free part taken out of PieceMenuPopup
7940     int whichMenu; int xSqr, ySqr;
7941
7942     if(seekGraphUp) { // [HGM] seekgraph
7943         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7944         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7945         return -2;
7946     }
7947
7948     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7949          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7950         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7951         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7952         if(action == Press)   {
7953             originalFlip = flipView;
7954             flipView = !flipView; // temporarily flip board to see game from partners perspective
7955             DrawPosition(TRUE, partnerBoard);
7956             DisplayMessage(partnerStatus, "");
7957             partnerUp = TRUE;
7958         } else if(action == Release) {
7959             flipView = originalFlip;
7960             DrawPosition(TRUE, boards[currentMove]);
7961             partnerUp = FALSE;
7962         }
7963         return -2;
7964     }
7965
7966     xSqr = EventToSquare(x, BOARD_WIDTH);
7967     ySqr = EventToSquare(y, BOARD_HEIGHT);
7968     if (action == Release) {
7969         if(pieceSweep != EmptySquare) {
7970             EditPositionMenuEvent(pieceSweep, toX, toY);
7971             pieceSweep = EmptySquare;
7972         } else UnLoadPV(); // [HGM] pv
7973     }
7974     if (action != Press) return -2; // return code to be ignored
7975     switch (gameMode) {
7976       case IcsExamining:
7977         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7978       case EditPosition:
7979         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7980         if (xSqr < 0 || ySqr < 0) return -1;
7981         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7982         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7983         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7984         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7985         NextPiece(0);
7986         return 2; // grab
7987       case IcsObserving:
7988         if(!appData.icsEngineAnalyze) return -1;
7989       case IcsPlayingWhite:
7990       case IcsPlayingBlack:
7991         if(!appData.zippyPlay) goto noZip;
7992       case AnalyzeMode:
7993       case AnalyzeFile:
7994       case MachinePlaysWhite:
7995       case MachinePlaysBlack:
7996       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7997         if (!appData.dropMenu) {
7998           LoadPV(x, y);
7999           return 2; // flag front-end to grab mouse events
8000         }
8001         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8002            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8003       case EditGame:
8004       noZip:
8005         if (xSqr < 0 || ySqr < 0) return -1;
8006         if (!appData.dropMenu || appData.testLegality &&
8007             gameInfo.variant != VariantBughouse &&
8008             gameInfo.variant != VariantCrazyhouse) return -1;
8009         whichMenu = 1; // drop menu
8010         break;
8011       default:
8012         return -1;
8013     }
8014
8015     if (((*fromX = xSqr) < 0) ||
8016         ((*fromY = ySqr) < 0)) {
8017         *fromX = *fromY = -1;
8018         return -1;
8019     }
8020     if (flipView)
8021       *fromX = BOARD_WIDTH - 1 - *fromX;
8022     else
8023       *fromY = BOARD_HEIGHT - 1 - *fromY;
8024
8025     return whichMenu;
8026 }
8027
8028 void
8029 Wheel (int dir, int x, int y)
8030 {
8031     if(gameMode == EditPosition) {
8032         int xSqr = EventToSquare(x, BOARD_WIDTH);
8033         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8034         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8035         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8036         do {
8037             boards[currentMove][ySqr][xSqr] += dir;
8038             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8039             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8040         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8041         DrawPosition(FALSE, boards[currentMove]);
8042     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8043 }
8044
8045 void
8046 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8047 {
8048 //    char * hint = lastHint;
8049     FrontEndProgramStats stats;
8050
8051     stats.which = cps == &first ? 0 : 1;
8052     stats.depth = cpstats->depth;
8053     stats.nodes = cpstats->nodes;
8054     stats.score = cpstats->score;
8055     stats.time = cpstats->time;
8056     stats.pv = cpstats->movelist;
8057     stats.hint = lastHint;
8058     stats.an_move_index = 0;
8059     stats.an_move_count = 0;
8060
8061     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8062         stats.hint = cpstats->move_name;
8063         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8064         stats.an_move_count = cpstats->nr_moves;
8065     }
8066
8067     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
8068
8069     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8070         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8071
8072     SetProgramStats( &stats );
8073 }
8074
8075 void
8076 ClearEngineOutputPane (int which)
8077 {
8078     static FrontEndProgramStats dummyStats;
8079     dummyStats.which = which;
8080     dummyStats.pv = "#";
8081     SetProgramStats( &dummyStats );
8082 }
8083
8084 #define MAXPLAYERS 500
8085
8086 char *
8087 TourneyStandings (int display)
8088 {
8089     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8090     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8091     char result, *p, *names[MAXPLAYERS];
8092
8093     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8094         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8095     names[0] = p = strdup(appData.participants);
8096     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8097
8098     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8099
8100     while(result = appData.results[nr]) {
8101         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8102         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8103         wScore = bScore = 0;
8104         switch(result) {
8105           case '+': wScore = 2; break;
8106           case '-': bScore = 2; break;
8107           case '=': wScore = bScore = 1; break;
8108           case ' ':
8109           case '*': return strdup("busy"); // tourney not finished
8110         }
8111         score[w] += wScore;
8112         score[b] += bScore;
8113         games[w]++;
8114         games[b]++;
8115         nr++;
8116     }
8117     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8118     for(w=0; w<nPlayers; w++) {
8119         bScore = -1;
8120         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8121         ranking[w] = b; points[w] = bScore; score[b] = -2;
8122     }
8123     p = malloc(nPlayers*34+1);
8124     for(w=0; w<nPlayers && w<display; w++)
8125         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8126     free(names[0]);
8127     return p;
8128 }
8129
8130 void
8131 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8132 {       // count all piece types
8133         int p, f, r;
8134         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8135         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8136         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8137                 p = board[r][f];
8138                 pCnt[p]++;
8139                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8140                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8141                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8142                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8143                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8144                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8145         }
8146 }
8147
8148 int
8149 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8150 {
8151         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8152         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8153
8154         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8155         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8156         if(myPawns == 2 && nMine == 3) // KPP
8157             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8158         if(myPawns == 1 && nMine == 2) // KP
8159             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8160         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8161             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8162         if(myPawns) return FALSE;
8163         if(pCnt[WhiteRook+side])
8164             return pCnt[BlackRook-side] ||
8165                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8166                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8167                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8168         if(pCnt[WhiteCannon+side]) {
8169             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8170             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8171         }
8172         if(pCnt[WhiteKnight+side])
8173             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8174         return FALSE;
8175 }
8176
8177 int
8178 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8179 {
8180         VariantClass v = gameInfo.variant;
8181
8182         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8183         if(v == VariantShatranj) return TRUE; // always winnable through baring
8184         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8185         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8186
8187         if(v == VariantXiangqi) {
8188                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8189
8190                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8191                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8192                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8193                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8194                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8195                 if(stale) // we have at least one last-rank P plus perhaps C
8196                     return majors // KPKX
8197                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8198                 else // KCA*E*
8199                     return pCnt[WhiteFerz+side] // KCAK
8200                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8201                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8202                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8203
8204         } else if(v == VariantKnightmate) {
8205                 if(nMine == 1) return FALSE;
8206                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8207         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8208                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8209
8210                 if(nMine == 1) return FALSE; // bare King
8211                 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
8212                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8213                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8214                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8215                 if(pCnt[WhiteKnight+side])
8216                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8217                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8218                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8219                 if(nBishops)
8220                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8221                 if(pCnt[WhiteAlfil+side])
8222                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8223                 if(pCnt[WhiteWazir+side])
8224                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8225         }
8226
8227         return TRUE;
8228 }
8229
8230 int
8231 CompareWithRights (Board b1, Board b2)
8232 {
8233     int rights = 0;
8234     if(!CompareBoards(b1, b2)) return FALSE;
8235     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8236     /* compare castling rights */
8237     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8238            rights++; /* King lost rights, while rook still had them */
8239     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8240         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8241            rights++; /* but at least one rook lost them */
8242     }
8243     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8244            rights++;
8245     if( b1[CASTLING][5] != NoRights ) {
8246         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8247            rights++;
8248     }
8249     return rights == 0;
8250 }
8251
8252 int
8253 Adjudicate (ChessProgramState *cps)
8254 {       // [HGM] some adjudications useful with buggy engines
8255         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8256         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8257         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8258         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8259         int k, drop, count = 0; static int bare = 1;
8260         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8261         Boolean canAdjudicate = !appData.icsActive;
8262
8263         // most tests only when we understand the game, i.e. legality-checking on
8264             if( appData.testLegality )
8265             {   /* [HGM] Some more adjudications for obstinate engines */
8266                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8267                 static int moveCount = 6;
8268                 ChessMove result;
8269                 char *reason = NULL;
8270
8271                 /* Count what is on board. */
8272                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8273
8274                 /* Some material-based adjudications that have to be made before stalemate test */
8275                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8276                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8277                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8278                      if(canAdjudicate && appData.checkMates) {
8279                          if(engineOpponent)
8280                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8281                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8282                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8283                          return 1;
8284                      }
8285                 }
8286
8287                 /* Bare King in Shatranj (loses) or Losers (wins) */
8288                 if( nrW == 1 || nrB == 1) {
8289                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8290                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8291                      if(canAdjudicate && appData.checkMates) {
8292                          if(engineOpponent)
8293                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8294                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8295                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8296                          return 1;
8297                      }
8298                   } else
8299                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8300                   {    /* bare King */
8301                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8302                         if(canAdjudicate && appData.checkMates) {
8303                             /* but only adjudicate if adjudication enabled */
8304                             if(engineOpponent)
8305                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8306                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8307                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8308                             return 1;
8309                         }
8310                   }
8311                 } else bare = 1;
8312
8313
8314             // don't wait for engine to announce game end if we can judge ourselves
8315             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8316               case MT_CHECK:
8317                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8318                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8319                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8320                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8321                             checkCnt++;
8322                         if(checkCnt >= 2) {
8323                             reason = "Xboard adjudication: 3rd check";
8324                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8325                             break;
8326                         }
8327                     }
8328                 }
8329               case MT_NONE:
8330               default:
8331                 break;
8332               case MT_STEALMATE:
8333               case MT_STALEMATE:
8334               case MT_STAINMATE:
8335                 reason = "Xboard adjudication: Stalemate";
8336                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8337                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8338                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8339                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8340                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8341                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8342                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8343                                                                         EP_CHECKMATE : EP_WINS);
8344                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8345                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8346                 }
8347                 break;
8348               case MT_CHECKMATE:
8349                 reason = "Xboard adjudication: Checkmate";
8350                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8351                 if(gameInfo.variant == VariantShogi) {
8352                     if(forwardMostMove > backwardMostMove
8353                        && moveList[forwardMostMove-1][1] == '@'
8354                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8355                         reason = "XBoard adjudication: pawn-drop mate";
8356                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8357                     }
8358                 }
8359                 break;
8360             }
8361
8362                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8363                     case EP_STALEMATE:
8364                         result = GameIsDrawn; break;
8365                     case EP_CHECKMATE:
8366                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8367                     case EP_WINS:
8368                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8369                     default:
8370                         result = EndOfFile;
8371                 }
8372                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8373                     if(engineOpponent)
8374                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8375                     GameEnds( result, reason, GE_XBOARD );
8376                     return 1;
8377                 }
8378
8379                 /* Next absolutely insufficient mating material. */
8380                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8381                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8382                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8383
8384                      /* always flag draws, for judging claims */
8385                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8386
8387                      if(canAdjudicate && appData.materialDraws) {
8388                          /* but only adjudicate them if adjudication enabled */
8389                          if(engineOpponent) {
8390                            SendToProgram("force\n", engineOpponent); // suppress reply
8391                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8392                          }
8393                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8394                          return 1;
8395                      }
8396                 }
8397
8398                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8399                 if(gameInfo.variant == VariantXiangqi ?
8400                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8401                  : nrW + nrB == 4 &&
8402                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8403                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8404                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8405                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8406                    ) ) {
8407                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8408                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8409                           if(engineOpponent) {
8410                             SendToProgram("force\n", engineOpponent); // suppress reply
8411                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8412                           }
8413                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8414                           return 1;
8415                      }
8416                 } else moveCount = 6;
8417             }
8418
8419         // Repetition draws and 50-move rule can be applied independently of legality testing
8420
8421                 /* Check for rep-draws */
8422                 count = 0;
8423                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8424                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8425                 for(k = forwardMostMove-2;
8426                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8427                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8428                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8429                     k-=2)
8430                 {   int rights=0;
8431                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8432                         /* compare castling rights */
8433                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8434                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8435                                 rights++; /* King lost rights, while rook still had them */
8436                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8437                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8438                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8439                                    rights++; /* but at least one rook lost them */
8440                         }
8441                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8442                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8443                                 rights++;
8444                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8445                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8446                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8447                                    rights++;
8448                         }
8449                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8450                             && appData.drawRepeats > 1) {
8451                              /* adjudicate after user-specified nr of repeats */
8452                              int result = GameIsDrawn;
8453                              char *details = "XBoard adjudication: repetition draw";
8454                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8455                                 // [HGM] xiangqi: check for forbidden perpetuals
8456                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8457                                 for(m=forwardMostMove; m>k; m-=2) {
8458                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8459                                         ourPerpetual = 0; // the current mover did not always check
8460                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8461                                         hisPerpetual = 0; // the opponent did not always check
8462                                 }
8463                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8464                                                                         ourPerpetual, hisPerpetual);
8465                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8466                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8467                                     details = "Xboard adjudication: perpetual checking";
8468                                 } else
8469                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8470                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8471                                 } else
8472                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8473                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8474                                         result = BlackWins;
8475                                         details = "Xboard adjudication: repetition";
8476                                     }
8477                                 } else // it must be XQ
8478                                 // Now check for perpetual chases
8479                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8480                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8481                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8482                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8483                                         static char resdet[MSG_SIZ];
8484                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8485                                         details = resdet;
8486                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8487                                     } else
8488                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8489                                         break; // Abort repetition-checking loop.
8490                                 }
8491                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8492                              }
8493                              if(engineOpponent) {
8494                                SendToProgram("force\n", engineOpponent); // suppress reply
8495                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8496                              }
8497                              GameEnds( result, details, GE_XBOARD );
8498                              return 1;
8499                         }
8500                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8501                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8502                     }
8503                 }
8504
8505                 /* Now we test for 50-move draws. Determine ply count */
8506                 count = forwardMostMove;
8507                 /* look for last irreversble move */
8508                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8509                     count--;
8510                 /* if we hit starting position, add initial plies */
8511                 if( count == backwardMostMove )
8512                     count -= initialRulePlies;
8513                 count = forwardMostMove - count;
8514                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8515                         // adjust reversible move counter for checks in Xiangqi
8516                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8517                         if(i < backwardMostMove) i = backwardMostMove;
8518                         while(i <= forwardMostMove) {
8519                                 lastCheck = inCheck; // check evasion does not count
8520                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8521                                 if(inCheck || lastCheck) count--; // check does not count
8522                                 i++;
8523                         }
8524                 }
8525                 if( count >= 100)
8526                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8527                          /* this is used to judge if draw claims are legal */
8528                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8529                          if(engineOpponent) {
8530                            SendToProgram("force\n", engineOpponent); // suppress reply
8531                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8532                          }
8533                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8534                          return 1;
8535                 }
8536
8537                 /* if draw offer is pending, treat it as a draw claim
8538                  * when draw condition present, to allow engines a way to
8539                  * claim draws before making their move to avoid a race
8540                  * condition occurring after their move
8541                  */
8542                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8543                          char *p = NULL;
8544                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8545                              p = "Draw claim: 50-move rule";
8546                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8547                              p = "Draw claim: 3-fold repetition";
8548                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8549                              p = "Draw claim: insufficient mating material";
8550                          if( p != NULL && canAdjudicate) {
8551                              if(engineOpponent) {
8552                                SendToProgram("force\n", engineOpponent); // suppress reply
8553                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8554                              }
8555                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8556                              return 1;
8557                          }
8558                 }
8559
8560                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8561                     if(engineOpponent) {
8562                       SendToProgram("force\n", engineOpponent); // suppress reply
8563                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8564                     }
8565                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8566                     return 1;
8567                 }
8568         return 0;
8569 }
8570
8571 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8572 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8573 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8574
8575 static int
8576 BitbaseProbe ()
8577 {
8578     int pieces[10], squares[10], cnt=0, r, f, res;
8579     static int loaded;
8580     static PPROBE_EGBB probeBB;
8581     if(!appData.testLegality) return 10;
8582     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8583     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8584     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8585     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8586         ChessSquare piece = boards[forwardMostMove][r][f];
8587         int black = (piece >= BlackPawn);
8588         int type = piece - black*BlackPawn;
8589         if(piece == EmptySquare) continue;
8590         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8591         if(type == WhiteKing) type = WhiteQueen + 1;
8592         type = egbbCode[type];
8593         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8594         pieces[cnt] = type + black*6;
8595         if(++cnt > 5) return 11;
8596     }
8597     pieces[cnt] = squares[cnt] = 0;
8598     // probe EGBB
8599     if(loaded == 2) return 13; // loading failed before
8600     if(loaded == 0) {
8601         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8602         HMODULE lib;
8603         PLOAD_EGBB loadBB;
8604         loaded = 2; // prepare for failure
8605         if(!path) return 13; // no egbb installed
8606         strncpy(buf, path + 8, MSG_SIZ);
8607         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8608         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8609         lib = LoadLibrary(buf);
8610         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8611         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8612         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8613         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8614         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8615         loaded = 1; // success!
8616     }
8617     res = probeBB(forwardMostMove & 1, pieces, squares);
8618     return res > 0 ? 1 : res < 0 ? -1 : 0;
8619 }
8620
8621 char *
8622 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8623 {   // [HGM] book: this routine intercepts moves to simulate book replies
8624     char *bookHit = NULL;
8625
8626     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8627         char buf[MSG_SIZ];
8628         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8629         SendToProgram(buf, cps);
8630     }
8631     //first determine if the incoming move brings opponent into his book
8632     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8633         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8634     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8635     if(bookHit != NULL && !cps->bookSuspend) {
8636         // make sure opponent is not going to reply after receiving move to book position
8637         SendToProgram("force\n", cps);
8638         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8639     }
8640     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8641     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8642     // now arrange restart after book miss
8643     if(bookHit) {
8644         // after a book hit we never send 'go', and the code after the call to this routine
8645         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8646         char buf[MSG_SIZ], *move = bookHit;
8647         if(cps->useSAN) {
8648             int fromX, fromY, toX, toY;
8649             char promoChar;
8650             ChessMove moveType;
8651             move = buf + 30;
8652             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8653                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8654                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8655                                     PosFlags(forwardMostMove),
8656                                     fromY, fromX, toY, toX, promoChar, move);
8657             } else {
8658                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8659                 bookHit = NULL;
8660             }
8661         }
8662         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8663         SendToProgram(buf, cps);
8664         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8665     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8666         SendToProgram("go\n", cps);
8667         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8668     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8669         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8670             SendToProgram("go\n", cps);
8671         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8672     }
8673     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8674 }
8675
8676 int
8677 LoadError (char *errmess, ChessProgramState *cps)
8678 {   // unloads engine and switches back to -ncp mode if it was first
8679     if(cps->initDone) return FALSE;
8680     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8681     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8682     cps->pr = NoProc;
8683     if(cps == &first) {
8684         appData.noChessProgram = TRUE;
8685         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8686         gameMode = BeginningOfGame; ModeHighlight();
8687         SetNCPMode();
8688     }
8689     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8690     DisplayMessage("", ""); // erase waiting message
8691     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8692     return TRUE;
8693 }
8694
8695 char *savedMessage;
8696 ChessProgramState *savedState;
8697 void
8698 DeferredBookMove (void)
8699 {
8700         if(savedState->lastPing != savedState->lastPong)
8701                     ScheduleDelayedEvent(DeferredBookMove, 10);
8702         else
8703         HandleMachineMove(savedMessage, savedState);
8704 }
8705
8706 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8707 static ChessProgramState *stalledEngine;
8708 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8709
8710 void
8711 HandleMachineMove (char *message, ChessProgramState *cps)
8712 {
8713     static char firstLeg[20], legs;
8714     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8715     char realname[MSG_SIZ];
8716     int fromX, fromY, toX, toY;
8717     ChessMove moveType;
8718     char promoChar, roar;
8719     char *p, *pv=buf1;
8720     int oldError;
8721     char *bookHit;
8722
8723     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8724         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8725         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8726             DisplayError(_("Invalid pairing from pairing engine"), 0);
8727             return;
8728         }
8729         pairingReceived = 1;
8730         NextMatchGame();
8731         return; // Skim the pairing messages here.
8732     }
8733
8734     oldError = cps->userError; cps->userError = 0;
8735
8736 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8737     /*
8738      * Kludge to ignore BEL characters
8739      */
8740     while (*message == '\007') message++;
8741
8742     /*
8743      * [HGM] engine debug message: ignore lines starting with '#' character
8744      */
8745     if(cps->debug && *message == '#') return;
8746
8747     /*
8748      * Look for book output
8749      */
8750     if (cps == &first && bookRequested) {
8751         if (message[0] == '\t' || message[0] == ' ') {
8752             /* Part of the book output is here; append it */
8753             strcat(bookOutput, message);
8754             strcat(bookOutput, "  \n");
8755             return;
8756         } else if (bookOutput[0] != NULLCHAR) {
8757             /* All of book output has arrived; display it */
8758             char *p = bookOutput;
8759             while (*p != NULLCHAR) {
8760                 if (*p == '\t') *p = ' ';
8761                 p++;
8762             }
8763             DisplayInformation(bookOutput);
8764             bookRequested = FALSE;
8765             /* Fall through to parse the current output */
8766         }
8767     }
8768
8769     /*
8770      * Look for machine move.
8771      */
8772     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8773         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8774     {
8775         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8776             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8777             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8778             stalledEngine = cps;
8779             if(appData.ponderNextMove) { // bring opponent out of ponder
8780                 if(gameMode == TwoMachinesPlay) {
8781                     if(cps->other->pause)
8782                         PauseEngine(cps->other);
8783                     else
8784                         SendToProgram("easy\n", cps->other);
8785                 }
8786             }
8787             StopClocks();
8788             return;
8789         }
8790
8791       if(cps->usePing) {
8792
8793         /* This method is only useful on engines that support ping */
8794         if(abortEngineThink) {
8795             if (appData.debugMode) {
8796                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8797             }
8798             SendToProgram("undo\n", cps);
8799             return;
8800         }
8801
8802         if (cps->lastPing != cps->lastPong) {
8803             /* Extra move from before last new; ignore */
8804             if (appData.debugMode) {
8805                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8806             }
8807           return;
8808         }
8809
8810       } else {
8811
8812         int machineWhite = FALSE;
8813
8814         switch (gameMode) {
8815           case BeginningOfGame:
8816             /* Extra move from before last reset; ignore */
8817             if (appData.debugMode) {
8818                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8819             }
8820             return;
8821
8822           case EndOfGame:
8823           case IcsIdle:
8824           default:
8825             /* Extra move after we tried to stop.  The mode test is
8826                not a reliable way of detecting this problem, but it's
8827                the best we can do on engines that don't support ping.
8828             */
8829             if (appData.debugMode) {
8830                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8831                         cps->which, gameMode);
8832             }
8833             SendToProgram("undo\n", cps);
8834             return;
8835
8836           case MachinePlaysWhite:
8837           case IcsPlayingWhite:
8838             machineWhite = TRUE;
8839             break;
8840
8841           case MachinePlaysBlack:
8842           case IcsPlayingBlack:
8843             machineWhite = FALSE;
8844             break;
8845
8846           case TwoMachinesPlay:
8847             machineWhite = (cps->twoMachinesColor[0] == 'w');
8848             break;
8849         }
8850         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8851             if (appData.debugMode) {
8852                 fprintf(debugFP,
8853                         "Ignoring move out of turn by %s, gameMode %d"
8854                         ", forwardMost %d\n",
8855                         cps->which, gameMode, forwardMostMove);
8856             }
8857             return;
8858         }
8859       }
8860
8861         if(cps->alphaRank) AlphaRank(machineMove, 4);
8862
8863         // [HGM] lion: (some very limited) support for Alien protocol
8864         killX = killY = kill2X = kill2Y = -1;
8865         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8866             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8867             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8868             return;
8869         }
8870         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8871             char *q = strchr(p+1, ',');            // second comma?
8872             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8873             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8874             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8875         }
8876         if(firstLeg[0]) { // there was a previous leg;
8877             // only support case where same piece makes two step
8878             char buf[20], *p = machineMove+1, *q = buf+1, f;
8879             safeStrCpy(buf, machineMove, 20);
8880             while(isdigit(*q)) q++; // find start of to-square
8881             safeStrCpy(machineMove, firstLeg, 20);
8882             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8883             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
8884             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)
8885             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8886             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8887             firstLeg[0] = NULLCHAR; legs = 0;
8888         }
8889
8890         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8891                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8892             /* Machine move could not be parsed; ignore it. */
8893           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8894                     machineMove, _(cps->which));
8895             DisplayMoveError(buf1);
8896             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8897                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8898             if (gameMode == TwoMachinesPlay) {
8899               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8900                        buf1, GE_XBOARD);
8901             }
8902             return;
8903         }
8904
8905         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8906         /* So we have to redo legality test with true e.p. status here,  */
8907         /* to make sure an illegal e.p. capture does not slip through,   */
8908         /* to cause a forfeit on a justified illegal-move complaint      */
8909         /* of the opponent.                                              */
8910         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8911            ChessMove moveType;
8912            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8913                              fromY, fromX, toY, toX, promoChar);
8914             if(moveType == IllegalMove) {
8915               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8916                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8917                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8918                            buf1, GE_XBOARD);
8919                 return;
8920            } else if(!appData.fischerCastling)
8921            /* [HGM] Kludge to handle engines that send FRC-style castling
8922               when they shouldn't (like TSCP-Gothic) */
8923            switch(moveType) {
8924              case WhiteASideCastleFR:
8925              case BlackASideCastleFR:
8926                toX+=2;
8927                currentMoveString[2]++;
8928                break;
8929              case WhiteHSideCastleFR:
8930              case BlackHSideCastleFR:
8931                toX--;
8932                currentMoveString[2]--;
8933                break;
8934              default: ; // nothing to do, but suppresses warning of pedantic compilers
8935            }
8936         }
8937         hintRequested = FALSE;
8938         lastHint[0] = NULLCHAR;
8939         bookRequested = FALSE;
8940         /* Program may be pondering now */
8941         cps->maybeThinking = TRUE;
8942         if (cps->sendTime == 2) cps->sendTime = 1;
8943         if (cps->offeredDraw) cps->offeredDraw--;
8944
8945         /* [AS] Save move info*/
8946         pvInfoList[ forwardMostMove ].score = programStats.score;
8947         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8948         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8949
8950         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8951
8952         /* Test suites abort the 'game' after one move */
8953         if(*appData.finger) {
8954            static FILE *f;
8955            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8956            if(!f) f = fopen(appData.finger, "w");
8957            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8958            else { DisplayFatalError("Bad output file", errno, 0); return; }
8959            free(fen);
8960            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8961         }
8962         if(appData.epd) {
8963            if(solvingTime >= 0) {
8964               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8965               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8966            } else {
8967               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8968               if(solvingTime == -2) second.matchWins++;
8969            }
8970            OutputKibitz(2, buf1);
8971            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8972         }
8973
8974         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8975         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8976             int count = 0;
8977
8978             while( count < adjudicateLossPlies ) {
8979                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8980
8981                 if( count & 1 ) {
8982                     score = -score; /* Flip score for winning side */
8983                 }
8984
8985                 if( score > appData.adjudicateLossThreshold ) {
8986                     break;
8987                 }
8988
8989                 count++;
8990             }
8991
8992             if( count >= adjudicateLossPlies ) {
8993                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8994
8995                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8996                     "Xboard adjudication",
8997                     GE_XBOARD );
8998
8999                 return;
9000             }
9001         }
9002
9003         if(Adjudicate(cps)) {
9004             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9005             return; // [HGM] adjudicate: for all automatic game ends
9006         }
9007
9008 #if ZIPPY
9009         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9010             first.initDone) {
9011           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9012                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9013                 SendToICS("draw ");
9014                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9015           }
9016           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9017           ics_user_moved = 1;
9018           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9019                 char buf[3*MSG_SIZ];
9020
9021                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9022                         programStats.score / 100.,
9023                         programStats.depth,
9024                         programStats.time / 100.,
9025                         (unsigned int)programStats.nodes,
9026                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9027                         programStats.movelist);
9028                 SendToICS(buf);
9029           }
9030         }
9031 #endif
9032
9033         /* [AS] Clear stats for next move */
9034         ClearProgramStats();
9035         thinkOutput[0] = NULLCHAR;
9036         hiddenThinkOutputState = 0;
9037
9038         bookHit = NULL;
9039         if (gameMode == TwoMachinesPlay) {
9040             /* [HGM] relaying draw offers moved to after reception of move */
9041             /* and interpreting offer as claim if it brings draw condition */
9042             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9043                 SendToProgram("draw\n", cps->other);
9044             }
9045             if (cps->other->sendTime) {
9046                 SendTimeRemaining(cps->other,
9047                                   cps->other->twoMachinesColor[0] == 'w');
9048             }
9049             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9050             if (firstMove && !bookHit) {
9051                 firstMove = FALSE;
9052                 if (cps->other->useColors) {
9053                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9054                 }
9055                 SendToProgram("go\n", cps->other);
9056             }
9057             cps->other->maybeThinking = TRUE;
9058         }
9059
9060         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9061
9062         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9063
9064         if (!pausing && appData.ringBellAfterMoves) {
9065             if(!roar) RingBell();
9066         }
9067
9068         /*
9069          * Reenable menu items that were disabled while
9070          * machine was thinking
9071          */
9072         if (gameMode != TwoMachinesPlay)
9073             SetUserThinkingEnables();
9074
9075         // [HGM] book: after book hit opponent has received move and is now in force mode
9076         // force the book reply into it, and then fake that it outputted this move by jumping
9077         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9078         if(bookHit) {
9079                 static char bookMove[MSG_SIZ]; // a bit generous?
9080
9081                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9082                 strcat(bookMove, bookHit);
9083                 message = bookMove;
9084                 cps = cps->other;
9085                 programStats.nodes = programStats.depth = programStats.time =
9086                 programStats.score = programStats.got_only_move = 0;
9087                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9088
9089                 if(cps->lastPing != cps->lastPong) {
9090                     savedMessage = message; // args for deferred call
9091                     savedState = cps;
9092                     ScheduleDelayedEvent(DeferredBookMove, 10);
9093                     return;
9094                 }
9095                 goto FakeBookMove;
9096         }
9097
9098         return;
9099     }
9100
9101     /* Set special modes for chess engines.  Later something general
9102      *  could be added here; for now there is just one kludge feature,
9103      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9104      *  when "xboard" is given as an interactive command.
9105      */
9106     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9107         cps->useSigint = FALSE;
9108         cps->useSigterm = FALSE;
9109     }
9110     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9111       ParseFeatures(message+8, cps);
9112       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9113     }
9114
9115     if (!strncmp(message, "setup ", 6) && 
9116         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9117           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9118                                         ) { // [HGM] allow first engine to define opening position
9119       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9120       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9121       *buf = NULLCHAR;
9122       if(sscanf(message, "setup (%s", buf) == 1) {
9123         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9124         ASSIGN(appData.pieceToCharTable, buf);
9125       }
9126       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9127       if(dummy >= 3) {
9128         while(message[s] && message[s++] != ' ');
9129         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9130            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9131             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9132             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9133           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9134           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9135           startedFromSetupPosition = FALSE;
9136         }
9137       }
9138       if(startedFromSetupPosition) return;
9139       ParseFEN(boards[0], &dummy, message+s, FALSE);
9140       DrawPosition(TRUE, boards[0]);
9141       CopyBoard(initialPosition, boards[0]);
9142       startedFromSetupPosition = TRUE;
9143       return;
9144     }
9145     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9146       ChessSquare piece = WhitePawn;
9147       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9148       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9149       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9150       piece += CharToPiece(ID & 255) - WhitePawn;
9151       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9152       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9153       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9154       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9155       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9156       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9157                                                && gameInfo.variant != VariantGreat
9158                                                && gameInfo.variant != VariantFairy    ) return;
9159       if(piece < EmptySquare) {
9160         pieceDefs = TRUE;
9161         ASSIGN(pieceDesc[piece], buf1);
9162         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9163       }
9164       return;
9165     }
9166     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9167       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9168       Sweep(0);
9169       return;
9170     }
9171     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9172      * want this, I was asked to put it in, and obliged.
9173      */
9174     if (!strncmp(message, "setboard ", 9)) {
9175         Board initial_position;
9176
9177         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9178
9179         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9180             DisplayError(_("Bad FEN received from engine"), 0);
9181             return ;
9182         } else {
9183            Reset(TRUE, FALSE);
9184            CopyBoard(boards[0], initial_position);
9185            initialRulePlies = FENrulePlies;
9186            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9187            else gameMode = MachinePlaysBlack;
9188            DrawPosition(FALSE, boards[currentMove]);
9189         }
9190         return;
9191     }
9192
9193     /*
9194      * Look for communication commands
9195      */
9196     if (!strncmp(message, "telluser ", 9)) {
9197         if(message[9] == '\\' && message[10] == '\\')
9198             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9199         PlayTellSound();
9200         DisplayNote(message + 9);
9201         return;
9202     }
9203     if (!strncmp(message, "tellusererror ", 14)) {
9204         cps->userError = 1;
9205         if(message[14] == '\\' && message[15] == '\\')
9206             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9207         PlayTellSound();
9208         DisplayError(message + 14, 0);
9209         return;
9210     }
9211     if (!strncmp(message, "tellopponent ", 13)) {
9212       if (appData.icsActive) {
9213         if (loggedOn) {
9214           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9215           SendToICS(buf1);
9216         }
9217       } else {
9218         DisplayNote(message + 13);
9219       }
9220       return;
9221     }
9222     if (!strncmp(message, "tellothers ", 11)) {
9223       if (appData.icsActive) {
9224         if (loggedOn) {
9225           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9226           SendToICS(buf1);
9227         }
9228       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9229       return;
9230     }
9231     if (!strncmp(message, "tellall ", 8)) {
9232       if (appData.icsActive) {
9233         if (loggedOn) {
9234           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9235           SendToICS(buf1);
9236         }
9237       } else {
9238         DisplayNote(message + 8);
9239       }
9240       return;
9241     }
9242     if (strncmp(message, "warning", 7) == 0) {
9243         /* Undocumented feature, use tellusererror in new code */
9244         DisplayError(message, 0);
9245         return;
9246     }
9247     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9248         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9249         strcat(realname, " query");
9250         AskQuestion(realname, buf2, buf1, cps->pr);
9251         return;
9252     }
9253     /* Commands from the engine directly to ICS.  We don't allow these to be
9254      *  sent until we are logged on. Crafty kibitzes have been known to
9255      *  interfere with the login process.
9256      */
9257     if (loggedOn) {
9258         if (!strncmp(message, "tellics ", 8)) {
9259             SendToICS(message + 8);
9260             SendToICS("\n");
9261             return;
9262         }
9263         if (!strncmp(message, "tellicsnoalias ", 15)) {
9264             SendToICS(ics_prefix);
9265             SendToICS(message + 15);
9266             SendToICS("\n");
9267             return;
9268         }
9269         /* The following are for backward compatibility only */
9270         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9271             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9272             SendToICS(ics_prefix);
9273             SendToICS(message);
9274             SendToICS("\n");
9275             return;
9276         }
9277     }
9278     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9279         if(initPing == cps->lastPong) {
9280             if(gameInfo.variant == VariantUnknown) {
9281                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9282                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9283                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9284             }
9285             initPing = -1;
9286         }
9287         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9288             abortEngineThink = FALSE;
9289             DisplayMessage("", "");
9290             ThawUI();
9291         }
9292         return;
9293     }
9294     if(!strncmp(message, "highlight ", 10)) {
9295         if(appData.testLegality && !*engineVariant && appData.markers) return;
9296         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9297         return;
9298     }
9299     if(!strncmp(message, "click ", 6)) {
9300         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9301         if(appData.testLegality || !appData.oneClick) return;
9302         sscanf(message+6, "%c%d%c", &f, &y, &c);
9303         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9304         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9305         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9306         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9307         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9308         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9309             LeftClick(Release, lastLeftX, lastLeftY);
9310         controlKey  = (c == ',');
9311         LeftClick(Press, x, y);
9312         LeftClick(Release, x, y);
9313         first.highlight = f;
9314         return;
9315     }
9316     /*
9317      * If the move is illegal, cancel it and redraw the board.
9318      * Also deal with other error cases.  Matching is rather loose
9319      * here to accommodate engines written before the spec.
9320      */
9321     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9322         strncmp(message, "Error", 5) == 0) {
9323         if (StrStr(message, "name") ||
9324             StrStr(message, "rating") || StrStr(message, "?") ||
9325             StrStr(message, "result") || StrStr(message, "board") ||
9326             StrStr(message, "bk") || StrStr(message, "computer") ||
9327             StrStr(message, "variant") || StrStr(message, "hint") ||
9328             StrStr(message, "random") || StrStr(message, "depth") ||
9329             StrStr(message, "accepted")) {
9330             return;
9331         }
9332         if (StrStr(message, "protover")) {
9333           /* Program is responding to input, so it's apparently done
9334              initializing, and this error message indicates it is
9335              protocol version 1.  So we don't need to wait any longer
9336              for it to initialize and send feature commands. */
9337           FeatureDone(cps, 1);
9338           cps->protocolVersion = 1;
9339           return;
9340         }
9341         cps->maybeThinking = FALSE;
9342
9343         if (StrStr(message, "draw")) {
9344             /* Program doesn't have "draw" command */
9345             cps->sendDrawOffers = 0;
9346             return;
9347         }
9348         if (cps->sendTime != 1 &&
9349             (StrStr(message, "time") || StrStr(message, "otim"))) {
9350           /* Program apparently doesn't have "time" or "otim" command */
9351           cps->sendTime = 0;
9352           return;
9353         }
9354         if (StrStr(message, "analyze")) {
9355             cps->analysisSupport = FALSE;
9356             cps->analyzing = FALSE;
9357 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9358             EditGameEvent(); // [HGM] try to preserve loaded game
9359             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9360             DisplayError(buf2, 0);
9361             return;
9362         }
9363         if (StrStr(message, "(no matching move)st")) {
9364           /* Special kludge for GNU Chess 4 only */
9365           cps->stKludge = TRUE;
9366           SendTimeControl(cps, movesPerSession, timeControl,
9367                           timeIncrement, appData.searchDepth,
9368                           searchTime);
9369           return;
9370         }
9371         if (StrStr(message, "(no matching move)sd")) {
9372           /* Special kludge for GNU Chess 4 only */
9373           cps->sdKludge = TRUE;
9374           SendTimeControl(cps, movesPerSession, timeControl,
9375                           timeIncrement, appData.searchDepth,
9376                           searchTime);
9377           return;
9378         }
9379         if (!StrStr(message, "llegal")) {
9380             return;
9381         }
9382         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9383             gameMode == IcsIdle) return;
9384         if (forwardMostMove <= backwardMostMove) return;
9385         if (pausing) PauseEvent();
9386       if(appData.forceIllegal) {
9387             // [HGM] illegal: machine refused move; force position after move into it
9388           SendToProgram("force\n", cps);
9389           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9390                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9391                 // when black is to move, while there might be nothing on a2 or black
9392                 // might already have the move. So send the board as if white has the move.
9393                 // But first we must change the stm of the engine, as it refused the last move
9394                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9395                 if(WhiteOnMove(forwardMostMove)) {
9396                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9397                     SendBoard(cps, forwardMostMove); // kludgeless board
9398                 } else {
9399                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9400                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9401                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9402                 }
9403           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9404             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9405                  gameMode == TwoMachinesPlay)
9406               SendToProgram("go\n", cps);
9407             return;
9408       } else
9409         if (gameMode == PlayFromGameFile) {
9410             /* Stop reading this game file */
9411             gameMode = EditGame;
9412             ModeHighlight();
9413         }
9414         /* [HGM] illegal-move claim should forfeit game when Xboard */
9415         /* only passes fully legal moves                            */
9416         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9417             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9418                                 "False illegal-move claim", GE_XBOARD );
9419             return; // do not take back move we tested as valid
9420         }
9421         currentMove = forwardMostMove-1;
9422         DisplayMove(currentMove-1); /* before DisplayMoveError */
9423         SwitchClocks(forwardMostMove-1); // [HGM] race
9424         DisplayBothClocks();
9425         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9426                 parseList[currentMove], _(cps->which));
9427         DisplayMoveError(buf1);
9428         DrawPosition(FALSE, boards[currentMove]);
9429
9430         SetUserThinkingEnables();
9431         return;
9432     }
9433     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9434         /* Program has a broken "time" command that
9435            outputs a string not ending in newline.
9436            Don't use it. */
9437         cps->sendTime = 0;
9438     }
9439     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9440         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9441             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9442     }
9443
9444     /*
9445      * If chess program startup fails, exit with an error message.
9446      * Attempts to recover here are futile. [HGM] Well, we try anyway
9447      */
9448     if ((StrStr(message, "unknown host") != NULL)
9449         || (StrStr(message, "No remote directory") != NULL)
9450         || (StrStr(message, "not found") != NULL)
9451         || (StrStr(message, "No such file") != NULL)
9452         || (StrStr(message, "can't alloc") != NULL)
9453         || (StrStr(message, "Permission denied") != NULL)) {
9454
9455         cps->maybeThinking = FALSE;
9456         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9457                 _(cps->which), cps->program, cps->host, message);
9458         RemoveInputSource(cps->isr);
9459         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9460             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9461             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9462         }
9463         return;
9464     }
9465
9466     /*
9467      * Look for hint output
9468      */
9469     if (sscanf(message, "Hint: %s", buf1) == 1) {
9470         if (cps == &first && hintRequested) {
9471             hintRequested = FALSE;
9472             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9473                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9474                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9475                                     PosFlags(forwardMostMove),
9476                                     fromY, fromX, toY, toX, promoChar, buf1);
9477                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9478                 DisplayInformation(buf2);
9479             } else {
9480                 /* Hint move could not be parsed!? */
9481               snprintf(buf2, sizeof(buf2),
9482                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9483                         buf1, _(cps->which));
9484                 DisplayError(buf2, 0);
9485             }
9486         } else {
9487           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9488         }
9489         return;
9490     }
9491
9492     /*
9493      * Ignore other messages if game is not in progress
9494      */
9495     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9496         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9497
9498     /*
9499      * look for win, lose, draw, or draw offer
9500      */
9501     if (strncmp(message, "1-0", 3) == 0) {
9502         char *p, *q, *r = "";
9503         p = strchr(message, '{');
9504         if (p) {
9505             q = strchr(p, '}');
9506             if (q) {
9507                 *q = NULLCHAR;
9508                 r = p + 1;
9509             }
9510         }
9511         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9512         return;
9513     } else if (strncmp(message, "0-1", 3) == 0) {
9514         char *p, *q, *r = "";
9515         p = strchr(message, '{');
9516         if (p) {
9517             q = strchr(p, '}');
9518             if (q) {
9519                 *q = NULLCHAR;
9520                 r = p + 1;
9521             }
9522         }
9523         /* Kludge for Arasan 4.1 bug */
9524         if (strcmp(r, "Black resigns") == 0) {
9525             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9526             return;
9527         }
9528         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9529         return;
9530     } else if (strncmp(message, "1/2", 3) == 0) {
9531         char *p, *q, *r = "";
9532         p = strchr(message, '{');
9533         if (p) {
9534             q = strchr(p, '}');
9535             if (q) {
9536                 *q = NULLCHAR;
9537                 r = p + 1;
9538             }
9539         }
9540
9541         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9542         return;
9543
9544     } else if (strncmp(message, "White resign", 12) == 0) {
9545         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9546         return;
9547     } else if (strncmp(message, "Black resign", 12) == 0) {
9548         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9549         return;
9550     } else if (strncmp(message, "White matches", 13) == 0 ||
9551                strncmp(message, "Black matches", 13) == 0   ) {
9552         /* [HGM] ignore GNUShogi noises */
9553         return;
9554     } else if (strncmp(message, "White", 5) == 0 &&
9555                message[5] != '(' &&
9556                StrStr(message, "Black") == NULL) {
9557         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9558         return;
9559     } else if (strncmp(message, "Black", 5) == 0 &&
9560                message[5] != '(') {
9561         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9562         return;
9563     } else if (strcmp(message, "resign") == 0 ||
9564                strcmp(message, "computer resigns") == 0) {
9565         switch (gameMode) {
9566           case MachinePlaysBlack:
9567           case IcsPlayingBlack:
9568             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9569             break;
9570           case MachinePlaysWhite:
9571           case IcsPlayingWhite:
9572             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9573             break;
9574           case TwoMachinesPlay:
9575             if (cps->twoMachinesColor[0] == 'w')
9576               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9577             else
9578               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9579             break;
9580           default:
9581             /* can't happen */
9582             break;
9583         }
9584         return;
9585     } else if (strncmp(message, "opponent mates", 14) == 0) {
9586         switch (gameMode) {
9587           case MachinePlaysBlack:
9588           case IcsPlayingBlack:
9589             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9590             break;
9591           case MachinePlaysWhite:
9592           case IcsPlayingWhite:
9593             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9594             break;
9595           case TwoMachinesPlay:
9596             if (cps->twoMachinesColor[0] == 'w')
9597               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9598             else
9599               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9600             break;
9601           default:
9602             /* can't happen */
9603             break;
9604         }
9605         return;
9606     } else if (strncmp(message, "computer mates", 14) == 0) {
9607         switch (gameMode) {
9608           case MachinePlaysBlack:
9609           case IcsPlayingBlack:
9610             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9611             break;
9612           case MachinePlaysWhite:
9613           case IcsPlayingWhite:
9614             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9615             break;
9616           case TwoMachinesPlay:
9617             if (cps->twoMachinesColor[0] == 'w')
9618               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9619             else
9620               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9621             break;
9622           default:
9623             /* can't happen */
9624             break;
9625         }
9626         return;
9627     } else if (strncmp(message, "checkmate", 9) == 0) {
9628         if (WhiteOnMove(forwardMostMove)) {
9629             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9630         } else {
9631             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9632         }
9633         return;
9634     } else if (strstr(message, "Draw") != NULL ||
9635                strstr(message, "game is a draw") != NULL) {
9636         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9637         return;
9638     } else if (strstr(message, "offer") != NULL &&
9639                strstr(message, "draw") != NULL) {
9640 #if ZIPPY
9641         if (appData.zippyPlay && first.initDone) {
9642             /* Relay offer to ICS */
9643             SendToICS(ics_prefix);
9644             SendToICS("draw\n");
9645         }
9646 #endif
9647         cps->offeredDraw = 2; /* valid until this engine moves twice */
9648         if (gameMode == TwoMachinesPlay) {
9649             if (cps->other->offeredDraw) {
9650                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9651             /* [HGM] in two-machine mode we delay relaying draw offer      */
9652             /* until after we also have move, to see if it is really claim */
9653             }
9654         } else if (gameMode == MachinePlaysWhite ||
9655                    gameMode == MachinePlaysBlack) {
9656           if (userOfferedDraw) {
9657             DisplayInformation(_("Machine accepts your draw offer"));
9658             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9659           } else {
9660             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9661           }
9662         }
9663     }
9664
9665
9666     /*
9667      * Look for thinking output
9668      */
9669     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9670           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9671                                 ) {
9672         int plylev, mvleft, mvtot, curscore, time;
9673         char mvname[MOVE_LEN];
9674         u64 nodes; // [DM]
9675         char plyext;
9676         int ignore = FALSE;
9677         int prefixHint = FALSE;
9678         mvname[0] = NULLCHAR;
9679
9680         switch (gameMode) {
9681           case MachinePlaysBlack:
9682           case IcsPlayingBlack:
9683             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9684             break;
9685           case MachinePlaysWhite:
9686           case IcsPlayingWhite:
9687             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9688             break;
9689           case AnalyzeMode:
9690           case AnalyzeFile:
9691             break;
9692           case IcsObserving: /* [DM] icsEngineAnalyze */
9693             if (!appData.icsEngineAnalyze) ignore = TRUE;
9694             break;
9695           case TwoMachinesPlay:
9696             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9697                 ignore = TRUE;
9698             }
9699             break;
9700           default:
9701             ignore = TRUE;
9702             break;
9703         }
9704
9705         if (!ignore) {
9706             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9707             int solved = 0;
9708             buf1[0] = NULLCHAR;
9709             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9710                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9711                 char score_buf[MSG_SIZ];
9712
9713                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9714                     nodes += u64Const(0x100000000);
9715
9716                 if (plyext != ' ' && plyext != '\t') {
9717                     time *= 100;
9718                 }
9719
9720                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9721                 if( cps->scoreIsAbsolute &&
9722                     ( gameMode == MachinePlaysBlack ||
9723                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9724                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9725                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9726                      !WhiteOnMove(currentMove)
9727                     ) )
9728                 {
9729                     curscore = -curscore;
9730                 }
9731
9732                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9733
9734                 if(*bestMove) { // rememer time best EPD move was first found
9735                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9736                     ChessMove mt; char *p = bestMove;
9737                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9738                     solved = 0;
9739                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9740                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9741                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9742                             solved = 1;
9743                             break;
9744                         }
9745                         while(*p && *p != ' ') p++;
9746                         while(*p == ' ') p++;
9747                     }
9748                     if(!solved) solvingTime = -1;
9749                 }
9750                 if(*avoidMove && !solved) {
9751                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9752                     ChessMove mt; char *p = avoidMove, solved = 1;
9753                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9754                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9755                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9756                             solved = 0; solvingTime = -2;
9757                             break;
9758                         }
9759                         while(*p && *p != ' ') p++;
9760                         while(*p == ' ') p++;
9761                     }
9762                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9763                 }
9764
9765                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9766                         char buf[MSG_SIZ];
9767                         FILE *f;
9768                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9769                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9770                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9771                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9772                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9773                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9774                                 fclose(f);
9775                         }
9776                         else
9777                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9778                           DisplayError(_("failed writing PV"), 0);
9779                 }
9780
9781                 tempStats.depth = plylev;
9782                 tempStats.nodes = nodes;
9783                 tempStats.time = time;
9784                 tempStats.score = curscore;
9785                 tempStats.got_only_move = 0;
9786
9787                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9788                         int ticklen;
9789
9790                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9791                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9792                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9793                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9794                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9795                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9796                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9797                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9798                 }
9799
9800                 /* Buffer overflow protection */
9801                 if (pv[0] != NULLCHAR) {
9802                     if (strlen(pv) >= sizeof(tempStats.movelist)
9803                         && appData.debugMode) {
9804                         fprintf(debugFP,
9805                                 "PV is too long; using the first %u bytes.\n",
9806                                 (unsigned) sizeof(tempStats.movelist) - 1);
9807                     }
9808
9809                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9810                 } else {
9811                     sprintf(tempStats.movelist, " no PV\n");
9812                 }
9813
9814                 if (tempStats.seen_stat) {
9815                     tempStats.ok_to_send = 1;
9816                 }
9817
9818                 if (strchr(tempStats.movelist, '(') != NULL) {
9819                     tempStats.line_is_book = 1;
9820                     tempStats.nr_moves = 0;
9821                     tempStats.moves_left = 0;
9822                 } else {
9823                     tempStats.line_is_book = 0;
9824                 }
9825
9826                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9827                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9828
9829                 SendProgramStatsToFrontend( cps, &tempStats );
9830
9831                 /*
9832                     [AS] Protect the thinkOutput buffer from overflow... this
9833                     is only useful if buf1 hasn't overflowed first!
9834                 */
9835                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9836                 if(curscore >= MATE_SCORE) 
9837                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9838                 else if(curscore <= -MATE_SCORE) 
9839                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9840                 else
9841                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9842                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9843                          plylev,
9844                          (gameMode == TwoMachinesPlay ?
9845                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9846                          score_buf,
9847                          prefixHint ? lastHint : "",
9848                          prefixHint ? " " : "" );
9849
9850                 if( buf1[0] != NULLCHAR ) {
9851                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9852
9853                     if( strlen(pv) > max_len ) {
9854                         if( appData.debugMode) {
9855                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9856                         }
9857                         pv[max_len+1] = '\0';
9858                     }
9859
9860                     strcat( thinkOutput, pv);
9861                 }
9862
9863                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9864                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9865                     DisplayMove(currentMove - 1);
9866                 }
9867                 return;
9868
9869             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9870                 /* crafty (9.25+) says "(only move) <move>"
9871                  * if there is only 1 legal move
9872                  */
9873                 sscanf(p, "(only move) %s", buf1);
9874                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9875                 sprintf(programStats.movelist, "%s (only move)", buf1);
9876                 programStats.depth = 1;
9877                 programStats.nr_moves = 1;
9878                 programStats.moves_left = 1;
9879                 programStats.nodes = 1;
9880                 programStats.time = 1;
9881                 programStats.got_only_move = 1;
9882
9883                 /* Not really, but we also use this member to
9884                    mean "line isn't going to change" (Crafty
9885                    isn't searching, so stats won't change) */
9886                 programStats.line_is_book = 1;
9887
9888                 SendProgramStatsToFrontend( cps, &programStats );
9889
9890                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9891                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9892                     DisplayMove(currentMove - 1);
9893                 }
9894                 return;
9895             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9896                               &time, &nodes, &plylev, &mvleft,
9897                               &mvtot, mvname) >= 5) {
9898                 /* The stat01: line is from Crafty (9.29+) in response
9899                    to the "." command */
9900                 programStats.seen_stat = 1;
9901                 cps->maybeThinking = TRUE;
9902
9903                 if (programStats.got_only_move || !appData.periodicUpdates)
9904                   return;
9905
9906                 programStats.depth = plylev;
9907                 programStats.time = time;
9908                 programStats.nodes = nodes;
9909                 programStats.moves_left = mvleft;
9910                 programStats.nr_moves = mvtot;
9911                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9912                 programStats.ok_to_send = 1;
9913                 programStats.movelist[0] = '\0';
9914
9915                 SendProgramStatsToFrontend( cps, &programStats );
9916
9917                 return;
9918
9919             } else if (strncmp(message,"++",2) == 0) {
9920                 /* Crafty 9.29+ outputs this */
9921                 programStats.got_fail = 2;
9922                 return;
9923
9924             } else if (strncmp(message,"--",2) == 0) {
9925                 /* Crafty 9.29+ outputs this */
9926                 programStats.got_fail = 1;
9927                 return;
9928
9929             } else if (thinkOutput[0] != NULLCHAR &&
9930                        strncmp(message, "    ", 4) == 0) {
9931                 unsigned message_len;
9932
9933                 p = message;
9934                 while (*p && *p == ' ') p++;
9935
9936                 message_len = strlen( p );
9937
9938                 /* [AS] Avoid buffer overflow */
9939                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9940                     strcat(thinkOutput, " ");
9941                     strcat(thinkOutput, p);
9942                 }
9943
9944                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9945                     strcat(programStats.movelist, " ");
9946                     strcat(programStats.movelist, p);
9947                 }
9948
9949                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9950                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9951                     DisplayMove(currentMove - 1);
9952                 }
9953                 return;
9954             }
9955         }
9956         else {
9957             buf1[0] = NULLCHAR;
9958
9959             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9960                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9961             {
9962                 ChessProgramStats cpstats;
9963
9964                 if (plyext != ' ' && plyext != '\t') {
9965                     time *= 100;
9966                 }
9967
9968                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9969                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9970                     curscore = -curscore;
9971                 }
9972
9973                 cpstats.depth = plylev;
9974                 cpstats.nodes = nodes;
9975                 cpstats.time = time;
9976                 cpstats.score = curscore;
9977                 cpstats.got_only_move = 0;
9978                 cpstats.movelist[0] = '\0';
9979
9980                 if (buf1[0] != NULLCHAR) {
9981                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9982                 }
9983
9984                 cpstats.ok_to_send = 0;
9985                 cpstats.line_is_book = 0;
9986                 cpstats.nr_moves = 0;
9987                 cpstats.moves_left = 0;
9988
9989                 SendProgramStatsToFrontend( cps, &cpstats );
9990             }
9991         }
9992     }
9993 }
9994
9995
9996 /* Parse a game score from the character string "game", and
9997    record it as the history of the current game.  The game
9998    score is NOT assumed to start from the standard position.
9999    The display is not updated in any way.
10000    */
10001 void
10002 ParseGameHistory (char *game)
10003 {
10004     ChessMove moveType;
10005     int fromX, fromY, toX, toY, boardIndex;
10006     char promoChar;
10007     char *p, *q;
10008     char buf[MSG_SIZ];
10009
10010     if (appData.debugMode)
10011       fprintf(debugFP, "Parsing game history: %s\n", game);
10012
10013     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10014     gameInfo.site = StrSave(appData.icsHost);
10015     gameInfo.date = PGNDate();
10016     gameInfo.round = StrSave("-");
10017
10018     /* Parse out names of players */
10019     while (*game == ' ') game++;
10020     p = buf;
10021     while (*game != ' ') *p++ = *game++;
10022     *p = NULLCHAR;
10023     gameInfo.white = StrSave(buf);
10024     while (*game == ' ') game++;
10025     p = buf;
10026     while (*game != ' ' && *game != '\n') *p++ = *game++;
10027     *p = NULLCHAR;
10028     gameInfo.black = StrSave(buf);
10029
10030     /* Parse moves */
10031     boardIndex = blackPlaysFirst ? 1 : 0;
10032     yynewstr(game);
10033     for (;;) {
10034         yyboardindex = boardIndex;
10035         moveType = (ChessMove) Myylex();
10036         switch (moveType) {
10037           case IllegalMove:             /* maybe suicide chess, etc. */
10038   if (appData.debugMode) {
10039     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10040     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10041     setbuf(debugFP, NULL);
10042   }
10043           case WhitePromotion:
10044           case BlackPromotion:
10045           case WhiteNonPromotion:
10046           case BlackNonPromotion:
10047           case NormalMove:
10048           case FirstLeg:
10049           case WhiteCapturesEnPassant:
10050           case BlackCapturesEnPassant:
10051           case WhiteKingSideCastle:
10052           case WhiteQueenSideCastle:
10053           case BlackKingSideCastle:
10054           case BlackQueenSideCastle:
10055           case WhiteKingSideCastleWild:
10056           case WhiteQueenSideCastleWild:
10057           case BlackKingSideCastleWild:
10058           case BlackQueenSideCastleWild:
10059           /* PUSH Fabien */
10060           case WhiteHSideCastleFR:
10061           case WhiteASideCastleFR:
10062           case BlackHSideCastleFR:
10063           case BlackASideCastleFR:
10064           /* POP Fabien */
10065             fromX = currentMoveString[0] - AAA;
10066             fromY = currentMoveString[1] - ONE;
10067             toX = currentMoveString[2] - AAA;
10068             toY = currentMoveString[3] - ONE;
10069             promoChar = currentMoveString[4];
10070             break;
10071           case WhiteDrop:
10072           case BlackDrop:
10073             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10074             fromX = moveType == WhiteDrop ?
10075               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10076             (int) CharToPiece(ToLower(currentMoveString[0]));
10077             fromY = DROP_RANK;
10078             toX = currentMoveString[2] - AAA;
10079             toY = currentMoveString[3] - ONE;
10080             promoChar = NULLCHAR;
10081             break;
10082           case AmbiguousMove:
10083             /* bug? */
10084             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10085   if (appData.debugMode) {
10086     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10087     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10088     setbuf(debugFP, NULL);
10089   }
10090             DisplayError(buf, 0);
10091             return;
10092           case ImpossibleMove:
10093             /* bug? */
10094             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10095   if (appData.debugMode) {
10096     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10097     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10098     setbuf(debugFP, NULL);
10099   }
10100             DisplayError(buf, 0);
10101             return;
10102           case EndOfFile:
10103             if (boardIndex < backwardMostMove) {
10104                 /* Oops, gap.  How did that happen? */
10105                 DisplayError(_("Gap in move list"), 0);
10106                 return;
10107             }
10108             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10109             if (boardIndex > forwardMostMove) {
10110                 forwardMostMove = boardIndex;
10111             }
10112             return;
10113           case ElapsedTime:
10114             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10115                 strcat(parseList[boardIndex-1], " ");
10116                 strcat(parseList[boardIndex-1], yy_text);
10117             }
10118             continue;
10119           case Comment:
10120           case PGNTag:
10121           case NAG:
10122           default:
10123             /* ignore */
10124             continue;
10125           case WhiteWins:
10126           case BlackWins:
10127           case GameIsDrawn:
10128           case GameUnfinished:
10129             if (gameMode == IcsExamining) {
10130                 if (boardIndex < backwardMostMove) {
10131                     /* Oops, gap.  How did that happen? */
10132                     return;
10133                 }
10134                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10135                 return;
10136             }
10137             gameInfo.result = moveType;
10138             p = strchr(yy_text, '{');
10139             if (p == NULL) p = strchr(yy_text, '(');
10140             if (p == NULL) {
10141                 p = yy_text;
10142                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10143             } else {
10144                 q = strchr(p, *p == '{' ? '}' : ')');
10145                 if (q != NULL) *q = NULLCHAR;
10146                 p++;
10147             }
10148             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10149             gameInfo.resultDetails = StrSave(p);
10150             continue;
10151         }
10152         if (boardIndex >= forwardMostMove &&
10153             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10154             backwardMostMove = blackPlaysFirst ? 1 : 0;
10155             return;
10156         }
10157         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10158                                  fromY, fromX, toY, toX, promoChar,
10159                                  parseList[boardIndex]);
10160         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10161         /* currentMoveString is set as a side-effect of yylex */
10162         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10163         strcat(moveList[boardIndex], "\n");
10164         boardIndex++;
10165         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10166         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10167           case MT_NONE:
10168           case MT_STALEMATE:
10169           default:
10170             break;
10171           case MT_CHECK:
10172             if(!IS_SHOGI(gameInfo.variant))
10173                 strcat(parseList[boardIndex - 1], "+");
10174             break;
10175           case MT_CHECKMATE:
10176           case MT_STAINMATE:
10177             strcat(parseList[boardIndex - 1], "#");
10178             break;
10179         }
10180     }
10181 }
10182
10183
10184 /* Apply a move to the given board  */
10185 void
10186 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10187 {
10188   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10189   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10190
10191     /* [HGM] compute & store e.p. status and castling rights for new position */
10192     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10193
10194       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10195       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10196       board[EP_STATUS] = EP_NONE;
10197       board[EP_FILE] = board[EP_RANK] = 100;
10198
10199   if (fromY == DROP_RANK) {
10200         /* must be first */
10201         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10202             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10203             return;
10204         }
10205         piece = board[toY][toX] = (ChessSquare) fromX;
10206   } else {
10207 //      ChessSquare victim;
10208       int i;
10209
10210       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10211 //           victim = board[killY][killX],
10212            killed = board[killY][killX],
10213            board[killY][killX] = EmptySquare,
10214            board[EP_STATUS] = EP_CAPTURE;
10215            if( kill2X >= 0 && kill2Y >= 0)
10216              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10217       }
10218
10219       if( board[toY][toX] != EmptySquare ) {
10220            board[EP_STATUS] = EP_CAPTURE;
10221            if( (fromX != toX || fromY != toY) && // not igui!
10222                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10223                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10224                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10225            }
10226       }
10227
10228       pawn = board[fromY][fromX];
10229       if( pawn == WhiteLance || pawn == BlackLance ) {
10230            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10231                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10232                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10233            }
10234       }
10235       if( pawn == WhitePawn ) {
10236            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10237                board[EP_STATUS] = EP_PAWN_MOVE;
10238            if( toY-fromY>=2) {
10239                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10240                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10241                         gameInfo.variant != VariantBerolina || toX < fromX)
10242                       board[EP_STATUS] = toX | berolina;
10243                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10244                         gameInfo.variant != VariantBerolina || toX > fromX)
10245                       board[EP_STATUS] = toX;
10246            }
10247       } else
10248       if( pawn == BlackPawn ) {
10249            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10250                board[EP_STATUS] = EP_PAWN_MOVE;
10251            if( toY-fromY<= -2) {
10252                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10253                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10254                         gameInfo.variant != VariantBerolina || toX < fromX)
10255                       board[EP_STATUS] = toX | berolina;
10256                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10257                         gameInfo.variant != VariantBerolina || toX > fromX)
10258                       board[EP_STATUS] = toX;
10259            }
10260        }
10261
10262        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10263        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10264        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10265        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10266
10267        for(i=0; i<nrCastlingRights; i++) {
10268            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10269               board[CASTLING][i] == toX   && castlingRank[i] == toY
10270              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10271        }
10272
10273        if(gameInfo.variant == VariantSChess) { // update virginity
10274            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10275            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10276            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10277            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10278        }
10279
10280      if (fromX == toX && fromY == toY && killX < 0) return;
10281
10282      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10283      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10284      if(gameInfo.variant == VariantKnightmate)
10285          king += (int) WhiteUnicorn - (int) WhiteKing;
10286
10287     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10288        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10289         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10290         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10291         board[EP_STATUS] = EP_NONE; // capture was fake!
10292     } else
10293     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10294         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10295         board[toY][toX] = piece;
10296         board[EP_STATUS] = EP_NONE; // capture was fake!
10297     } else
10298     /* Code added by Tord: */
10299     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10300     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10301         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10302       board[EP_STATUS] = EP_NONE; // capture was fake!
10303       board[fromY][fromX] = EmptySquare;
10304       board[toY][toX] = EmptySquare;
10305       if((toX > fromX) != (piece == WhiteRook)) {
10306         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10307       } else {
10308         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10309       }
10310     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10311                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10312       board[EP_STATUS] = EP_NONE;
10313       board[fromY][fromX] = EmptySquare;
10314       board[toY][toX] = EmptySquare;
10315       if((toX > fromX) != (piece == BlackRook)) {
10316         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10317       } else {
10318         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10319       }
10320     /* End of code added by Tord */
10321
10322     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10323         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10324         board[toY][toX] = piece;
10325     } else if (board[fromY][fromX] == king
10326         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10327         && toY == fromY && toX > fromX+1) {
10328         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10329                                                                                              ; // castle with nearest piece
10330         board[fromY][toX-1] = board[fromY][rookX];
10331         board[fromY][rookX] = EmptySquare;
10332         board[fromY][fromX] = EmptySquare;
10333         board[toY][toX] = king;
10334     } else if (board[fromY][fromX] == king
10335         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10336                && toY == fromY && toX < fromX-1) {
10337         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10338                                                                                   ; // castle with nearest piece
10339         board[fromY][toX+1] = board[fromY][rookX];
10340         board[fromY][rookX] = EmptySquare;
10341         board[fromY][fromX] = EmptySquare;
10342         board[toY][toX] = king;
10343     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10344                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10345                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10346                ) {
10347         /* white pawn promotion */
10348         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10349         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10350             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10351         board[fromY][fromX] = EmptySquare;
10352     } else if ((fromY >= BOARD_HEIGHT>>1)
10353                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10354                && (toX != fromX)
10355                && gameInfo.variant != VariantXiangqi
10356                && gameInfo.variant != VariantBerolina
10357                && (pawn == WhitePawn)
10358                && (board[toY][toX] == EmptySquare)) {
10359         board[fromY][fromX] = EmptySquare;
10360         board[toY][toX] = piece;
10361         if(toY == epRank - 128 + 1)
10362             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10363         else
10364             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10365     } else if ((fromY == BOARD_HEIGHT-4)
10366                && (toX == fromX)
10367                && gameInfo.variant == VariantBerolina
10368                && (board[fromY][fromX] == WhitePawn)
10369                && (board[toY][toX] == EmptySquare)) {
10370         board[fromY][fromX] = EmptySquare;
10371         board[toY][toX] = WhitePawn;
10372         if(oldEP & EP_BEROLIN_A) {
10373                 captured = board[fromY][fromX-1];
10374                 board[fromY][fromX-1] = EmptySquare;
10375         }else{  captured = board[fromY][fromX+1];
10376                 board[fromY][fromX+1] = EmptySquare;
10377         }
10378     } else if (board[fromY][fromX] == king
10379         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10380                && toY == fromY && toX > fromX+1) {
10381         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10382                                                                                              ;
10383         board[fromY][toX-1] = board[fromY][rookX];
10384         board[fromY][rookX] = EmptySquare;
10385         board[fromY][fromX] = EmptySquare;
10386         board[toY][toX] = king;
10387     } else if (board[fromY][fromX] == king
10388         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10389                && toY == fromY && toX < fromX-1) {
10390         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10391                                                                                 ;
10392         board[fromY][toX+1] = board[fromY][rookX];
10393         board[fromY][rookX] = EmptySquare;
10394         board[fromY][fromX] = EmptySquare;
10395         board[toY][toX] = king;
10396     } else if (fromY == 7 && fromX == 3
10397                && board[fromY][fromX] == BlackKing
10398                && toY == 7 && toX == 5) {
10399         board[fromY][fromX] = EmptySquare;
10400         board[toY][toX] = BlackKing;
10401         board[fromY][7] = EmptySquare;
10402         board[toY][4] = BlackRook;
10403     } else if (fromY == 7 && fromX == 3
10404                && board[fromY][fromX] == BlackKing
10405                && toY == 7 && toX == 1) {
10406         board[fromY][fromX] = EmptySquare;
10407         board[toY][toX] = BlackKing;
10408         board[fromY][0] = EmptySquare;
10409         board[toY][2] = BlackRook;
10410     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10411                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10412                && toY < promoRank && promoChar
10413                ) {
10414         /* black pawn promotion */
10415         board[toY][toX] = CharToPiece(ToLower(promoChar));
10416         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10417             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10418         board[fromY][fromX] = EmptySquare;
10419     } else if ((fromY < BOARD_HEIGHT>>1)
10420                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10421                && (toX != fromX)
10422                && gameInfo.variant != VariantXiangqi
10423                && gameInfo.variant != VariantBerolina
10424                && (pawn == BlackPawn)
10425                && (board[toY][toX] == EmptySquare)) {
10426         board[fromY][fromX] = EmptySquare;
10427         board[toY][toX] = piece;
10428         if(toY == epRank - 128 - 1)
10429             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10430         else
10431             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10432     } else if ((fromY == 3)
10433                && (toX == fromX)
10434                && gameInfo.variant == VariantBerolina
10435                && (board[fromY][fromX] == BlackPawn)
10436                && (board[toY][toX] == EmptySquare)) {
10437         board[fromY][fromX] = EmptySquare;
10438         board[toY][toX] = BlackPawn;
10439         if(oldEP & EP_BEROLIN_A) {
10440                 captured = board[fromY][fromX-1];
10441                 board[fromY][fromX-1] = EmptySquare;
10442         }else{  captured = board[fromY][fromX+1];
10443                 board[fromY][fromX+1] = EmptySquare;
10444         }
10445     } else {
10446         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10447         board[fromY][fromX] = EmptySquare;
10448         board[toY][toX] = piece;
10449     }
10450   }
10451
10452     if (gameInfo.holdingsWidth != 0) {
10453
10454       /* !!A lot more code needs to be written to support holdings  */
10455       /* [HGM] OK, so I have written it. Holdings are stored in the */
10456       /* penultimate board files, so they are automaticlly stored   */
10457       /* in the game history.                                       */
10458       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10459                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10460         /* Delete from holdings, by decreasing count */
10461         /* and erasing image if necessary            */
10462         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10463         if(p < (int) BlackPawn) { /* white drop */
10464              p -= (int)WhitePawn;
10465                  p = PieceToNumber((ChessSquare)p);
10466              if(p >= gameInfo.holdingsSize) p = 0;
10467              if(--board[p][BOARD_WIDTH-2] <= 0)
10468                   board[p][BOARD_WIDTH-1] = EmptySquare;
10469              if((int)board[p][BOARD_WIDTH-2] < 0)
10470                         board[p][BOARD_WIDTH-2] = 0;
10471         } else {                  /* black drop */
10472              p -= (int)BlackPawn;
10473                  p = PieceToNumber((ChessSquare)p);
10474              if(p >= gameInfo.holdingsSize) p = 0;
10475              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10476                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10477              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10478                         board[BOARD_HEIGHT-1-p][1] = 0;
10479         }
10480       }
10481       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10482           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10483         /* [HGM] holdings: Add to holdings, if holdings exist */
10484         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10485                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10486                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10487         }
10488         p = (int) captured;
10489         if (p >= (int) BlackPawn) {
10490           p -= (int)BlackPawn;
10491           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10492                   /* Restore shogi-promoted piece to its original  first */
10493                   captured = (ChessSquare) (DEMOTED(captured));
10494                   p = DEMOTED(p);
10495           }
10496           p = PieceToNumber((ChessSquare)p);
10497           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10498           board[p][BOARD_WIDTH-2]++;
10499           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10500         } else {
10501           p -= (int)WhitePawn;
10502           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10503                   captured = (ChessSquare) (DEMOTED(captured));
10504                   p = DEMOTED(p);
10505           }
10506           p = PieceToNumber((ChessSquare)p);
10507           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10508           board[BOARD_HEIGHT-1-p][1]++;
10509           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10510         }
10511       }
10512     } else if (gameInfo.variant == VariantAtomic) {
10513       if (captured != EmptySquare) {
10514         int y, x;
10515         for (y = toY-1; y <= toY+1; y++) {
10516           for (x = toX-1; x <= toX+1; x++) {
10517             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10518                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10519               board[y][x] = EmptySquare;
10520             }
10521           }
10522         }
10523         board[toY][toX] = EmptySquare;
10524       }
10525     }
10526
10527     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10528         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10529     } else
10530     if(promoChar == '+') {
10531         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10532         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10533         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10534           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10535     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10536         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10537         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10538            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10539         board[toY][toX] = newPiece;
10540     }
10541     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10542                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10543         // [HGM] superchess: take promotion piece out of holdings
10544         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10545         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10546             if(!--board[k][BOARD_WIDTH-2])
10547                 board[k][BOARD_WIDTH-1] = EmptySquare;
10548         } else {
10549             if(!--board[BOARD_HEIGHT-1-k][1])
10550                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10551         }
10552     }
10553 }
10554
10555 /* Updates forwardMostMove */
10556 void
10557 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10558 {
10559     int x = toX, y = toY;
10560     char *s = parseList[forwardMostMove];
10561     ChessSquare p = boards[forwardMostMove][toY][toX];
10562 //    forwardMostMove++; // [HGM] bare: moved downstream
10563
10564     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10565     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10566     (void) CoordsToAlgebraic(boards[forwardMostMove],
10567                              PosFlags(forwardMostMove),
10568                              fromY, fromX, y, x, (killX < 0)*promoChar,
10569                              s);
10570     if(kill2X >= 0 && kill2Y >= 0)
10571         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10572     if(killX >= 0 && killY >= 0)
10573         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10574                                            toX + AAA, toY + ONE - '0', promoChar);
10575
10576     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10577         int timeLeft; static int lastLoadFlag=0; int king, piece;
10578         piece = boards[forwardMostMove][fromY][fromX];
10579         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10580         if(gameInfo.variant == VariantKnightmate)
10581             king += (int) WhiteUnicorn - (int) WhiteKing;
10582         if(forwardMostMove == 0) {
10583             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10584                 fprintf(serverMoves, "%s;", UserName());
10585             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10586                 fprintf(serverMoves, "%s;", second.tidy);
10587             fprintf(serverMoves, "%s;", first.tidy);
10588             if(gameMode == MachinePlaysWhite)
10589                 fprintf(serverMoves, "%s;", UserName());
10590             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10591                 fprintf(serverMoves, "%s;", second.tidy);
10592         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10593         lastLoadFlag = loadFlag;
10594         // print base move
10595         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10596         // print castling suffix
10597         if( toY == fromY && piece == king ) {
10598             if(toX-fromX > 1)
10599                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10600             if(fromX-toX >1)
10601                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10602         }
10603         // e.p. suffix
10604         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10605              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10606              boards[forwardMostMove][toY][toX] == EmptySquare
10607              && fromX != toX && fromY != toY)
10608                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10609         // promotion suffix
10610         if(promoChar != NULLCHAR) {
10611             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10612                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10613                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10614             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10615         }
10616         if(!loadFlag) {
10617                 char buf[MOVE_LEN*2], *p; int len;
10618             fprintf(serverMoves, "/%d/%d",
10619                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10620             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10621             else                      timeLeft = blackTimeRemaining/1000;
10622             fprintf(serverMoves, "/%d", timeLeft);
10623                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10624                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10625                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10626                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10627             fprintf(serverMoves, "/%s", buf);
10628         }
10629         fflush(serverMoves);
10630     }
10631
10632     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10633         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10634       return;
10635     }
10636     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10637     if (commentList[forwardMostMove+1] != NULL) {
10638         free(commentList[forwardMostMove+1]);
10639         commentList[forwardMostMove+1] = NULL;
10640     }
10641     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10642     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10643     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10644     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10645     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10646     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10647     adjustedClock = FALSE;
10648     gameInfo.result = GameUnfinished;
10649     if (gameInfo.resultDetails != NULL) {
10650         free(gameInfo.resultDetails);
10651         gameInfo.resultDetails = NULL;
10652     }
10653     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10654                               moveList[forwardMostMove - 1]);
10655     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10656       case MT_NONE:
10657       case MT_STALEMATE:
10658       default:
10659         break;
10660       case MT_CHECK:
10661         if(!IS_SHOGI(gameInfo.variant))
10662             strcat(parseList[forwardMostMove - 1], "+");
10663         break;
10664       case MT_CHECKMATE:
10665       case MT_STAINMATE:
10666         strcat(parseList[forwardMostMove - 1], "#");
10667         break;
10668     }
10669 }
10670
10671 /* Updates currentMove if not pausing */
10672 void
10673 ShowMove (int fromX, int fromY, int toX, int toY)
10674 {
10675     int instant = (gameMode == PlayFromGameFile) ?
10676         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10677     if(appData.noGUI) return;
10678     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10679         if (!instant) {
10680             if (forwardMostMove == currentMove + 1) {
10681                 AnimateMove(boards[forwardMostMove - 1],
10682                             fromX, fromY, toX, toY);
10683             }
10684         }
10685         currentMove = forwardMostMove;
10686     }
10687
10688     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10689
10690     if (instant) return;
10691
10692     DisplayMove(currentMove - 1);
10693     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10694             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10695                 SetHighlights(fromX, fromY, toX, toY);
10696             }
10697     }
10698     DrawPosition(FALSE, boards[currentMove]);
10699     DisplayBothClocks();
10700     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10701 }
10702
10703 void
10704 SendEgtPath (ChessProgramState *cps)
10705 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10706         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10707
10708         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10709
10710         while(*p) {
10711             char c, *q = name+1, *r, *s;
10712
10713             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10714             while(*p && *p != ',') *q++ = *p++;
10715             *q++ = ':'; *q = 0;
10716             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10717                 strcmp(name, ",nalimov:") == 0 ) {
10718                 // take nalimov path from the menu-changeable option first, if it is defined
10719               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10720                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10721             } else
10722             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10723                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10724                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10725                 s = r = StrStr(s, ":") + 1; // beginning of path info
10726                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10727                 c = *r; *r = 0;             // temporarily null-terminate path info
10728                     *--q = 0;               // strip of trailig ':' from name
10729                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10730                 *r = c;
10731                 SendToProgram(buf,cps);     // send egtbpath command for this format
10732             }
10733             if(*p == ',') p++; // read away comma to position for next format name
10734         }
10735 }
10736
10737 static int
10738 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10739 {
10740       int width = 8, height = 8, holdings = 0;             // most common sizes
10741       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10742       // correct the deviations default for each variant
10743       if( v == VariantXiangqi ) width = 9,  height = 10;
10744       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10745       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10746       if( v == VariantCapablanca || v == VariantCapaRandom ||
10747           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10748                                 width = 10;
10749       if( v == VariantCourier ) width = 12;
10750       if( v == VariantSuper )                            holdings = 8;
10751       if( v == VariantGreat )   width = 10,              holdings = 8;
10752       if( v == VariantSChess )                           holdings = 7;
10753       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10754       if( v == VariantChuChess) width = 10, height = 10;
10755       if( v == VariantChu )     width = 12, height = 12;
10756       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10757              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10758              holdingsSize >= 0 && holdingsSize != holdings;
10759 }
10760
10761 char variantError[MSG_SIZ];
10762
10763 char *
10764 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10765 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10766       char *p, *variant = VariantName(v);
10767       static char b[MSG_SIZ];
10768       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10769            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10770                                                holdingsSize, variant); // cook up sized variant name
10771            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10772            if(StrStr(list, b) == NULL) {
10773                // specific sized variant not known, check if general sizing allowed
10774                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10775                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10776                             boardWidth, boardHeight, holdingsSize, engine);
10777                    return NULL;
10778                }
10779                /* [HGM] here we really should compare with the maximum supported board size */
10780            }
10781       } else snprintf(b, MSG_SIZ,"%s", variant);
10782       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10783       p = StrStr(list, b);
10784       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10785       if(p == NULL) {
10786           // occurs not at all in list, or only as sub-string
10787           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10788           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10789               int l = strlen(variantError);
10790               char *q;
10791               while(p != list && p[-1] != ',') p--;
10792               q = strchr(p, ',');
10793               if(q) *q = NULLCHAR;
10794               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10795               if(q) *q= ',';
10796           }
10797           return NULL;
10798       }
10799       return b;
10800 }
10801
10802 void
10803 InitChessProgram (ChessProgramState *cps, int setup)
10804 /* setup needed to setup FRC opening position */
10805 {
10806     char buf[MSG_SIZ], *b;
10807     if (appData.noChessProgram) return;
10808     hintRequested = FALSE;
10809     bookRequested = FALSE;
10810
10811     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10812     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10813     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10814     if(cps->memSize) { /* [HGM] memory */
10815       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10816         SendToProgram(buf, cps);
10817     }
10818     SendEgtPath(cps); /* [HGM] EGT */
10819     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10820       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10821         SendToProgram(buf, cps);
10822     }
10823
10824     setboardSpoiledMachineBlack = FALSE;
10825     SendToProgram(cps->initString, cps);
10826     if (gameInfo.variant != VariantNormal &&
10827         gameInfo.variant != VariantLoadable
10828         /* [HGM] also send variant if board size non-standard */
10829         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10830
10831       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10832                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10833
10834       if (b == NULL) {
10835         VariantClass v;
10836         char c, *q = cps->variants, *p = strchr(q, ',');
10837         if(p) *p = NULLCHAR;
10838         v = StringToVariant(q);
10839         DisplayError(variantError, 0);
10840         if(v != VariantUnknown && cps == &first) {
10841             int w, h, s;
10842             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10843                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10844             ASSIGN(appData.variant, q);
10845             Reset(TRUE, FALSE);
10846         }
10847         if(p) *p = ',';
10848         return;
10849       }
10850
10851       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10852       SendToProgram(buf, cps);
10853     }
10854     currentlyInitializedVariant = gameInfo.variant;
10855
10856     /* [HGM] send opening position in FRC to first engine */
10857     if(setup) {
10858           SendToProgram("force\n", cps);
10859           SendBoard(cps, 0);
10860           /* engine is now in force mode! Set flag to wake it up after first move. */
10861           setboardSpoiledMachineBlack = 1;
10862     }
10863
10864     if (cps->sendICS) {
10865       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10866       SendToProgram(buf, cps);
10867     }
10868     cps->maybeThinking = FALSE;
10869     cps->offeredDraw = 0;
10870     if (!appData.icsActive) {
10871         SendTimeControl(cps, movesPerSession, timeControl,
10872                         timeIncrement, appData.searchDepth,
10873                         searchTime);
10874     }
10875     if (appData.showThinking
10876         // [HGM] thinking: four options require thinking output to be sent
10877         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10878                                 ) {
10879         SendToProgram("post\n", cps);
10880     }
10881     SendToProgram("hard\n", cps);
10882     if (!appData.ponderNextMove) {
10883         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10884            it without being sure what state we are in first.  "hard"
10885            is not a toggle, so that one is OK.
10886          */
10887         SendToProgram("easy\n", cps);
10888     }
10889     if (cps->usePing) {
10890       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10891       SendToProgram(buf, cps);
10892     }
10893     cps->initDone = TRUE;
10894     ClearEngineOutputPane(cps == &second);
10895 }
10896
10897
10898 void
10899 ResendOptions (ChessProgramState *cps)
10900 { // send the stored value of the options
10901   int i;
10902   char buf[MSG_SIZ];
10903   Option *opt = cps->option;
10904   for(i=0; i<cps->nrOptions; i++, opt++) {
10905       switch(opt->type) {
10906         case Spin:
10907         case Slider:
10908         case CheckBox:
10909             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10910           break;
10911         case ComboBox:
10912           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10913           break;
10914         default:
10915             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10916           break;
10917         case Button:
10918         case SaveButton:
10919           continue;
10920       }
10921       SendToProgram(buf, cps);
10922   }
10923 }
10924
10925 void
10926 StartChessProgram (ChessProgramState *cps)
10927 {
10928     char buf[MSG_SIZ];
10929     int err;
10930
10931     if (appData.noChessProgram) return;
10932     cps->initDone = FALSE;
10933
10934     if (strcmp(cps->host, "localhost") == 0) {
10935         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10936     } else if (*appData.remoteShell == NULLCHAR) {
10937         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10938     } else {
10939         if (*appData.remoteUser == NULLCHAR) {
10940           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10941                     cps->program);
10942         } else {
10943           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10944                     cps->host, appData.remoteUser, cps->program);
10945         }
10946         err = StartChildProcess(buf, "", &cps->pr);
10947     }
10948
10949     if (err != 0) {
10950       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10951         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10952         if(cps != &first) return;
10953         appData.noChessProgram = TRUE;
10954         ThawUI();
10955         SetNCPMode();
10956 //      DisplayFatalError(buf, err, 1);
10957 //      cps->pr = NoProc;
10958 //      cps->isr = NULL;
10959         return;
10960     }
10961
10962     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10963     if (cps->protocolVersion > 1) {
10964       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10965       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10966         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10967         cps->comboCnt = 0;  //                and values of combo boxes
10968       }
10969       SendToProgram(buf, cps);
10970       if(cps->reload) ResendOptions(cps);
10971     } else {
10972       SendToProgram("xboard\n", cps);
10973     }
10974 }
10975
10976 void
10977 TwoMachinesEventIfReady P((void))
10978 {
10979   static int curMess = 0;
10980   if (first.lastPing != first.lastPong) {
10981     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10982     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10983     return;
10984   }
10985   if (second.lastPing != second.lastPong) {
10986     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10987     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10988     return;
10989   }
10990   DisplayMessage("", ""); curMess = 0;
10991   TwoMachinesEvent();
10992 }
10993
10994 char *
10995 MakeName (char *template)
10996 {
10997     time_t clock;
10998     struct tm *tm;
10999     static char buf[MSG_SIZ];
11000     char *p = buf;
11001     int i;
11002
11003     clock = time((time_t *)NULL);
11004     tm = localtime(&clock);
11005
11006     while(*p++ = *template++) if(p[-1] == '%') {
11007         switch(*template++) {
11008           case 0:   *p = 0; return buf;
11009           case 'Y': i = tm->tm_year+1900; break;
11010           case 'y': i = tm->tm_year-100; break;
11011           case 'M': i = tm->tm_mon+1; break;
11012           case 'd': i = tm->tm_mday; break;
11013           case 'h': i = tm->tm_hour; break;
11014           case 'm': i = tm->tm_min; break;
11015           case 's': i = tm->tm_sec; break;
11016           default:  i = 0;
11017         }
11018         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11019     }
11020     return buf;
11021 }
11022
11023 int
11024 CountPlayers (char *p)
11025 {
11026     int n = 0;
11027     while(p = strchr(p, '\n')) p++, n++; // count participants
11028     return n;
11029 }
11030
11031 FILE *
11032 WriteTourneyFile (char *results, FILE *f)
11033 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11034     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11035     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11036         // create a file with tournament description
11037         fprintf(f, "-participants {%s}\n", appData.participants);
11038         fprintf(f, "-seedBase %d\n", appData.seedBase);
11039         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11040         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11041         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11042         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11043         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11044         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11045         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11046         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11047         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11048         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11049         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11050         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11051         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11052         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11053         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11054         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11055         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11056         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11057         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11058         fprintf(f, "-smpCores %d\n", appData.smpCores);
11059         if(searchTime > 0)
11060                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11061         else {
11062                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11063                 fprintf(f, "-tc %s\n", appData.timeControl);
11064                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11065         }
11066         fprintf(f, "-results \"%s\"\n", results);
11067     }
11068     return f;
11069 }
11070
11071 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11072
11073 void
11074 Substitute (char *participants, int expunge)
11075 {
11076     int i, changed, changes=0, nPlayers=0;
11077     char *p, *q, *r, buf[MSG_SIZ];
11078     if(participants == NULL) return;
11079     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11080     r = p = participants; q = appData.participants;
11081     while(*p && *p == *q) {
11082         if(*p == '\n') r = p+1, nPlayers++;
11083         p++; q++;
11084     }
11085     if(*p) { // difference
11086         while(*p && *p++ != '\n')
11087                                  ;
11088         while(*q && *q++ != '\n')
11089                                  ;
11090       changed = nPlayers;
11091         changes = 1 + (strcmp(p, q) != 0);
11092     }
11093     if(changes == 1) { // a single engine mnemonic was changed
11094         q = r; while(*q) nPlayers += (*q++ == '\n');
11095         p = buf; while(*r && (*p = *r++) != '\n') p++;
11096         *p = NULLCHAR;
11097         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11098         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11099         if(mnemonic[i]) { // The substitute is valid
11100             FILE *f;
11101             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11102                 flock(fileno(f), LOCK_EX);
11103                 ParseArgsFromFile(f);
11104                 fseek(f, 0, SEEK_SET);
11105                 FREE(appData.participants); appData.participants = participants;
11106                 if(expunge) { // erase results of replaced engine
11107                     int len = strlen(appData.results), w, b, dummy;
11108                     for(i=0; i<len; i++) {
11109                         Pairing(i, nPlayers, &w, &b, &dummy);
11110                         if((w == changed || b == changed) && appData.results[i] == '*') {
11111                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11112                             fclose(f);
11113                             return;
11114                         }
11115                     }
11116                     for(i=0; i<len; i++) {
11117                         Pairing(i, nPlayers, &w, &b, &dummy);
11118                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11119                     }
11120                 }
11121                 WriteTourneyFile(appData.results, f);
11122                 fclose(f); // release lock
11123                 return;
11124             }
11125         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11126     }
11127     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11128     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11129     free(participants);
11130     return;
11131 }
11132
11133 int
11134 CheckPlayers (char *participants)
11135 {
11136         int i;
11137         char buf[MSG_SIZ], *p;
11138         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11139         while(p = strchr(participants, '\n')) {
11140             *p = NULLCHAR;
11141             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11142             if(!mnemonic[i]) {
11143                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11144                 *p = '\n';
11145                 DisplayError(buf, 0);
11146                 return 1;
11147             }
11148             *p = '\n';
11149             participants = p + 1;
11150         }
11151         return 0;
11152 }
11153
11154 int
11155 CreateTourney (char *name)
11156 {
11157         FILE *f;
11158         if(matchMode && strcmp(name, appData.tourneyFile)) {
11159              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11160         }
11161         if(name[0] == NULLCHAR) {
11162             if(appData.participants[0])
11163                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11164             return 0;
11165         }
11166         f = fopen(name, "r");
11167         if(f) { // file exists
11168             ASSIGN(appData.tourneyFile, name);
11169             ParseArgsFromFile(f); // parse it
11170         } else {
11171             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11172             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11173                 DisplayError(_("Not enough participants"), 0);
11174                 return 0;
11175             }
11176             if(CheckPlayers(appData.participants)) return 0;
11177             ASSIGN(appData.tourneyFile, name);
11178             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11179             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11180         }
11181         fclose(f);
11182         appData.noChessProgram = FALSE;
11183         appData.clockMode = TRUE;
11184         SetGNUMode();
11185         return 1;
11186 }
11187
11188 int
11189 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11190 {
11191     char buf[MSG_SIZ], *p, *q;
11192     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11193     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11194     skip = !all && group[0]; // if group requested, we start in skip mode
11195     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11196         p = names; q = buf; header = 0;
11197         while(*p && *p != '\n') *q++ = *p++;
11198         *q = 0;
11199         if(*p == '\n') p++;
11200         if(buf[0] == '#') {
11201             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11202             depth++; // we must be entering a new group
11203             if(all) continue; // suppress printing group headers when complete list requested
11204             header = 1;
11205             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11206         }
11207         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11208         if(engineList[i]) free(engineList[i]);
11209         engineList[i] = strdup(buf);
11210         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11211         if(engineMnemonic[i]) free(engineMnemonic[i]);
11212         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11213             strcat(buf, " (");
11214             sscanf(q + 8, "%s", buf + strlen(buf));
11215             strcat(buf, ")");
11216         }
11217         engineMnemonic[i] = strdup(buf);
11218         i++;
11219     }
11220     engineList[i] = engineMnemonic[i] = NULL;
11221     return i;
11222 }
11223
11224 // following implemented as macro to avoid type limitations
11225 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11226
11227 void
11228 SwapEngines (int n)
11229 {   // swap settings for first engine and other engine (so far only some selected options)
11230     int h;
11231     char *p;
11232     if(n == 0) return;
11233     SWAP(directory, p)
11234     SWAP(chessProgram, p)
11235     SWAP(isUCI, h)
11236     SWAP(hasOwnBookUCI, h)
11237     SWAP(protocolVersion, h)
11238     SWAP(reuse, h)
11239     SWAP(scoreIsAbsolute, h)
11240     SWAP(timeOdds, h)
11241     SWAP(logo, p)
11242     SWAP(pgnName, p)
11243     SWAP(pvSAN, h)
11244     SWAP(engOptions, p)
11245     SWAP(engInitString, p)
11246     SWAP(computerString, p)
11247     SWAP(features, p)
11248     SWAP(fenOverride, p)
11249     SWAP(NPS, h)
11250     SWAP(accumulateTC, h)
11251     SWAP(drawDepth, h)
11252     SWAP(host, p)
11253     SWAP(pseudo, h)
11254 }
11255
11256 int
11257 GetEngineLine (char *s, int n)
11258 {
11259     int i;
11260     char buf[MSG_SIZ];
11261     extern char *icsNames;
11262     if(!s || !*s) return 0;
11263     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11264     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11265     if(!mnemonic[i]) return 0;
11266     if(n == 11) return 1; // just testing if there was a match
11267     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11268     if(n == 1) SwapEngines(n);
11269     ParseArgsFromString(buf);
11270     if(n == 1) SwapEngines(n);
11271     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11272         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11273         ParseArgsFromString(buf);
11274     }
11275     return 1;
11276 }
11277
11278 int
11279 SetPlayer (int player, char *p)
11280 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11281     int i;
11282     char buf[MSG_SIZ], *engineName;
11283     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11284     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11285     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11286     if(mnemonic[i]) {
11287         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11288         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11289         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11290         ParseArgsFromString(buf);
11291     } else { // no engine with this nickname is installed!
11292         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11293         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11294         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11295         ModeHighlight();
11296         DisplayError(buf, 0);
11297         return 0;
11298     }
11299     free(engineName);
11300     return i;
11301 }
11302
11303 char *recentEngines;
11304
11305 void
11306 RecentEngineEvent (int nr)
11307 {
11308     int n;
11309 //    SwapEngines(1); // bump first to second
11310 //    ReplaceEngine(&second, 1); // and load it there
11311     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11312     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11313     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11314         ReplaceEngine(&first, 0);
11315         FloatToFront(&appData.recentEngineList, command[n]);
11316     }
11317 }
11318
11319 int
11320 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11321 {   // determine players from game number
11322     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11323
11324     if(appData.tourneyType == 0) {
11325         roundsPerCycle = (nPlayers - 1) | 1;
11326         pairingsPerRound = nPlayers / 2;
11327     } else if(appData.tourneyType > 0) {
11328         roundsPerCycle = nPlayers - appData.tourneyType;
11329         pairingsPerRound = appData.tourneyType;
11330     }
11331     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11332     gamesPerCycle = gamesPerRound * roundsPerCycle;
11333     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11334     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11335     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11336     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11337     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11338     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11339
11340     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11341     if(appData.roundSync) *syncInterval = gamesPerRound;
11342
11343     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11344
11345     if(appData.tourneyType == 0) {
11346         if(curPairing == (nPlayers-1)/2 ) {
11347             *whitePlayer = curRound;
11348             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11349         } else {
11350             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11351             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11352             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11353             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11354         }
11355     } else if(appData.tourneyType > 1) {
11356         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11357         *whitePlayer = curRound + appData.tourneyType;
11358     } else if(appData.tourneyType > 0) {
11359         *whitePlayer = curPairing;
11360         *blackPlayer = curRound + appData.tourneyType;
11361     }
11362
11363     // take care of white/black alternation per round.
11364     // For cycles and games this is already taken care of by default, derived from matchGame!
11365     return curRound & 1;
11366 }
11367
11368 int
11369 NextTourneyGame (int nr, int *swapColors)
11370 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11371     char *p, *q;
11372     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11373     FILE *tf;
11374     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11375     tf = fopen(appData.tourneyFile, "r");
11376     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11377     ParseArgsFromFile(tf); fclose(tf);
11378     InitTimeControls(); // TC might be altered from tourney file
11379
11380     nPlayers = CountPlayers(appData.participants); // count participants
11381     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11382     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11383
11384     if(syncInterval) {
11385         p = q = appData.results;
11386         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11387         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11388             DisplayMessage(_("Waiting for other game(s)"),"");
11389             waitingForGame = TRUE;
11390             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11391             return 0;
11392         }
11393         waitingForGame = FALSE;
11394     }
11395
11396     if(appData.tourneyType < 0) {
11397         if(nr>=0 && !pairingReceived) {
11398             char buf[1<<16];
11399             if(pairing.pr == NoProc) {
11400                 if(!appData.pairingEngine[0]) {
11401                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11402                     return 0;
11403                 }
11404                 StartChessProgram(&pairing); // starts the pairing engine
11405             }
11406             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11407             SendToProgram(buf, &pairing);
11408             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11409             SendToProgram(buf, &pairing);
11410             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11411         }
11412         pairingReceived = 0;                              // ... so we continue here
11413         *swapColors = 0;
11414         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11415         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11416         matchGame = 1; roundNr = nr / syncInterval + 1;
11417     }
11418
11419     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11420
11421     // redefine engines, engine dir, etc.
11422     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11423     if(first.pr == NoProc) {
11424       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11425       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11426     }
11427     if(second.pr == NoProc) {
11428       SwapEngines(1);
11429       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11430       SwapEngines(1);         // and make that valid for second engine by swapping
11431       InitEngine(&second, 1);
11432     }
11433     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11434     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11435     return OK;
11436 }
11437
11438 void
11439 NextMatchGame ()
11440 {   // performs game initialization that does not invoke engines, and then tries to start the game
11441     int res, firstWhite, swapColors = 0;
11442     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11443     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
11444         char buf[MSG_SIZ];
11445         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11446         if(strcmp(buf, currentDebugFile)) { // name has changed
11447             FILE *f = fopen(buf, "w");
11448             if(f) { // if opening the new file failed, just keep using the old one
11449                 ASSIGN(currentDebugFile, buf);
11450                 fclose(debugFP);
11451                 debugFP = f;
11452             }
11453             if(appData.serverFileName) {
11454                 if(serverFP) fclose(serverFP);
11455                 serverFP = fopen(appData.serverFileName, "w");
11456                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11457                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11458             }
11459         }
11460     }
11461     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11462     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11463     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11464     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11465     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11466     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11467     Reset(FALSE, first.pr != NoProc);
11468     res = LoadGameOrPosition(matchGame); // setup game
11469     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11470     if(!res) return; // abort when bad game/pos file
11471     if(appData.epd) {// in EPD mode we make sure first engine is to move
11472         firstWhite = !(forwardMostMove & 1);
11473         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11474         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11475     }
11476     TwoMachinesEvent();
11477 }
11478
11479 void
11480 UserAdjudicationEvent (int result)
11481 {
11482     ChessMove gameResult = GameIsDrawn;
11483
11484     if( result > 0 ) {
11485         gameResult = WhiteWins;
11486     }
11487     else if( result < 0 ) {
11488         gameResult = BlackWins;
11489     }
11490
11491     if( gameMode == TwoMachinesPlay ) {
11492         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11493     }
11494 }
11495
11496
11497 // [HGM] save: calculate checksum of game to make games easily identifiable
11498 int
11499 StringCheckSum (char *s)
11500 {
11501         int i = 0;
11502         if(s==NULL) return 0;
11503         while(*s) i = i*259 + *s++;
11504         return i;
11505 }
11506
11507 int
11508 GameCheckSum ()
11509 {
11510         int i, sum=0;
11511         for(i=backwardMostMove; i<forwardMostMove; i++) {
11512                 sum += pvInfoList[i].depth;
11513                 sum += StringCheckSum(parseList[i]);
11514                 sum += StringCheckSum(commentList[i]);
11515                 sum *= 261;
11516         }
11517         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11518         return sum + StringCheckSum(commentList[i]);
11519 } // end of save patch
11520
11521 void
11522 GameEnds (ChessMove result, char *resultDetails, int whosays)
11523 {
11524     GameMode nextGameMode;
11525     int isIcsGame;
11526     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11527
11528     if(endingGame) return; /* [HGM] crash: forbid recursion */
11529     endingGame = 1;
11530     if(twoBoards) { // [HGM] dual: switch back to one board
11531         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11532         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11533     }
11534     if (appData.debugMode) {
11535       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11536               result, resultDetails ? resultDetails : "(null)", whosays);
11537     }
11538
11539     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11540
11541     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11542
11543     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11544         /* If we are playing on ICS, the server decides when the
11545            game is over, but the engine can offer to draw, claim
11546            a draw, or resign.
11547          */
11548 #if ZIPPY
11549         if (appData.zippyPlay && first.initDone) {
11550             if (result == GameIsDrawn) {
11551                 /* In case draw still needs to be claimed */
11552                 SendToICS(ics_prefix);
11553                 SendToICS("draw\n");
11554             } else if (StrCaseStr(resultDetails, "resign")) {
11555                 SendToICS(ics_prefix);
11556                 SendToICS("resign\n");
11557             }
11558         }
11559 #endif
11560         endingGame = 0; /* [HGM] crash */
11561         return;
11562     }
11563
11564     /* If we're loading the game from a file, stop */
11565     if (whosays == GE_FILE) {
11566       (void) StopLoadGameTimer();
11567       gameFileFP = NULL;
11568     }
11569
11570     /* Cancel draw offers */
11571     first.offeredDraw = second.offeredDraw = 0;
11572
11573     /* If this is an ICS game, only ICS can really say it's done;
11574        if not, anyone can. */
11575     isIcsGame = (gameMode == IcsPlayingWhite ||
11576                  gameMode == IcsPlayingBlack ||
11577                  gameMode == IcsObserving    ||
11578                  gameMode == IcsExamining);
11579
11580     if (!isIcsGame || whosays == GE_ICS) {
11581         /* OK -- not an ICS game, or ICS said it was done */
11582         StopClocks();
11583         if (!isIcsGame && !appData.noChessProgram)
11584           SetUserThinkingEnables();
11585
11586         /* [HGM] if a machine claims the game end we verify this claim */
11587         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11588             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11589                 char claimer;
11590                 ChessMove trueResult = (ChessMove) -1;
11591
11592                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11593                                             first.twoMachinesColor[0] :
11594                                             second.twoMachinesColor[0] ;
11595
11596                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11597                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11598                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11599                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11600                 } else
11601                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11602                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11603                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11604                 } else
11605                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11606                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11607                 }
11608
11609                 // now verify win claims, but not in drop games, as we don't understand those yet
11610                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11611                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11612                     (result == WhiteWins && claimer == 'w' ||
11613                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11614                       if (appData.debugMode) {
11615                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11616                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11617                       }
11618                       if(result != trueResult) {
11619                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11620                               result = claimer == 'w' ? BlackWins : WhiteWins;
11621                               resultDetails = buf;
11622                       }
11623                 } else
11624                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11625                     && (forwardMostMove <= backwardMostMove ||
11626                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11627                         (claimer=='b')==(forwardMostMove&1))
11628                                                                                   ) {
11629                       /* [HGM] verify: draws that were not flagged are false claims */
11630                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11631                       result = claimer == 'w' ? BlackWins : WhiteWins;
11632                       resultDetails = buf;
11633                 }
11634                 /* (Claiming a loss is accepted no questions asked!) */
11635             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11636                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11637                 result = GameUnfinished;
11638                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11639             }
11640             /* [HGM] bare: don't allow bare King to win */
11641             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11642                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11643                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11644                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11645                && result != GameIsDrawn)
11646             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11647                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11648                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11649                         if(p >= 0 && p <= (int)WhiteKing) k++;
11650                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11651                 }
11652                 if (appData.debugMode) {
11653                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11654                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11655                 }
11656                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11657                         result = GameIsDrawn;
11658                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11659                         resultDetails = buf;
11660                 }
11661             }
11662         }
11663
11664
11665         if(serverMoves != NULL && !loadFlag) { char c = '=';
11666             if(result==WhiteWins) c = '+';
11667             if(result==BlackWins) c = '-';
11668             if(resultDetails != NULL)
11669                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11670         }
11671         if (resultDetails != NULL) {
11672             gameInfo.result = result;
11673             gameInfo.resultDetails = StrSave(resultDetails);
11674
11675             /* display last move only if game was not loaded from file */
11676             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11677                 DisplayMove(currentMove - 1);
11678
11679             if (forwardMostMove != 0) {
11680                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11681                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11682                                                                 ) {
11683                     if (*appData.saveGameFile != NULLCHAR) {
11684                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11685                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11686                         else
11687                         SaveGameToFile(appData.saveGameFile, TRUE);
11688                     } else if (appData.autoSaveGames) {
11689                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11690                     }
11691                     if (*appData.savePositionFile != NULLCHAR) {
11692                         SavePositionToFile(appData.savePositionFile);
11693                     }
11694                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11695                 }
11696             }
11697
11698             /* Tell program how game ended in case it is learning */
11699             /* [HGM] Moved this to after saving the PGN, just in case */
11700             /* engine died and we got here through time loss. In that */
11701             /* case we will get a fatal error writing the pipe, which */
11702             /* would otherwise lose us the PGN.                       */
11703             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11704             /* output during GameEnds should never be fatal anymore   */
11705             if (gameMode == MachinePlaysWhite ||
11706                 gameMode == MachinePlaysBlack ||
11707                 gameMode == TwoMachinesPlay ||
11708                 gameMode == IcsPlayingWhite ||
11709                 gameMode == IcsPlayingBlack ||
11710                 gameMode == BeginningOfGame) {
11711                 char buf[MSG_SIZ];
11712                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11713                         resultDetails);
11714                 if (first.pr != NoProc) {
11715                     SendToProgram(buf, &first);
11716                 }
11717                 if (second.pr != NoProc &&
11718                     gameMode == TwoMachinesPlay) {
11719                     SendToProgram(buf, &second);
11720                 }
11721             }
11722         }
11723
11724         if (appData.icsActive) {
11725             if (appData.quietPlay &&
11726                 (gameMode == IcsPlayingWhite ||
11727                  gameMode == IcsPlayingBlack)) {
11728                 SendToICS(ics_prefix);
11729                 SendToICS("set shout 1\n");
11730             }
11731             nextGameMode = IcsIdle;
11732             ics_user_moved = FALSE;
11733             /* clean up premove.  It's ugly when the game has ended and the
11734              * premove highlights are still on the board.
11735              */
11736             if (gotPremove) {
11737               gotPremove = FALSE;
11738               ClearPremoveHighlights();
11739               DrawPosition(FALSE, boards[currentMove]);
11740             }
11741             if (whosays == GE_ICS) {
11742                 switch (result) {
11743                 case WhiteWins:
11744                     if (gameMode == IcsPlayingWhite)
11745                         PlayIcsWinSound();
11746                     else if(gameMode == IcsPlayingBlack)
11747                         PlayIcsLossSound();
11748                     break;
11749                 case BlackWins:
11750                     if (gameMode == IcsPlayingBlack)
11751                         PlayIcsWinSound();
11752                     else if(gameMode == IcsPlayingWhite)
11753                         PlayIcsLossSound();
11754                     break;
11755                 case GameIsDrawn:
11756                     PlayIcsDrawSound();
11757                     break;
11758                 default:
11759                     PlayIcsUnfinishedSound();
11760                 }
11761             }
11762             if(appData.quitNext) { ExitEvent(0); return; }
11763         } else if (gameMode == EditGame ||
11764                    gameMode == PlayFromGameFile ||
11765                    gameMode == AnalyzeMode ||
11766                    gameMode == AnalyzeFile) {
11767             nextGameMode = gameMode;
11768         } else {
11769             nextGameMode = EndOfGame;
11770         }
11771         pausing = FALSE;
11772         ModeHighlight();
11773     } else {
11774         nextGameMode = gameMode;
11775     }
11776
11777     if (appData.noChessProgram) {
11778         gameMode = nextGameMode;
11779         ModeHighlight();
11780         endingGame = 0; /* [HGM] crash */
11781         return;
11782     }
11783
11784     if (first.reuse) {
11785         /* Put first chess program into idle state */
11786         if (first.pr != NoProc &&
11787             (gameMode == MachinePlaysWhite ||
11788              gameMode == MachinePlaysBlack ||
11789              gameMode == TwoMachinesPlay ||
11790              gameMode == IcsPlayingWhite ||
11791              gameMode == IcsPlayingBlack ||
11792              gameMode == BeginningOfGame)) {
11793             SendToProgram("force\n", &first);
11794             if (first.usePing) {
11795               char buf[MSG_SIZ];
11796               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11797               SendToProgram(buf, &first);
11798             }
11799         }
11800     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11801         /* Kill off first chess program */
11802         if (first.isr != NULL)
11803           RemoveInputSource(first.isr);
11804         first.isr = NULL;
11805
11806         if (first.pr != NoProc) {
11807             ExitAnalyzeMode();
11808             DoSleep( appData.delayBeforeQuit );
11809             SendToProgram("quit\n", &first);
11810             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11811             first.reload = TRUE;
11812         }
11813         first.pr = NoProc;
11814     }
11815     if (second.reuse) {
11816         /* Put second chess program into idle state */
11817         if (second.pr != NoProc &&
11818             gameMode == TwoMachinesPlay) {
11819             SendToProgram("force\n", &second);
11820             if (second.usePing) {
11821               char buf[MSG_SIZ];
11822               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11823               SendToProgram(buf, &second);
11824             }
11825         }
11826     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11827         /* Kill off second chess program */
11828         if (second.isr != NULL)
11829           RemoveInputSource(second.isr);
11830         second.isr = NULL;
11831
11832         if (second.pr != NoProc) {
11833             DoSleep( appData.delayBeforeQuit );
11834             SendToProgram("quit\n", &second);
11835             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11836             second.reload = TRUE;
11837         }
11838         second.pr = NoProc;
11839     }
11840
11841     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11842         char resChar = '=';
11843         switch (result) {
11844         case WhiteWins:
11845           resChar = '+';
11846           if (first.twoMachinesColor[0] == 'w') {
11847             first.matchWins++;
11848           } else {
11849             second.matchWins++;
11850           }
11851           break;
11852         case BlackWins:
11853           resChar = '-';
11854           if (first.twoMachinesColor[0] == 'b') {
11855             first.matchWins++;
11856           } else {
11857             second.matchWins++;
11858           }
11859           break;
11860         case GameUnfinished:
11861           resChar = ' ';
11862         default:
11863           break;
11864         }
11865
11866         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11867         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11868             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11869             ReserveGame(nextGame, resChar); // sets nextGame
11870             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11871             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11872         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11873
11874         if (nextGame <= appData.matchGames && !abortMatch) {
11875             gameMode = nextGameMode;
11876             matchGame = nextGame; // this will be overruled in tourney mode!
11877             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11878             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11879             endingGame = 0; /* [HGM] crash */
11880             return;
11881         } else {
11882             gameMode = nextGameMode;
11883             if(appData.epd) {
11884                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11885                 OutputKibitz(2, buf);
11886                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11887                 OutputKibitz(2, buf);
11888                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11889                 if(second.matchWins) OutputKibitz(2, buf);
11890                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11891                 OutputKibitz(2, buf);
11892             }
11893             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11894                      first.tidy, second.tidy,
11895                      first.matchWins, second.matchWins,
11896                      appData.matchGames - (first.matchWins + second.matchWins));
11897             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11898             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11899             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11900             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11901                 first.twoMachinesColor = "black\n";
11902                 second.twoMachinesColor = "white\n";
11903             } else {
11904                 first.twoMachinesColor = "white\n";
11905                 second.twoMachinesColor = "black\n";
11906             }
11907         }
11908     }
11909     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11910         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11911       ExitAnalyzeMode();
11912     gameMode = nextGameMode;
11913     ModeHighlight();
11914     endingGame = 0;  /* [HGM] crash */
11915     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11916         if(matchMode == TRUE) { // match through command line: exit with or without popup
11917             if(ranking) {
11918                 ToNrEvent(forwardMostMove);
11919                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11920                 else ExitEvent(0);
11921             } else DisplayFatalError(buf, 0, 0);
11922         } else { // match through menu; just stop, with or without popup
11923             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11924             ModeHighlight();
11925             if(ranking){
11926                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11927             } else DisplayNote(buf);
11928       }
11929       if(ranking) free(ranking);
11930     }
11931 }
11932
11933 /* Assumes program was just initialized (initString sent).
11934    Leaves program in force mode. */
11935 void
11936 FeedMovesToProgram (ChessProgramState *cps, int upto)
11937 {
11938     int i;
11939
11940     if (appData.debugMode)
11941       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11942               startedFromSetupPosition ? "position and " : "",
11943               backwardMostMove, upto, cps->which);
11944     if(currentlyInitializedVariant != gameInfo.variant) {
11945       char buf[MSG_SIZ];
11946         // [HGM] variantswitch: make engine aware of new variant
11947         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11948                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11949                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11950         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11951         SendToProgram(buf, cps);
11952         currentlyInitializedVariant = gameInfo.variant;
11953     }
11954     SendToProgram("force\n", cps);
11955     if (startedFromSetupPosition) {
11956         SendBoard(cps, backwardMostMove);
11957     if (appData.debugMode) {
11958         fprintf(debugFP, "feedMoves\n");
11959     }
11960     }
11961     for (i = backwardMostMove; i < upto; i++) {
11962         SendMoveToProgram(i, cps);
11963     }
11964 }
11965
11966
11967 int
11968 ResurrectChessProgram ()
11969 {
11970      /* The chess program may have exited.
11971         If so, restart it and feed it all the moves made so far. */
11972     static int doInit = 0;
11973
11974     if (appData.noChessProgram) return 1;
11975
11976     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11977         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11978         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11979         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11980     } else {
11981         if (first.pr != NoProc) return 1;
11982         StartChessProgram(&first);
11983     }
11984     InitChessProgram(&first, FALSE);
11985     FeedMovesToProgram(&first, currentMove);
11986
11987     if (!first.sendTime) {
11988         /* can't tell gnuchess what its clock should read,
11989            so we bow to its notion. */
11990         ResetClocks();
11991         timeRemaining[0][currentMove] = whiteTimeRemaining;
11992         timeRemaining[1][currentMove] = blackTimeRemaining;
11993     }
11994
11995     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11996                 appData.icsEngineAnalyze) && first.analysisSupport) {
11997       SendToProgram("analyze\n", &first);
11998       first.analyzing = TRUE;
11999     }
12000     return 1;
12001 }
12002
12003 /*
12004  * Button procedures
12005  */
12006 void
12007 Reset (int redraw, int init)
12008 {
12009     int i;
12010
12011     if (appData.debugMode) {
12012         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12013                 redraw, init, gameMode);
12014     }
12015     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12016     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12017     CleanupTail(); // [HGM] vari: delete any stored variations
12018     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12019     pausing = pauseExamInvalid = FALSE;
12020     startedFromSetupPosition = blackPlaysFirst = FALSE;
12021     firstMove = TRUE;
12022     whiteFlag = blackFlag = FALSE;
12023     userOfferedDraw = FALSE;
12024     hintRequested = bookRequested = FALSE;
12025     first.maybeThinking = FALSE;
12026     second.maybeThinking = FALSE;
12027     first.bookSuspend = FALSE; // [HGM] book
12028     second.bookSuspend = FALSE;
12029     thinkOutput[0] = NULLCHAR;
12030     lastHint[0] = NULLCHAR;
12031     ClearGameInfo(&gameInfo);
12032     gameInfo.variant = StringToVariant(appData.variant);
12033     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12034         gameInfo.variant = VariantUnknown;
12035         strncpy(engineVariant, appData.variant, MSG_SIZ);
12036     }
12037     ics_user_moved = ics_clock_paused = FALSE;
12038     ics_getting_history = H_FALSE;
12039     ics_gamenum = -1;
12040     white_holding[0] = black_holding[0] = NULLCHAR;
12041     ClearProgramStats();
12042     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12043
12044     ResetFrontEnd();
12045     ClearHighlights();
12046     flipView = appData.flipView;
12047     ClearPremoveHighlights();
12048     gotPremove = FALSE;
12049     alarmSounded = FALSE;
12050     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12051
12052     GameEnds(EndOfFile, NULL, GE_PLAYER);
12053     if(appData.serverMovesName != NULL) {
12054         /* [HGM] prepare to make moves file for broadcasting */
12055         clock_t t = clock();
12056         if(serverMoves != NULL) fclose(serverMoves);
12057         serverMoves = fopen(appData.serverMovesName, "r");
12058         if(serverMoves != NULL) {
12059             fclose(serverMoves);
12060             /* delay 15 sec before overwriting, so all clients can see end */
12061             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12062         }
12063         serverMoves = fopen(appData.serverMovesName, "w");
12064     }
12065
12066     ExitAnalyzeMode();
12067     gameMode = BeginningOfGame;
12068     ModeHighlight();
12069     if(appData.icsActive) gameInfo.variant = VariantNormal;
12070     currentMove = forwardMostMove = backwardMostMove = 0;
12071     MarkTargetSquares(1);
12072     InitPosition(redraw);
12073     for (i = 0; i < MAX_MOVES; i++) {
12074         if (commentList[i] != NULL) {
12075             free(commentList[i]);
12076             commentList[i] = NULL;
12077         }
12078     }
12079     ResetClocks();
12080     timeRemaining[0][0] = whiteTimeRemaining;
12081     timeRemaining[1][0] = blackTimeRemaining;
12082
12083     if (first.pr == NoProc) {
12084         StartChessProgram(&first);
12085     }
12086     if (init) {
12087             InitChessProgram(&first, startedFromSetupPosition);
12088     }
12089     DisplayTitle("");
12090     DisplayMessage("", "");
12091     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12092     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12093     ClearMap();        // [HGM] exclude: invalidate map
12094 }
12095
12096 void
12097 AutoPlayGameLoop ()
12098 {
12099     for (;;) {
12100         if (!AutoPlayOneMove())
12101           return;
12102         if (matchMode || appData.timeDelay == 0)
12103           continue;
12104         if (appData.timeDelay < 0)
12105           return;
12106         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12107         break;
12108     }
12109 }
12110
12111 void
12112 AnalyzeNextGame()
12113 {
12114     ReloadGame(1); // next game
12115 }
12116
12117 int
12118 AutoPlayOneMove ()
12119 {
12120     int fromX, fromY, toX, toY;
12121
12122     if (appData.debugMode) {
12123       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12124     }
12125
12126     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12127       return FALSE;
12128
12129     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12130       pvInfoList[currentMove].depth = programStats.depth;
12131       pvInfoList[currentMove].score = programStats.score;
12132       pvInfoList[currentMove].time  = 0;
12133       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12134       else { // append analysis of final position as comment
12135         char buf[MSG_SIZ];
12136         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12137         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12138       }
12139       programStats.depth = 0;
12140     }
12141
12142     if (currentMove >= forwardMostMove) {
12143       if(gameMode == AnalyzeFile) {
12144           if(appData.loadGameIndex == -1) {
12145             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12146           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12147           } else {
12148           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12149         }
12150       }
12151 //      gameMode = EndOfGame;
12152 //      ModeHighlight();
12153
12154       /* [AS] Clear current move marker at the end of a game */
12155       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12156
12157       return FALSE;
12158     }
12159
12160     toX = moveList[currentMove][2] - AAA;
12161     toY = moveList[currentMove][3] - ONE;
12162
12163     if (moveList[currentMove][1] == '@') {
12164         if (appData.highlightLastMove) {
12165             SetHighlights(-1, -1, toX, toY);
12166         }
12167     } else {
12168         fromX = moveList[currentMove][0] - AAA;
12169         fromY = moveList[currentMove][1] - ONE;
12170
12171         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12172
12173         if(moveList[currentMove][4] == ';') { // multi-leg
12174             killX = moveList[currentMove][5] - AAA;
12175             killY = moveList[currentMove][6] - ONE;
12176         }
12177         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12178         killX = killY = -1;
12179
12180         if (appData.highlightLastMove) {
12181             SetHighlights(fromX, fromY, toX, toY);
12182         }
12183     }
12184     DisplayMove(currentMove);
12185     SendMoveToProgram(currentMove++, &first);
12186     DisplayBothClocks();
12187     DrawPosition(FALSE, boards[currentMove]);
12188     // [HGM] PV info: always display, routine tests if empty
12189     DisplayComment(currentMove - 1, commentList[currentMove]);
12190     return TRUE;
12191 }
12192
12193
12194 int
12195 LoadGameOneMove (ChessMove readAhead)
12196 {
12197     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12198     char promoChar = NULLCHAR;
12199     ChessMove moveType;
12200     char move[MSG_SIZ];
12201     char *p, *q;
12202
12203     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12204         gameMode != AnalyzeMode && gameMode != Training) {
12205         gameFileFP = NULL;
12206         return FALSE;
12207     }
12208
12209     yyboardindex = forwardMostMove;
12210     if (readAhead != EndOfFile) {
12211       moveType = readAhead;
12212     } else {
12213       if (gameFileFP == NULL)
12214           return FALSE;
12215       moveType = (ChessMove) Myylex();
12216     }
12217
12218     done = FALSE;
12219     switch (moveType) {
12220       case Comment:
12221         if (appData.debugMode)
12222           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12223         p = yy_text;
12224
12225         /* append the comment but don't display it */
12226         AppendComment(currentMove, p, FALSE);
12227         return TRUE;
12228
12229       case WhiteCapturesEnPassant:
12230       case BlackCapturesEnPassant:
12231       case WhitePromotion:
12232       case BlackPromotion:
12233       case WhiteNonPromotion:
12234       case BlackNonPromotion:
12235       case NormalMove:
12236       case FirstLeg:
12237       case WhiteKingSideCastle:
12238       case WhiteQueenSideCastle:
12239       case BlackKingSideCastle:
12240       case BlackQueenSideCastle:
12241       case WhiteKingSideCastleWild:
12242       case WhiteQueenSideCastleWild:
12243       case BlackKingSideCastleWild:
12244       case BlackQueenSideCastleWild:
12245       /* PUSH Fabien */
12246       case WhiteHSideCastleFR:
12247       case WhiteASideCastleFR:
12248       case BlackHSideCastleFR:
12249       case BlackASideCastleFR:
12250       /* POP Fabien */
12251         if (appData.debugMode)
12252           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12253         fromX = currentMoveString[0] - AAA;
12254         fromY = currentMoveString[1] - ONE;
12255         toX = currentMoveString[2] - AAA;
12256         toY = currentMoveString[3] - ONE;
12257         promoChar = currentMoveString[4];
12258         if(promoChar == ';') promoChar = currentMoveString[7];
12259         break;
12260
12261       case WhiteDrop:
12262       case BlackDrop:
12263         if (appData.debugMode)
12264           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12265         fromX = moveType == WhiteDrop ?
12266           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12267         (int) CharToPiece(ToLower(currentMoveString[0]));
12268         fromY = DROP_RANK;
12269         toX = currentMoveString[2] - AAA;
12270         toY = currentMoveString[3] - ONE;
12271         break;
12272
12273       case WhiteWins:
12274       case BlackWins:
12275       case GameIsDrawn:
12276       case GameUnfinished:
12277         if (appData.debugMode)
12278           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12279         p = strchr(yy_text, '{');
12280         if (p == NULL) p = strchr(yy_text, '(');
12281         if (p == NULL) {
12282             p = yy_text;
12283             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12284         } else {
12285             q = strchr(p, *p == '{' ? '}' : ')');
12286             if (q != NULL) *q = NULLCHAR;
12287             p++;
12288         }
12289         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12290         GameEnds(moveType, p, GE_FILE);
12291         done = TRUE;
12292         if (cmailMsgLoaded) {
12293             ClearHighlights();
12294             flipView = WhiteOnMove(currentMove);
12295             if (moveType == GameUnfinished) flipView = !flipView;
12296             if (appData.debugMode)
12297               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12298         }
12299         break;
12300
12301       case EndOfFile:
12302         if (appData.debugMode)
12303           fprintf(debugFP, "Parser hit end of file\n");
12304         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12305           case MT_NONE:
12306           case MT_CHECK:
12307             break;
12308           case MT_CHECKMATE:
12309           case MT_STAINMATE:
12310             if (WhiteOnMove(currentMove)) {
12311                 GameEnds(BlackWins, "Black mates", GE_FILE);
12312             } else {
12313                 GameEnds(WhiteWins, "White mates", GE_FILE);
12314             }
12315             break;
12316           case MT_STALEMATE:
12317             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12318             break;
12319         }
12320         done = TRUE;
12321         break;
12322
12323       case MoveNumberOne:
12324         if (lastLoadGameStart == GNUChessGame) {
12325             /* GNUChessGames have numbers, but they aren't move numbers */
12326             if (appData.debugMode)
12327               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12328                       yy_text, (int) moveType);
12329             return LoadGameOneMove(EndOfFile); /* tail recursion */
12330         }
12331         /* else fall thru */
12332
12333       case XBoardGame:
12334       case GNUChessGame:
12335       case PGNTag:
12336         /* Reached start of next game in file */
12337         if (appData.debugMode)
12338           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12339         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12340           case MT_NONE:
12341           case MT_CHECK:
12342             break;
12343           case MT_CHECKMATE:
12344           case MT_STAINMATE:
12345             if (WhiteOnMove(currentMove)) {
12346                 GameEnds(BlackWins, "Black mates", GE_FILE);
12347             } else {
12348                 GameEnds(WhiteWins, "White mates", GE_FILE);
12349             }
12350             break;
12351           case MT_STALEMATE:
12352             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12353             break;
12354         }
12355         done = TRUE;
12356         break;
12357
12358       case PositionDiagram:     /* should not happen; ignore */
12359       case ElapsedTime:         /* ignore */
12360       case NAG:                 /* ignore */
12361         if (appData.debugMode)
12362           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12363                   yy_text, (int) moveType);
12364         return LoadGameOneMove(EndOfFile); /* tail recursion */
12365
12366       case IllegalMove:
12367         if (appData.testLegality) {
12368             if (appData.debugMode)
12369               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12370             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12371                     (forwardMostMove / 2) + 1,
12372                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12373             DisplayError(move, 0);
12374             done = TRUE;
12375         } else {
12376             if (appData.debugMode)
12377               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12378                       yy_text, currentMoveString);
12379             if(currentMoveString[1] == '@') {
12380                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12381                 fromY = DROP_RANK;
12382             } else {
12383                 fromX = currentMoveString[0] - AAA;
12384                 fromY = currentMoveString[1] - ONE;
12385             }
12386             toX = currentMoveString[2] - AAA;
12387             toY = currentMoveString[3] - ONE;
12388             promoChar = currentMoveString[4];
12389         }
12390         break;
12391
12392       case AmbiguousMove:
12393         if (appData.debugMode)
12394           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12395         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12396                 (forwardMostMove / 2) + 1,
12397                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12398         DisplayError(move, 0);
12399         done = TRUE;
12400         break;
12401
12402       default:
12403       case ImpossibleMove:
12404         if (appData.debugMode)
12405           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12406         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12407                 (forwardMostMove / 2) + 1,
12408                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12409         DisplayError(move, 0);
12410         done = TRUE;
12411         break;
12412     }
12413
12414     if (done) {
12415         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12416             DrawPosition(FALSE, boards[currentMove]);
12417             DisplayBothClocks();
12418             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12419               DisplayComment(currentMove - 1, commentList[currentMove]);
12420         }
12421         (void) StopLoadGameTimer();
12422         gameFileFP = NULL;
12423         cmailOldMove = forwardMostMove;
12424         return FALSE;
12425     } else {
12426         /* currentMoveString is set as a side-effect of yylex */
12427
12428         thinkOutput[0] = NULLCHAR;
12429         MakeMove(fromX, fromY, toX, toY, promoChar);
12430         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12431         currentMove = forwardMostMove;
12432         return TRUE;
12433     }
12434 }
12435
12436 /* Load the nth game from the given file */
12437 int
12438 LoadGameFromFile (char *filename, int n, char *title, int useList)
12439 {
12440     FILE *f;
12441     char buf[MSG_SIZ];
12442
12443     if (strcmp(filename, "-") == 0) {
12444         f = stdin;
12445         title = "stdin";
12446     } else {
12447         f = fopen(filename, "rb");
12448         if (f == NULL) {
12449           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12450             DisplayError(buf, errno);
12451             return FALSE;
12452         }
12453     }
12454     if (fseek(f, 0, 0) == -1) {
12455         /* f is not seekable; probably a pipe */
12456         useList = FALSE;
12457     }
12458     if (useList && n == 0) {
12459         int error = GameListBuild(f);
12460         if (error) {
12461             DisplayError(_("Cannot build game list"), error);
12462         } else if (!ListEmpty(&gameList) &&
12463                    ((ListGame *) gameList.tailPred)->number > 1) {
12464             GameListPopUp(f, title);
12465             return TRUE;
12466         }
12467         GameListDestroy();
12468         n = 1;
12469     }
12470     if (n == 0) n = 1;
12471     return LoadGame(f, n, title, FALSE);
12472 }
12473
12474
12475 void
12476 MakeRegisteredMove ()
12477 {
12478     int fromX, fromY, toX, toY;
12479     char promoChar;
12480     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12481         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12482           case CMAIL_MOVE:
12483           case CMAIL_DRAW:
12484             if (appData.debugMode)
12485               fprintf(debugFP, "Restoring %s for game %d\n",
12486                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12487
12488             thinkOutput[0] = NULLCHAR;
12489             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12490             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12491             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12492             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12493             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12494             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12495             MakeMove(fromX, fromY, toX, toY, promoChar);
12496             ShowMove(fromX, fromY, toX, toY);
12497
12498             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12499               case MT_NONE:
12500               case MT_CHECK:
12501                 break;
12502
12503               case MT_CHECKMATE:
12504               case MT_STAINMATE:
12505                 if (WhiteOnMove(currentMove)) {
12506                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12507                 } else {
12508                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12509                 }
12510                 break;
12511
12512               case MT_STALEMATE:
12513                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12514                 break;
12515             }
12516
12517             break;
12518
12519           case CMAIL_RESIGN:
12520             if (WhiteOnMove(currentMove)) {
12521                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12522             } else {
12523                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12524             }
12525             break;
12526
12527           case CMAIL_ACCEPT:
12528             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12529             break;
12530
12531           default:
12532             break;
12533         }
12534     }
12535
12536     return;
12537 }
12538
12539 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12540 int
12541 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12542 {
12543     int retVal;
12544
12545     if (gameNumber > nCmailGames) {
12546         DisplayError(_("No more games in this message"), 0);
12547         return FALSE;
12548     }
12549     if (f == lastLoadGameFP) {
12550         int offset = gameNumber - lastLoadGameNumber;
12551         if (offset == 0) {
12552             cmailMsg[0] = NULLCHAR;
12553             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12554                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12555                 nCmailMovesRegistered--;
12556             }
12557             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12558             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12559                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12560             }
12561         } else {
12562             if (! RegisterMove()) return FALSE;
12563         }
12564     }
12565
12566     retVal = LoadGame(f, gameNumber, title, useList);
12567
12568     /* Make move registered during previous look at this game, if any */
12569     MakeRegisteredMove();
12570
12571     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12572         commentList[currentMove]
12573           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12574         DisplayComment(currentMove - 1, commentList[currentMove]);
12575     }
12576
12577     return retVal;
12578 }
12579
12580 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12581 int
12582 ReloadGame (int offset)
12583 {
12584     int gameNumber = lastLoadGameNumber + offset;
12585     if (lastLoadGameFP == NULL) {
12586         DisplayError(_("No game has been loaded yet"), 0);
12587         return FALSE;
12588     }
12589     if (gameNumber <= 0) {
12590         DisplayError(_("Can't back up any further"), 0);
12591         return FALSE;
12592     }
12593     if (cmailMsgLoaded) {
12594         return CmailLoadGame(lastLoadGameFP, gameNumber,
12595                              lastLoadGameTitle, lastLoadGameUseList);
12596     } else {
12597         return LoadGame(lastLoadGameFP, gameNumber,
12598                         lastLoadGameTitle, lastLoadGameUseList);
12599     }
12600 }
12601
12602 int keys[EmptySquare+1];
12603
12604 int
12605 PositionMatches (Board b1, Board b2)
12606 {
12607     int r, f, sum=0;
12608     switch(appData.searchMode) {
12609         case 1: return CompareWithRights(b1, b2);
12610         case 2:
12611             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12612                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12613             }
12614             return TRUE;
12615         case 3:
12616             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12617               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12618                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12619             }
12620             return sum==0;
12621         case 4:
12622             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12623                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12624             }
12625             return sum==0;
12626     }
12627     return TRUE;
12628 }
12629
12630 #define Q_PROMO  4
12631 #define Q_EP     3
12632 #define Q_BCASTL 2
12633 #define Q_WCASTL 1
12634
12635 int pieceList[256], quickBoard[256];
12636 ChessSquare pieceType[256] = { EmptySquare };
12637 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12638 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12639 int soughtTotal, turn;
12640 Boolean epOK, flipSearch;
12641
12642 typedef struct {
12643     unsigned char piece, to;
12644 } Move;
12645
12646 #define DSIZE (250000)
12647
12648 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12649 Move *moveDatabase = initialSpace;
12650 unsigned int movePtr, dataSize = DSIZE;
12651
12652 int
12653 MakePieceList (Board board, int *counts)
12654 {
12655     int r, f, n=Q_PROMO, total=0;
12656     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12657     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12658         int sq = f + (r<<4);
12659         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12660             quickBoard[sq] = ++n;
12661             pieceList[n] = sq;
12662             pieceType[n] = board[r][f];
12663             counts[board[r][f]]++;
12664             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12665             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12666             total++;
12667         }
12668     }
12669     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12670     return total;
12671 }
12672
12673 void
12674 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12675 {
12676     int sq = fromX + (fromY<<4);
12677     int piece = quickBoard[sq], rook;
12678     quickBoard[sq] = 0;
12679     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12680     if(piece == pieceList[1] && fromY == toY) {
12681       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12682         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12683         moveDatabase[movePtr++].piece = Q_WCASTL;
12684         quickBoard[sq] = piece;
12685         piece = quickBoard[from]; quickBoard[from] = 0;
12686         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12687       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12688         quickBoard[sq] = 0; // remove Rook
12689         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12690         moveDatabase[movePtr++].piece = Q_WCASTL;
12691         quickBoard[sq] = pieceList[1]; // put King
12692         piece = rook;
12693         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12694       }
12695     } else
12696     if(piece == pieceList[2] && fromY == toY) {
12697       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12698         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12699         moveDatabase[movePtr++].piece = Q_BCASTL;
12700         quickBoard[sq] = piece;
12701         piece = quickBoard[from]; quickBoard[from] = 0;
12702         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12703       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12704         quickBoard[sq] = 0; // remove Rook
12705         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12706         moveDatabase[movePtr++].piece = Q_BCASTL;
12707         quickBoard[sq] = pieceList[2]; // put King
12708         piece = rook;
12709         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12710       }
12711     } else
12712     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12713         quickBoard[(fromY<<4)+toX] = 0;
12714         moveDatabase[movePtr].piece = Q_EP;
12715         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12716         moveDatabase[movePtr].to = sq;
12717     } else
12718     if(promoPiece != pieceType[piece]) {
12719         moveDatabase[movePtr++].piece = Q_PROMO;
12720         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12721     }
12722     moveDatabase[movePtr].piece = piece;
12723     quickBoard[sq] = piece;
12724     movePtr++;
12725 }
12726
12727 int
12728 PackGame (Board board)
12729 {
12730     Move *newSpace = NULL;
12731     moveDatabase[movePtr].piece = 0; // terminate previous game
12732     if(movePtr > dataSize) {
12733         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12734         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12735         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12736         if(newSpace) {
12737             int i;
12738             Move *p = moveDatabase, *q = newSpace;
12739             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12740             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12741             moveDatabase = newSpace;
12742         } else { // calloc failed, we must be out of memory. Too bad...
12743             dataSize = 0; // prevent calloc events for all subsequent games
12744             return 0;     // and signal this one isn't cached
12745         }
12746     }
12747     movePtr++;
12748     MakePieceList(board, counts);
12749     return movePtr;
12750 }
12751
12752 int
12753 QuickCompare (Board board, int *minCounts, int *maxCounts)
12754 {   // compare according to search mode
12755     int r, f;
12756     switch(appData.searchMode)
12757     {
12758       case 1: // exact position match
12759         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12760         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12761             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12762         }
12763         break;
12764       case 2: // can have extra material on empty squares
12765         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12766             if(board[r][f] == EmptySquare) continue;
12767             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12768         }
12769         break;
12770       case 3: // material with exact Pawn structure
12771         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12772             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12773             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12774         } // fall through to material comparison
12775       case 4: // exact material
12776         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12777         break;
12778       case 6: // material range with given imbalance
12779         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12780         // fall through to range comparison
12781       case 5: // material range
12782         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12783     }
12784     return TRUE;
12785 }
12786
12787 int
12788 QuickScan (Board board, Move *move)
12789 {   // reconstruct game,and compare all positions in it
12790     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12791     do {
12792         int piece = move->piece;
12793         int to = move->to, from = pieceList[piece];
12794         if(found < 0) { // if already found just scan to game end for final piece count
12795           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12796            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12797            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12798                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12799             ) {
12800             static int lastCounts[EmptySquare+1];
12801             int i;
12802             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12803             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12804           } else stretch = 0;
12805           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12806           if(found >= 0 && !appData.minPieces) return found;
12807         }
12808         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12809           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12810           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12811             piece = (++move)->piece;
12812             from = pieceList[piece];
12813             counts[pieceType[piece]]--;
12814             pieceType[piece] = (ChessSquare) move->to;
12815             counts[move->to]++;
12816           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12817             counts[pieceType[quickBoard[to]]]--;
12818             quickBoard[to] = 0; total--;
12819             move++;
12820             continue;
12821           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12822             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12823             from  = pieceList[piece]; // so this must be King
12824             quickBoard[from] = 0;
12825             pieceList[piece] = to;
12826             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12827             quickBoard[from] = 0; // rook
12828             quickBoard[to] = piece;
12829             to = move->to; piece = move->piece;
12830             goto aftercastle;
12831           }
12832         }
12833         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12834         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12835         quickBoard[from] = 0;
12836       aftercastle:
12837         quickBoard[to] = piece;
12838         pieceList[piece] = to;
12839         cnt++; turn ^= 3;
12840         move++;
12841     } while(1);
12842 }
12843
12844 void
12845 InitSearch ()
12846 {
12847     int r, f;
12848     flipSearch = FALSE;
12849     CopyBoard(soughtBoard, boards[currentMove]);
12850     soughtTotal = MakePieceList(soughtBoard, maxSought);
12851     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12852     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12853     CopyBoard(reverseBoard, boards[currentMove]);
12854     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12855         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12856         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12857         reverseBoard[r][f] = piece;
12858     }
12859     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12860     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12861     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12862                  || (boards[currentMove][CASTLING][2] == NoRights ||
12863                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12864                  && (boards[currentMove][CASTLING][5] == NoRights ||
12865                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12866       ) {
12867         flipSearch = TRUE;
12868         CopyBoard(flipBoard, soughtBoard);
12869         CopyBoard(rotateBoard, reverseBoard);
12870         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12871             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12872             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12873         }
12874     }
12875     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12876     if(appData.searchMode >= 5) {
12877         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12878         MakePieceList(soughtBoard, minSought);
12879         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12880     }
12881     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12882         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12883 }
12884
12885 GameInfo dummyInfo;
12886 static int creatingBook;
12887
12888 int
12889 GameContainsPosition (FILE *f, ListGame *lg)
12890 {
12891     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12892     int fromX, fromY, toX, toY;
12893     char promoChar;
12894     static int initDone=FALSE;
12895
12896     // weed out games based on numerical tag comparison
12897     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12898     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12899     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12900     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12901     if(!initDone) {
12902         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12903         initDone = TRUE;
12904     }
12905     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12906     else CopyBoard(boards[scratch], initialPosition); // default start position
12907     if(lg->moves) {
12908         turn = btm + 1;
12909         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12910         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12911     }
12912     if(btm) plyNr++;
12913     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12914     fseek(f, lg->offset, 0);
12915     yynewfile(f);
12916     while(1) {
12917         yyboardindex = scratch;
12918         quickFlag = plyNr+1;
12919         next = Myylex();
12920         quickFlag = 0;
12921         switch(next) {
12922             case PGNTag:
12923                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12924             default:
12925                 continue;
12926
12927             case XBoardGame:
12928             case GNUChessGame:
12929                 if(plyNr) return -1; // after we have seen moves, this is for new game
12930               continue;
12931
12932             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12933             case ImpossibleMove:
12934             case WhiteWins: // game ends here with these four
12935             case BlackWins:
12936             case GameIsDrawn:
12937             case GameUnfinished:
12938                 return -1;
12939
12940             case IllegalMove:
12941                 if(appData.testLegality) return -1;
12942             case WhiteCapturesEnPassant:
12943             case BlackCapturesEnPassant:
12944             case WhitePromotion:
12945             case BlackPromotion:
12946             case WhiteNonPromotion:
12947             case BlackNonPromotion:
12948             case NormalMove:
12949             case FirstLeg:
12950             case WhiteKingSideCastle:
12951             case WhiteQueenSideCastle:
12952             case BlackKingSideCastle:
12953             case BlackQueenSideCastle:
12954             case WhiteKingSideCastleWild:
12955             case WhiteQueenSideCastleWild:
12956             case BlackKingSideCastleWild:
12957             case BlackQueenSideCastleWild:
12958             case WhiteHSideCastleFR:
12959             case WhiteASideCastleFR:
12960             case BlackHSideCastleFR:
12961             case BlackASideCastleFR:
12962                 fromX = currentMoveString[0] - AAA;
12963                 fromY = currentMoveString[1] - ONE;
12964                 toX = currentMoveString[2] - AAA;
12965                 toY = currentMoveString[3] - ONE;
12966                 promoChar = currentMoveString[4];
12967                 break;
12968             case WhiteDrop:
12969             case BlackDrop:
12970                 fromX = next == WhiteDrop ?
12971                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12972                   (int) CharToPiece(ToLower(currentMoveString[0]));
12973                 fromY = DROP_RANK;
12974                 toX = currentMoveString[2] - AAA;
12975                 toY = currentMoveString[3] - ONE;
12976                 promoChar = 0;
12977                 break;
12978         }
12979         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12980         plyNr++;
12981         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12982         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12983         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12984         if(appData.findMirror) {
12985             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12986             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12987         }
12988     }
12989 }
12990
12991 /* Load the nth game from open file f */
12992 int
12993 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12994 {
12995     ChessMove cm;
12996     char buf[MSG_SIZ];
12997     int gn = gameNumber;
12998     ListGame *lg = NULL;
12999     int numPGNTags = 0, i;
13000     int err, pos = -1;
13001     GameMode oldGameMode;
13002     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13003     char oldName[MSG_SIZ];
13004
13005     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13006
13007     if (appData.debugMode)
13008         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13009
13010     if (gameMode == Training )
13011         SetTrainingModeOff();
13012
13013     oldGameMode = gameMode;
13014     if (gameMode != BeginningOfGame) {
13015       Reset(FALSE, TRUE);
13016     }
13017     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13018
13019     gameFileFP = f;
13020     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13021         fclose(lastLoadGameFP);
13022     }
13023
13024     if (useList) {
13025         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13026
13027         if (lg) {
13028             fseek(f, lg->offset, 0);
13029             GameListHighlight(gameNumber);
13030             pos = lg->position;
13031             gn = 1;
13032         }
13033         else {
13034             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13035               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13036             else
13037             DisplayError(_("Game number out of range"), 0);
13038             return FALSE;
13039         }
13040     } else {
13041         GameListDestroy();
13042         if (fseek(f, 0, 0) == -1) {
13043             if (f == lastLoadGameFP ?
13044                 gameNumber == lastLoadGameNumber + 1 :
13045                 gameNumber == 1) {
13046                 gn = 1;
13047             } else {
13048                 DisplayError(_("Can't seek on game file"), 0);
13049                 return FALSE;
13050             }
13051         }
13052     }
13053     lastLoadGameFP = f;
13054     lastLoadGameNumber = gameNumber;
13055     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13056     lastLoadGameUseList = useList;
13057
13058     yynewfile(f);
13059
13060     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13061       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13062                 lg->gameInfo.black);
13063             DisplayTitle(buf);
13064     } else if (*title != NULLCHAR) {
13065         if (gameNumber > 1) {
13066           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13067             DisplayTitle(buf);
13068         } else {
13069             DisplayTitle(title);
13070         }
13071     }
13072
13073     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13074         gameMode = PlayFromGameFile;
13075         ModeHighlight();
13076     }
13077
13078     currentMove = forwardMostMove = backwardMostMove = 0;
13079     CopyBoard(boards[0], initialPosition);
13080     StopClocks();
13081
13082     /*
13083      * Skip the first gn-1 games in the file.
13084      * Also skip over anything that precedes an identifiable
13085      * start of game marker, to avoid being confused by
13086      * garbage at the start of the file.  Currently
13087      * recognized start of game markers are the move number "1",
13088      * the pattern "gnuchess .* game", the pattern
13089      * "^[#;%] [^ ]* game file", and a PGN tag block.
13090      * A game that starts with one of the latter two patterns
13091      * will also have a move number 1, possibly
13092      * following a position diagram.
13093      * 5-4-02: Let's try being more lenient and allowing a game to
13094      * start with an unnumbered move.  Does that break anything?
13095      */
13096     cm = lastLoadGameStart = EndOfFile;
13097     while (gn > 0) {
13098         yyboardindex = forwardMostMove;
13099         cm = (ChessMove) Myylex();
13100         switch (cm) {
13101           case EndOfFile:
13102             if (cmailMsgLoaded) {
13103                 nCmailGames = CMAIL_MAX_GAMES - gn;
13104             } else {
13105                 Reset(TRUE, TRUE);
13106                 DisplayError(_("Game not found in file"), 0);
13107             }
13108             return FALSE;
13109
13110           case GNUChessGame:
13111           case XBoardGame:
13112             gn--;
13113             lastLoadGameStart = cm;
13114             break;
13115
13116           case MoveNumberOne:
13117             switch (lastLoadGameStart) {
13118               case GNUChessGame:
13119               case XBoardGame:
13120               case PGNTag:
13121                 break;
13122               case MoveNumberOne:
13123               case EndOfFile:
13124                 gn--;           /* count this game */
13125                 lastLoadGameStart = cm;
13126                 break;
13127               default:
13128                 /* impossible */
13129                 break;
13130             }
13131             break;
13132
13133           case PGNTag:
13134             switch (lastLoadGameStart) {
13135               case GNUChessGame:
13136               case PGNTag:
13137               case MoveNumberOne:
13138               case EndOfFile:
13139                 gn--;           /* count this game */
13140                 lastLoadGameStart = cm;
13141                 break;
13142               case XBoardGame:
13143                 lastLoadGameStart = cm; /* game counted already */
13144                 break;
13145               default:
13146                 /* impossible */
13147                 break;
13148             }
13149             if (gn > 0) {
13150                 do {
13151                     yyboardindex = forwardMostMove;
13152                     cm = (ChessMove) Myylex();
13153                 } while (cm == PGNTag || cm == Comment);
13154             }
13155             break;
13156
13157           case WhiteWins:
13158           case BlackWins:
13159           case GameIsDrawn:
13160             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13161                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13162                     != CMAIL_OLD_RESULT) {
13163                     nCmailResults ++ ;
13164                     cmailResult[  CMAIL_MAX_GAMES
13165                                 - gn - 1] = CMAIL_OLD_RESULT;
13166                 }
13167             }
13168             break;
13169
13170           case NormalMove:
13171           case FirstLeg:
13172             /* Only a NormalMove can be at the start of a game
13173              * without a position diagram. */
13174             if (lastLoadGameStart == EndOfFile ) {
13175               gn--;
13176               lastLoadGameStart = MoveNumberOne;
13177             }
13178             break;
13179
13180           default:
13181             break;
13182         }
13183     }
13184
13185     if (appData.debugMode)
13186       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13187
13188     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13189
13190     if (cm == XBoardGame) {
13191         /* Skip any header junk before position diagram and/or move 1 */
13192         for (;;) {
13193             yyboardindex = forwardMostMove;
13194             cm = (ChessMove) Myylex();
13195
13196             if (cm == EndOfFile ||
13197                 cm == GNUChessGame || cm == XBoardGame) {
13198                 /* Empty game; pretend end-of-file and handle later */
13199                 cm = EndOfFile;
13200                 break;
13201             }
13202
13203             if (cm == MoveNumberOne || cm == PositionDiagram ||
13204                 cm == PGNTag || cm == Comment)
13205               break;
13206         }
13207     } else if (cm == GNUChessGame) {
13208         if (gameInfo.event != NULL) {
13209             free(gameInfo.event);
13210         }
13211         gameInfo.event = StrSave(yy_text);
13212     }
13213
13214     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13215     while (cm == PGNTag) {
13216         if (appData.debugMode)
13217           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13218         err = ParsePGNTag(yy_text, &gameInfo);
13219         if (!err) numPGNTags++;
13220
13221         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13222         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13223             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13224             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13225             InitPosition(TRUE);
13226             oldVariant = gameInfo.variant;
13227             if (appData.debugMode)
13228               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13229         }
13230
13231
13232         if (gameInfo.fen != NULL) {
13233           Board initial_position;
13234           startedFromSetupPosition = TRUE;
13235           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13236             Reset(TRUE, TRUE);
13237             DisplayError(_("Bad FEN position in file"), 0);
13238             return FALSE;
13239           }
13240           CopyBoard(boards[0], initial_position);
13241           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13242             CopyBoard(initialPosition, initial_position);
13243           if (blackPlaysFirst) {
13244             currentMove = forwardMostMove = backwardMostMove = 1;
13245             CopyBoard(boards[1], initial_position);
13246             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13247             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13248             timeRemaining[0][1] = whiteTimeRemaining;
13249             timeRemaining[1][1] = blackTimeRemaining;
13250             if (commentList[0] != NULL) {
13251               commentList[1] = commentList[0];
13252               commentList[0] = NULL;
13253             }
13254           } else {
13255             currentMove = forwardMostMove = backwardMostMove = 0;
13256           }
13257           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13258           {   int i;
13259               initialRulePlies = FENrulePlies;
13260               for( i=0; i< nrCastlingRights; i++ )
13261                   initialRights[i] = initial_position[CASTLING][i];
13262           }
13263           yyboardindex = forwardMostMove;
13264           free(gameInfo.fen);
13265           gameInfo.fen = NULL;
13266         }
13267
13268         yyboardindex = forwardMostMove;
13269         cm = (ChessMove) Myylex();
13270
13271         /* Handle comments interspersed among the tags */
13272         while (cm == Comment) {
13273             char *p;
13274             if (appData.debugMode)
13275               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13276             p = yy_text;
13277             AppendComment(currentMove, p, FALSE);
13278             yyboardindex = forwardMostMove;
13279             cm = (ChessMove) Myylex();
13280         }
13281     }
13282
13283     /* don't rely on existence of Event tag since if game was
13284      * pasted from clipboard the Event tag may not exist
13285      */
13286     if (numPGNTags > 0){
13287         char *tags;
13288         if (gameInfo.variant == VariantNormal) {
13289           VariantClass v = StringToVariant(gameInfo.event);
13290           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13291           if(v < VariantShogi) gameInfo.variant = v;
13292         }
13293         if (!matchMode) {
13294           if( appData.autoDisplayTags ) {
13295             tags = PGNTags(&gameInfo);
13296             TagsPopUp(tags, CmailMsg());
13297             free(tags);
13298           }
13299         }
13300     } else {
13301         /* Make something up, but don't display it now */
13302         SetGameInfo();
13303         TagsPopDown();
13304     }
13305
13306     if (cm == PositionDiagram) {
13307         int i, j;
13308         char *p;
13309         Board initial_position;
13310
13311         if (appData.debugMode)
13312           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13313
13314         if (!startedFromSetupPosition) {
13315             p = yy_text;
13316             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13317               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13318                 switch (*p) {
13319                   case '{':
13320                   case '[':
13321                   case '-':
13322                   case ' ':
13323                   case '\t':
13324                   case '\n':
13325                   case '\r':
13326                     break;
13327                   default:
13328                     initial_position[i][j++] = CharToPiece(*p);
13329                     break;
13330                 }
13331             while (*p == ' ' || *p == '\t' ||
13332                    *p == '\n' || *p == '\r') p++;
13333
13334             if (strncmp(p, "black", strlen("black"))==0)
13335               blackPlaysFirst = TRUE;
13336             else
13337               blackPlaysFirst = FALSE;
13338             startedFromSetupPosition = TRUE;
13339
13340             CopyBoard(boards[0], initial_position);
13341             if (blackPlaysFirst) {
13342                 currentMove = forwardMostMove = backwardMostMove = 1;
13343                 CopyBoard(boards[1], initial_position);
13344                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13345                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13346                 timeRemaining[0][1] = whiteTimeRemaining;
13347                 timeRemaining[1][1] = blackTimeRemaining;
13348                 if (commentList[0] != NULL) {
13349                     commentList[1] = commentList[0];
13350                     commentList[0] = NULL;
13351                 }
13352             } else {
13353                 currentMove = forwardMostMove = backwardMostMove = 0;
13354             }
13355         }
13356         yyboardindex = forwardMostMove;
13357         cm = (ChessMove) Myylex();
13358     }
13359
13360   if(!creatingBook) {
13361     if (first.pr == NoProc) {
13362         StartChessProgram(&first);
13363     }
13364     InitChessProgram(&first, FALSE);
13365     if(gameInfo.variant == VariantUnknown && *oldName) {
13366         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13367         gameInfo.variant = v;
13368     }
13369     SendToProgram("force\n", &first);
13370     if (startedFromSetupPosition) {
13371         SendBoard(&first, forwardMostMove);
13372     if (appData.debugMode) {
13373         fprintf(debugFP, "Load Game\n");
13374     }
13375         DisplayBothClocks();
13376     }
13377   }
13378
13379     /* [HGM] server: flag to write setup moves in broadcast file as one */
13380     loadFlag = appData.suppressLoadMoves;
13381
13382     while (cm == Comment) {
13383         char *p;
13384         if (appData.debugMode)
13385           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13386         p = yy_text;
13387         AppendComment(currentMove, p, FALSE);
13388         yyboardindex = forwardMostMove;
13389         cm = (ChessMove) Myylex();
13390     }
13391
13392     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13393         cm == WhiteWins || cm == BlackWins ||
13394         cm == GameIsDrawn || cm == GameUnfinished) {
13395         DisplayMessage("", _("No moves in game"));
13396         if (cmailMsgLoaded) {
13397             if (appData.debugMode)
13398               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13399             ClearHighlights();
13400             flipView = FALSE;
13401         }
13402         DrawPosition(FALSE, boards[currentMove]);
13403         DisplayBothClocks();
13404         gameMode = EditGame;
13405         ModeHighlight();
13406         gameFileFP = NULL;
13407         cmailOldMove = 0;
13408         return TRUE;
13409     }
13410
13411     // [HGM] PV info: routine tests if comment empty
13412     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13413         DisplayComment(currentMove - 1, commentList[currentMove]);
13414     }
13415     if (!matchMode && appData.timeDelay != 0)
13416       DrawPosition(FALSE, boards[currentMove]);
13417
13418     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13419       programStats.ok_to_send = 1;
13420     }
13421
13422     /* if the first token after the PGN tags is a move
13423      * and not move number 1, retrieve it from the parser
13424      */
13425     if (cm != MoveNumberOne)
13426         LoadGameOneMove(cm);
13427
13428     /* load the remaining moves from the file */
13429     while (LoadGameOneMove(EndOfFile)) {
13430       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13431       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13432     }
13433
13434     /* rewind to the start of the game */
13435     currentMove = backwardMostMove;
13436
13437     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13438
13439     if (oldGameMode == AnalyzeFile) {
13440       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13441       AnalyzeFileEvent();
13442     } else
13443     if (oldGameMode == AnalyzeMode) {
13444       AnalyzeFileEvent();
13445     }
13446
13447     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13448         long int w, b; // [HGM] adjourn: restore saved clock times
13449         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13450         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13451             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13452             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13453         }
13454     }
13455
13456     if(creatingBook) return TRUE;
13457     if (!matchMode && pos > 0) {
13458         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13459     } else
13460     if (matchMode || appData.timeDelay == 0) {
13461       ToEndEvent();
13462     } else if (appData.timeDelay > 0) {
13463       AutoPlayGameLoop();
13464     }
13465
13466     if (appData.debugMode)
13467         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13468
13469     loadFlag = 0; /* [HGM] true game starts */
13470     return TRUE;
13471 }
13472
13473 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13474 int
13475 ReloadPosition (int offset)
13476 {
13477     int positionNumber = lastLoadPositionNumber + offset;
13478     if (lastLoadPositionFP == NULL) {
13479         DisplayError(_("No position has been loaded yet"), 0);
13480         return FALSE;
13481     }
13482     if (positionNumber <= 0) {
13483         DisplayError(_("Can't back up any further"), 0);
13484         return FALSE;
13485     }
13486     return LoadPosition(lastLoadPositionFP, positionNumber,
13487                         lastLoadPositionTitle);
13488 }
13489
13490 /* Load the nth position from the given file */
13491 int
13492 LoadPositionFromFile (char *filename, int n, char *title)
13493 {
13494     FILE *f;
13495     char buf[MSG_SIZ];
13496
13497     if (strcmp(filename, "-") == 0) {
13498         return LoadPosition(stdin, n, "stdin");
13499     } else {
13500         f = fopen(filename, "rb");
13501         if (f == NULL) {
13502             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13503             DisplayError(buf, errno);
13504             return FALSE;
13505         } else {
13506             return LoadPosition(f, n, title);
13507         }
13508     }
13509 }
13510
13511 /* Load the nth position from the given open file, and close it */
13512 int
13513 LoadPosition (FILE *f, int positionNumber, char *title)
13514 {
13515     char *p, line[MSG_SIZ];
13516     Board initial_position;
13517     int i, j, fenMode, pn;
13518
13519     if (gameMode == Training )
13520         SetTrainingModeOff();
13521
13522     if (gameMode != BeginningOfGame) {
13523         Reset(FALSE, TRUE);
13524     }
13525     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13526         fclose(lastLoadPositionFP);
13527     }
13528     if (positionNumber == 0) positionNumber = 1;
13529     lastLoadPositionFP = f;
13530     lastLoadPositionNumber = positionNumber;
13531     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13532     if (first.pr == NoProc && !appData.noChessProgram) {
13533       StartChessProgram(&first);
13534       InitChessProgram(&first, FALSE);
13535     }
13536     pn = positionNumber;
13537     if (positionNumber < 0) {
13538         /* Negative position number means to seek to that byte offset */
13539         if (fseek(f, -positionNumber, 0) == -1) {
13540             DisplayError(_("Can't seek on position file"), 0);
13541             return FALSE;
13542         };
13543         pn = 1;
13544     } else {
13545         if (fseek(f, 0, 0) == -1) {
13546             if (f == lastLoadPositionFP ?
13547                 positionNumber == lastLoadPositionNumber + 1 :
13548                 positionNumber == 1) {
13549                 pn = 1;
13550             } else {
13551                 DisplayError(_("Can't seek on position file"), 0);
13552                 return FALSE;
13553             }
13554         }
13555     }
13556     /* See if this file is FEN or old-style xboard */
13557     if (fgets(line, MSG_SIZ, f) == NULL) {
13558         DisplayError(_("Position not found in file"), 0);
13559         return FALSE;
13560     }
13561     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13562     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13563
13564     if (pn >= 2) {
13565         if (fenMode || line[0] == '#') pn--;
13566         while (pn > 0) {
13567             /* skip positions before number pn */
13568             if (fgets(line, MSG_SIZ, f) == NULL) {
13569                 Reset(TRUE, TRUE);
13570                 DisplayError(_("Position not found in file"), 0);
13571                 return FALSE;
13572             }
13573             if (fenMode || line[0] == '#') pn--;
13574         }
13575     }
13576
13577     if (fenMode) {
13578         char *p;
13579         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13580             DisplayError(_("Bad FEN position in file"), 0);
13581             return FALSE;
13582         }
13583         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13584             sscanf(p+4, "%[^;]", bestMove);
13585         } else *bestMove = NULLCHAR;
13586         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13587             sscanf(p+4, "%[^;]", avoidMove);
13588         } else *avoidMove = NULLCHAR;
13589     } else {
13590         (void) fgets(line, MSG_SIZ, f);
13591         (void) fgets(line, MSG_SIZ, f);
13592
13593         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13594             (void) fgets(line, MSG_SIZ, f);
13595             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13596                 if (*p == ' ')
13597                   continue;
13598                 initial_position[i][j++] = CharToPiece(*p);
13599             }
13600         }
13601
13602         blackPlaysFirst = FALSE;
13603         if (!feof(f)) {
13604             (void) fgets(line, MSG_SIZ, f);
13605             if (strncmp(line, "black", strlen("black"))==0)
13606               blackPlaysFirst = TRUE;
13607         }
13608     }
13609     startedFromSetupPosition = TRUE;
13610
13611     CopyBoard(boards[0], initial_position);
13612     if (blackPlaysFirst) {
13613         currentMove = forwardMostMove = backwardMostMove = 1;
13614         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13615         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13616         CopyBoard(boards[1], initial_position);
13617         DisplayMessage("", _("Black to play"));
13618     } else {
13619         currentMove = forwardMostMove = backwardMostMove = 0;
13620         DisplayMessage("", _("White to play"));
13621     }
13622     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13623     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13624         SendToProgram("force\n", &first);
13625         SendBoard(&first, forwardMostMove);
13626     }
13627     if (appData.debugMode) {
13628 int i, j;
13629   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13630   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13631         fprintf(debugFP, "Load Position\n");
13632     }
13633
13634     if (positionNumber > 1) {
13635       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13636         DisplayTitle(line);
13637     } else {
13638         DisplayTitle(title);
13639     }
13640     gameMode = EditGame;
13641     ModeHighlight();
13642     ResetClocks();
13643     timeRemaining[0][1] = whiteTimeRemaining;
13644     timeRemaining[1][1] = blackTimeRemaining;
13645     DrawPosition(FALSE, boards[currentMove]);
13646
13647     return TRUE;
13648 }
13649
13650
13651 void
13652 CopyPlayerNameIntoFileName (char **dest, char *src)
13653 {
13654     while (*src != NULLCHAR && *src != ',') {
13655         if (*src == ' ') {
13656             *(*dest)++ = '_';
13657             src++;
13658         } else {
13659             *(*dest)++ = *src++;
13660         }
13661     }
13662 }
13663
13664 char *
13665 DefaultFileName (char *ext)
13666 {
13667     static char def[MSG_SIZ];
13668     char *p;
13669
13670     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13671         p = def;
13672         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13673         *p++ = '-';
13674         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13675         *p++ = '.';
13676         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13677     } else {
13678         def[0] = NULLCHAR;
13679     }
13680     return def;
13681 }
13682
13683 /* Save the current game to the given file */
13684 int
13685 SaveGameToFile (char *filename, int append)
13686 {
13687     FILE *f;
13688     char buf[MSG_SIZ];
13689     int result, i, t,tot=0;
13690
13691     if (strcmp(filename, "-") == 0) {
13692         return SaveGame(stdout, 0, NULL);
13693     } else {
13694         for(i=0; i<10; i++) { // upto 10 tries
13695              f = fopen(filename, append ? "a" : "w");
13696              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13697              if(f || errno != 13) break;
13698              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13699              tot += t;
13700         }
13701         if (f == NULL) {
13702             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13703             DisplayError(buf, errno);
13704             return FALSE;
13705         } else {
13706             safeStrCpy(buf, lastMsg, MSG_SIZ);
13707             DisplayMessage(_("Waiting for access to save file"), "");
13708             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13709             DisplayMessage(_("Saving game"), "");
13710             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13711             result = SaveGame(f, 0, NULL);
13712             DisplayMessage(buf, "");
13713             return result;
13714         }
13715     }
13716 }
13717
13718 char *
13719 SavePart (char *str)
13720 {
13721     static char buf[MSG_SIZ];
13722     char *p;
13723
13724     p = strchr(str, ' ');
13725     if (p == NULL) return str;
13726     strncpy(buf, str, p - str);
13727     buf[p - str] = NULLCHAR;
13728     return buf;
13729 }
13730
13731 #define PGN_MAX_LINE 75
13732
13733 #define PGN_SIDE_WHITE  0
13734 #define PGN_SIDE_BLACK  1
13735
13736 static int
13737 FindFirstMoveOutOfBook (int side)
13738 {
13739     int result = -1;
13740
13741     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13742         int index = backwardMostMove;
13743         int has_book_hit = 0;
13744
13745         if( (index % 2) != side ) {
13746             index++;
13747         }
13748
13749         while( index < forwardMostMove ) {
13750             /* Check to see if engine is in book */
13751             int depth = pvInfoList[index].depth;
13752             int score = pvInfoList[index].score;
13753             int in_book = 0;
13754
13755             if( depth <= 2 ) {
13756                 in_book = 1;
13757             }
13758             else if( score == 0 && depth == 63 ) {
13759                 in_book = 1; /* Zappa */
13760             }
13761             else if( score == 2 && depth == 99 ) {
13762                 in_book = 1; /* Abrok */
13763             }
13764
13765             has_book_hit += in_book;
13766
13767             if( ! in_book ) {
13768                 result = index;
13769
13770                 break;
13771             }
13772
13773             index += 2;
13774         }
13775     }
13776
13777     return result;
13778 }
13779
13780 void
13781 GetOutOfBookInfo (char * buf)
13782 {
13783     int oob[2];
13784     int i;
13785     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13786
13787     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13788     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13789
13790     *buf = '\0';
13791
13792     if( oob[0] >= 0 || oob[1] >= 0 ) {
13793         for( i=0; i<2; i++ ) {
13794             int idx = oob[i];
13795
13796             if( idx >= 0 ) {
13797                 if( i > 0 && oob[0] >= 0 ) {
13798                     strcat( buf, "   " );
13799                 }
13800
13801                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13802                 sprintf( buf+strlen(buf), "%s%.2f",
13803                     pvInfoList[idx].score >= 0 ? "+" : "",
13804                     pvInfoList[idx].score / 100.0 );
13805             }
13806         }
13807     }
13808 }
13809
13810 /* Save game in PGN style */
13811 static void
13812 SaveGamePGN2 (FILE *f)
13813 {
13814     int i, offset, linelen, newblock;
13815 //    char *movetext;
13816     char numtext[32];
13817     int movelen, numlen, blank;
13818     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13819
13820     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13821
13822     PrintPGNTags(f, &gameInfo);
13823
13824     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13825
13826     if (backwardMostMove > 0 || startedFromSetupPosition) {
13827         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13828         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13829         fprintf(f, "\n{--------------\n");
13830         PrintPosition(f, backwardMostMove);
13831         fprintf(f, "--------------}\n");
13832         free(fen);
13833     }
13834     else {
13835         /* [AS] Out of book annotation */
13836         if( appData.saveOutOfBookInfo ) {
13837             char buf[64];
13838
13839             GetOutOfBookInfo( buf );
13840
13841             if( buf[0] != '\0' ) {
13842                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13843             }
13844         }
13845
13846         fprintf(f, "\n");
13847     }
13848
13849     i = backwardMostMove;
13850     linelen = 0;
13851     newblock = TRUE;
13852
13853     while (i < forwardMostMove) {
13854         /* Print comments preceding this move */
13855         if (commentList[i] != NULL) {
13856             if (linelen > 0) fprintf(f, "\n");
13857             fprintf(f, "%s", commentList[i]);
13858             linelen = 0;
13859             newblock = TRUE;
13860         }
13861
13862         /* Format move number */
13863         if ((i % 2) == 0)
13864           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13865         else
13866           if (newblock)
13867             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13868           else
13869             numtext[0] = NULLCHAR;
13870
13871         numlen = strlen(numtext);
13872         newblock = FALSE;
13873
13874         /* Print move number */
13875         blank = linelen > 0 && numlen > 0;
13876         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13877             fprintf(f, "\n");
13878             linelen = 0;
13879             blank = 0;
13880         }
13881         if (blank) {
13882             fprintf(f, " ");
13883             linelen++;
13884         }
13885         fprintf(f, "%s", numtext);
13886         linelen += numlen;
13887
13888         /* Get move */
13889         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13890         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13891
13892         /* Print move */
13893         blank = linelen > 0 && movelen > 0;
13894         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13895             fprintf(f, "\n");
13896             linelen = 0;
13897             blank = 0;
13898         }
13899         if (blank) {
13900             fprintf(f, " ");
13901             linelen++;
13902         }
13903         fprintf(f, "%s", move_buffer);
13904         linelen += movelen;
13905
13906         /* [AS] Add PV info if present */
13907         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13908             /* [HGM] add time */
13909             char buf[MSG_SIZ]; int seconds;
13910
13911             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13912
13913             if( seconds <= 0)
13914               buf[0] = 0;
13915             else
13916               if( seconds < 30 )
13917                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13918               else
13919                 {
13920                   seconds = (seconds + 4)/10; // round to full seconds
13921                   if( seconds < 60 )
13922                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13923                   else
13924                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13925                 }
13926
13927             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13928                       pvInfoList[i].score >= 0 ? "+" : "",
13929                       pvInfoList[i].score / 100.0,
13930                       pvInfoList[i].depth,
13931                       buf );
13932
13933             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13934
13935             /* Print score/depth */
13936             blank = linelen > 0 && movelen > 0;
13937             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13938                 fprintf(f, "\n");
13939                 linelen = 0;
13940                 blank = 0;
13941             }
13942             if (blank) {
13943                 fprintf(f, " ");
13944                 linelen++;
13945             }
13946             fprintf(f, "%s", move_buffer);
13947             linelen += movelen;
13948         }
13949
13950         i++;
13951     }
13952
13953     /* Start a new line */
13954     if (linelen > 0) fprintf(f, "\n");
13955
13956     /* Print comments after last move */
13957     if (commentList[i] != NULL) {
13958         fprintf(f, "%s\n", commentList[i]);
13959     }
13960
13961     /* Print result */
13962     if (gameInfo.resultDetails != NULL &&
13963         gameInfo.resultDetails[0] != NULLCHAR) {
13964         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13965         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13966            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13967             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13968         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13969     } else {
13970         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13971     }
13972 }
13973
13974 /* Save game in PGN style and close the file */
13975 int
13976 SaveGamePGN (FILE *f)
13977 {
13978     SaveGamePGN2(f);
13979     fclose(f);
13980     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13981     return TRUE;
13982 }
13983
13984 /* Save game in old style and close the file */
13985 int
13986 SaveGameOldStyle (FILE *f)
13987 {
13988     int i, offset;
13989     time_t tm;
13990
13991     tm = time((time_t *) NULL);
13992
13993     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13994     PrintOpponents(f);
13995
13996     if (backwardMostMove > 0 || startedFromSetupPosition) {
13997         fprintf(f, "\n[--------------\n");
13998         PrintPosition(f, backwardMostMove);
13999         fprintf(f, "--------------]\n");
14000     } else {
14001         fprintf(f, "\n");
14002     }
14003
14004     i = backwardMostMove;
14005     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14006
14007     while (i < forwardMostMove) {
14008         if (commentList[i] != NULL) {
14009             fprintf(f, "[%s]\n", commentList[i]);
14010         }
14011
14012         if ((i % 2) == 1) {
14013             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14014             i++;
14015         } else {
14016             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14017             i++;
14018             if (commentList[i] != NULL) {
14019                 fprintf(f, "\n");
14020                 continue;
14021             }
14022             if (i >= forwardMostMove) {
14023                 fprintf(f, "\n");
14024                 break;
14025             }
14026             fprintf(f, "%s\n", parseList[i]);
14027             i++;
14028         }
14029     }
14030
14031     if (commentList[i] != NULL) {
14032         fprintf(f, "[%s]\n", commentList[i]);
14033     }
14034
14035     /* This isn't really the old style, but it's close enough */
14036     if (gameInfo.resultDetails != NULL &&
14037         gameInfo.resultDetails[0] != NULLCHAR) {
14038         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14039                 gameInfo.resultDetails);
14040     } else {
14041         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14042     }
14043
14044     fclose(f);
14045     return TRUE;
14046 }
14047
14048 /* Save the current game to open file f and close the file */
14049 int
14050 SaveGame (FILE *f, int dummy, char *dummy2)
14051 {
14052     if (gameMode == EditPosition) EditPositionDone(TRUE);
14053     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14054     if (appData.oldSaveStyle)
14055       return SaveGameOldStyle(f);
14056     else
14057       return SaveGamePGN(f);
14058 }
14059
14060 /* Save the current position to the given file */
14061 int
14062 SavePositionToFile (char *filename)
14063 {
14064     FILE *f;
14065     char buf[MSG_SIZ];
14066
14067     if (strcmp(filename, "-") == 0) {
14068         return SavePosition(stdout, 0, NULL);
14069     } else {
14070         f = fopen(filename, "a");
14071         if (f == NULL) {
14072             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14073             DisplayError(buf, errno);
14074             return FALSE;
14075         } else {
14076             safeStrCpy(buf, lastMsg, MSG_SIZ);
14077             DisplayMessage(_("Waiting for access to save file"), "");
14078             flock(fileno(f), LOCK_EX); // [HGM] lock
14079             DisplayMessage(_("Saving position"), "");
14080             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14081             SavePosition(f, 0, NULL);
14082             DisplayMessage(buf, "");
14083             return TRUE;
14084         }
14085     }
14086 }
14087
14088 /* Save the current position to the given open file and close the file */
14089 int
14090 SavePosition (FILE *f, int dummy, char *dummy2)
14091 {
14092     time_t tm;
14093     char *fen;
14094
14095     if (gameMode == EditPosition) EditPositionDone(TRUE);
14096     if (appData.oldSaveStyle) {
14097         tm = time((time_t *) NULL);
14098
14099         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14100         PrintOpponents(f);
14101         fprintf(f, "[--------------\n");
14102         PrintPosition(f, currentMove);
14103         fprintf(f, "--------------]\n");
14104     } else {
14105         fen = PositionToFEN(currentMove, NULL, 1);
14106         fprintf(f, "%s\n", fen);
14107         free(fen);
14108     }
14109     fclose(f);
14110     return TRUE;
14111 }
14112
14113 void
14114 ReloadCmailMsgEvent (int unregister)
14115 {
14116 #if !WIN32
14117     static char *inFilename = NULL;
14118     static char *outFilename;
14119     int i;
14120     struct stat inbuf, outbuf;
14121     int status;
14122
14123     /* Any registered moves are unregistered if unregister is set, */
14124     /* i.e. invoked by the signal handler */
14125     if (unregister) {
14126         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14127             cmailMoveRegistered[i] = FALSE;
14128             if (cmailCommentList[i] != NULL) {
14129                 free(cmailCommentList[i]);
14130                 cmailCommentList[i] = NULL;
14131             }
14132         }
14133         nCmailMovesRegistered = 0;
14134     }
14135
14136     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14137         cmailResult[i] = CMAIL_NOT_RESULT;
14138     }
14139     nCmailResults = 0;
14140
14141     if (inFilename == NULL) {
14142         /* Because the filenames are static they only get malloced once  */
14143         /* and they never get freed                                      */
14144         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14145         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14146
14147         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14148         sprintf(outFilename, "%s.out", appData.cmailGameName);
14149     }
14150
14151     status = stat(outFilename, &outbuf);
14152     if (status < 0) {
14153         cmailMailedMove = FALSE;
14154     } else {
14155         status = stat(inFilename, &inbuf);
14156         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14157     }
14158
14159     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14160        counts the games, notes how each one terminated, etc.
14161
14162        It would be nice to remove this kludge and instead gather all
14163        the information while building the game list.  (And to keep it
14164        in the game list nodes instead of having a bunch of fixed-size
14165        parallel arrays.)  Note this will require getting each game's
14166        termination from the PGN tags, as the game list builder does
14167        not process the game moves.  --mann
14168        */
14169     cmailMsgLoaded = TRUE;
14170     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14171
14172     /* Load first game in the file or popup game menu */
14173     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14174
14175 #endif /* !WIN32 */
14176     return;
14177 }
14178
14179 int
14180 RegisterMove ()
14181 {
14182     FILE *f;
14183     char string[MSG_SIZ];
14184
14185     if (   cmailMailedMove
14186         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14187         return TRUE;            /* Allow free viewing  */
14188     }
14189
14190     /* Unregister move to ensure that we don't leave RegisterMove        */
14191     /* with the move registered when the conditions for registering no   */
14192     /* longer hold                                                       */
14193     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14194         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14195         nCmailMovesRegistered --;
14196
14197         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14198           {
14199               free(cmailCommentList[lastLoadGameNumber - 1]);
14200               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14201           }
14202     }
14203
14204     if (cmailOldMove == -1) {
14205         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14206         return FALSE;
14207     }
14208
14209     if (currentMove > cmailOldMove + 1) {
14210         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14211         return FALSE;
14212     }
14213
14214     if (currentMove < cmailOldMove) {
14215         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14216         return FALSE;
14217     }
14218
14219     if (forwardMostMove > currentMove) {
14220         /* Silently truncate extra moves */
14221         TruncateGame();
14222     }
14223
14224     if (   (currentMove == cmailOldMove + 1)
14225         || (   (currentMove == cmailOldMove)
14226             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14227                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14228         if (gameInfo.result != GameUnfinished) {
14229             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14230         }
14231
14232         if (commentList[currentMove] != NULL) {
14233             cmailCommentList[lastLoadGameNumber - 1]
14234               = StrSave(commentList[currentMove]);
14235         }
14236         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14237
14238         if (appData.debugMode)
14239           fprintf(debugFP, "Saving %s for game %d\n",
14240                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14241
14242         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14243
14244         f = fopen(string, "w");
14245         if (appData.oldSaveStyle) {
14246             SaveGameOldStyle(f); /* also closes the file */
14247
14248             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14249             f = fopen(string, "w");
14250             SavePosition(f, 0, NULL); /* also closes the file */
14251         } else {
14252             fprintf(f, "{--------------\n");
14253             PrintPosition(f, currentMove);
14254             fprintf(f, "--------------}\n\n");
14255
14256             SaveGame(f, 0, NULL); /* also closes the file*/
14257         }
14258
14259         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14260         nCmailMovesRegistered ++;
14261     } else if (nCmailGames == 1) {
14262         DisplayError(_("You have not made a move yet"), 0);
14263         return FALSE;
14264     }
14265
14266     return TRUE;
14267 }
14268
14269 void
14270 MailMoveEvent ()
14271 {
14272 #if !WIN32
14273     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14274     FILE *commandOutput;
14275     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14276     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14277     int nBuffers;
14278     int i;
14279     int archived;
14280     char *arcDir;
14281
14282     if (! cmailMsgLoaded) {
14283         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14284         return;
14285     }
14286
14287     if (nCmailGames == nCmailResults) {
14288         DisplayError(_("No unfinished games"), 0);
14289         return;
14290     }
14291
14292 #if CMAIL_PROHIBIT_REMAIL
14293     if (cmailMailedMove) {
14294       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);
14295         DisplayError(msg, 0);
14296         return;
14297     }
14298 #endif
14299
14300     if (! (cmailMailedMove || RegisterMove())) return;
14301
14302     if (   cmailMailedMove
14303         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14304       snprintf(string, MSG_SIZ, partCommandString,
14305                appData.debugMode ? " -v" : "", appData.cmailGameName);
14306         commandOutput = popen(string, "r");
14307
14308         if (commandOutput == NULL) {
14309             DisplayError(_("Failed to invoke cmail"), 0);
14310         } else {
14311             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14312                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14313             }
14314             if (nBuffers > 1) {
14315                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14316                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14317                 nBytes = MSG_SIZ - 1;
14318             } else {
14319                 (void) memcpy(msg, buffer, nBytes);
14320             }
14321             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14322
14323             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14324                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14325
14326                 archived = TRUE;
14327                 for (i = 0; i < nCmailGames; i ++) {
14328                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14329                         archived = FALSE;
14330                     }
14331                 }
14332                 if (   archived
14333                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14334                         != NULL)) {
14335                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14336                            arcDir,
14337                            appData.cmailGameName,
14338                            gameInfo.date);
14339                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14340                     cmailMsgLoaded = FALSE;
14341                 }
14342             }
14343
14344             DisplayInformation(msg);
14345             pclose(commandOutput);
14346         }
14347     } else {
14348         if ((*cmailMsg) != '\0') {
14349             DisplayInformation(cmailMsg);
14350         }
14351     }
14352
14353     return;
14354 #endif /* !WIN32 */
14355 }
14356
14357 char *
14358 CmailMsg ()
14359 {
14360 #if WIN32
14361     return NULL;
14362 #else
14363     int  prependComma = 0;
14364     char number[5];
14365     char string[MSG_SIZ];       /* Space for game-list */
14366     int  i;
14367
14368     if (!cmailMsgLoaded) return "";
14369
14370     if (cmailMailedMove) {
14371       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14372     } else {
14373         /* Create a list of games left */
14374       snprintf(string, MSG_SIZ, "[");
14375         for (i = 0; i < nCmailGames; i ++) {
14376             if (! (   cmailMoveRegistered[i]
14377                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14378                 if (prependComma) {
14379                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14380                 } else {
14381                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14382                     prependComma = 1;
14383                 }
14384
14385                 strcat(string, number);
14386             }
14387         }
14388         strcat(string, "]");
14389
14390         if (nCmailMovesRegistered + nCmailResults == 0) {
14391             switch (nCmailGames) {
14392               case 1:
14393                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14394                 break;
14395
14396               case 2:
14397                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14398                 break;
14399
14400               default:
14401                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14402                          nCmailGames);
14403                 break;
14404             }
14405         } else {
14406             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14407               case 1:
14408                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14409                          string);
14410                 break;
14411
14412               case 0:
14413                 if (nCmailResults == nCmailGames) {
14414                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14415                 } else {
14416                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14417                 }
14418                 break;
14419
14420               default:
14421                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14422                          string);
14423             }
14424         }
14425     }
14426     return cmailMsg;
14427 #endif /* WIN32 */
14428 }
14429
14430 void
14431 ResetGameEvent ()
14432 {
14433     if (gameMode == Training)
14434       SetTrainingModeOff();
14435
14436     Reset(TRUE, TRUE);
14437     cmailMsgLoaded = FALSE;
14438     if (appData.icsActive) {
14439       SendToICS(ics_prefix);
14440       SendToICS("refresh\n");
14441     }
14442 }
14443
14444 void
14445 ExitEvent (int status)
14446 {
14447     exiting++;
14448     if (exiting > 2) {
14449       /* Give up on clean exit */
14450       exit(status);
14451     }
14452     if (exiting > 1) {
14453       /* Keep trying for clean exit */
14454       return;
14455     }
14456
14457     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14458     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14459
14460     if (telnetISR != NULL) {
14461       RemoveInputSource(telnetISR);
14462     }
14463     if (icsPR != NoProc) {
14464       DestroyChildProcess(icsPR, TRUE);
14465     }
14466
14467     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14468     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14469
14470     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14471     /* make sure this other one finishes before killing it!                  */
14472     if(endingGame) { int count = 0;
14473         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14474         while(endingGame && count++ < 10) DoSleep(1);
14475         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14476     }
14477
14478     /* Kill off chess programs */
14479     if (first.pr != NoProc) {
14480         ExitAnalyzeMode();
14481
14482         DoSleep( appData.delayBeforeQuit );
14483         SendToProgram("quit\n", &first);
14484         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14485     }
14486     if (second.pr != NoProc) {
14487         DoSleep( appData.delayBeforeQuit );
14488         SendToProgram("quit\n", &second);
14489         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14490     }
14491     if (first.isr != NULL) {
14492         RemoveInputSource(first.isr);
14493     }
14494     if (second.isr != NULL) {
14495         RemoveInputSource(second.isr);
14496     }
14497
14498     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14499     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14500
14501     ShutDownFrontEnd();
14502     exit(status);
14503 }
14504
14505 void
14506 PauseEngine (ChessProgramState *cps)
14507 {
14508     SendToProgram("pause\n", cps);
14509     cps->pause = 2;
14510 }
14511
14512 void
14513 UnPauseEngine (ChessProgramState *cps)
14514 {
14515     SendToProgram("resume\n", cps);
14516     cps->pause = 1;
14517 }
14518
14519 void
14520 PauseEvent ()
14521 {
14522     if (appData.debugMode)
14523         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14524     if (pausing) {
14525         pausing = FALSE;
14526         ModeHighlight();
14527         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14528             StartClocks();
14529             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14530                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14531                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14532             }
14533             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14534             HandleMachineMove(stashedInputMove, stalledEngine);
14535             stalledEngine = NULL;
14536             return;
14537         }
14538         if (gameMode == MachinePlaysWhite ||
14539             gameMode == TwoMachinesPlay   ||
14540             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14541             if(first.pause)  UnPauseEngine(&first);
14542             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14543             if(second.pause) UnPauseEngine(&second);
14544             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14545             StartClocks();
14546         } else {
14547             DisplayBothClocks();
14548         }
14549         if (gameMode == PlayFromGameFile) {
14550             if (appData.timeDelay >= 0)
14551                 AutoPlayGameLoop();
14552         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14553             Reset(FALSE, TRUE);
14554             SendToICS(ics_prefix);
14555             SendToICS("refresh\n");
14556         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14557             ForwardInner(forwardMostMove);
14558         }
14559         pauseExamInvalid = FALSE;
14560     } else {
14561         switch (gameMode) {
14562           default:
14563             return;
14564           case IcsExamining:
14565             pauseExamForwardMostMove = forwardMostMove;
14566             pauseExamInvalid = FALSE;
14567             /* fall through */
14568           case IcsObserving:
14569           case IcsPlayingWhite:
14570           case IcsPlayingBlack:
14571             pausing = TRUE;
14572             ModeHighlight();
14573             return;
14574           case PlayFromGameFile:
14575             (void) StopLoadGameTimer();
14576             pausing = TRUE;
14577             ModeHighlight();
14578             break;
14579           case BeginningOfGame:
14580             if (appData.icsActive) return;
14581             /* else fall through */
14582           case MachinePlaysWhite:
14583           case MachinePlaysBlack:
14584           case TwoMachinesPlay:
14585             if (forwardMostMove == 0)
14586               return;           /* don't pause if no one has moved */
14587             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14588                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14589                 if(onMove->pause) {           // thinking engine can be paused
14590                     PauseEngine(onMove);      // do it
14591                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14592                         PauseEngine(onMove->other);
14593                     else
14594                         SendToProgram("easy\n", onMove->other);
14595                     StopClocks();
14596                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14597             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14598                 if(first.pause) {
14599                     PauseEngine(&first);
14600                     StopClocks();
14601                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14602             } else { // human on move, pause pondering by either method
14603                 if(first.pause)
14604                     PauseEngine(&first);
14605                 else if(appData.ponderNextMove)
14606                     SendToProgram("easy\n", &first);
14607                 StopClocks();
14608             }
14609             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14610           case AnalyzeMode:
14611             pausing = TRUE;
14612             ModeHighlight();
14613             break;
14614         }
14615     }
14616 }
14617
14618 void
14619 EditCommentEvent ()
14620 {
14621     char title[MSG_SIZ];
14622
14623     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14624       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14625     } else {
14626       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14627                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14628                parseList[currentMove - 1]);
14629     }
14630
14631     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14632 }
14633
14634
14635 void
14636 EditTagsEvent ()
14637 {
14638     char *tags = PGNTags(&gameInfo);
14639     bookUp = FALSE;
14640     EditTagsPopUp(tags, NULL);
14641     free(tags);
14642 }
14643
14644 void
14645 ToggleSecond ()
14646 {
14647   if(second.analyzing) {
14648     SendToProgram("exit\n", &second);
14649     second.analyzing = FALSE;
14650   } else {
14651     if (second.pr == NoProc) StartChessProgram(&second);
14652     InitChessProgram(&second, FALSE);
14653     FeedMovesToProgram(&second, currentMove);
14654
14655     SendToProgram("analyze\n", &second);
14656     second.analyzing = TRUE;
14657   }
14658 }
14659
14660 /* Toggle ShowThinking */
14661 void
14662 ToggleShowThinking()
14663 {
14664   appData.showThinking = !appData.showThinking;
14665   ShowThinkingEvent();
14666 }
14667
14668 int
14669 AnalyzeModeEvent ()
14670 {
14671     char buf[MSG_SIZ];
14672
14673     if (!first.analysisSupport) {
14674       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14675       DisplayError(buf, 0);
14676       return 0;
14677     }
14678     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14679     if (appData.icsActive) {
14680         if (gameMode != IcsObserving) {
14681           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14682             DisplayError(buf, 0);
14683             /* secure check */
14684             if (appData.icsEngineAnalyze) {
14685                 if (appData.debugMode)
14686                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14687                 ExitAnalyzeMode();
14688                 ModeHighlight();
14689             }
14690             return 0;
14691         }
14692         /* if enable, user wants to disable icsEngineAnalyze */
14693         if (appData.icsEngineAnalyze) {
14694                 ExitAnalyzeMode();
14695                 ModeHighlight();
14696                 return 0;
14697         }
14698         appData.icsEngineAnalyze = TRUE;
14699         if (appData.debugMode)
14700             fprintf(debugFP, "ICS engine analyze starting... \n");
14701     }
14702
14703     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14704     if (appData.noChessProgram || gameMode == AnalyzeMode)
14705       return 0;
14706
14707     if (gameMode != AnalyzeFile) {
14708         if (!appData.icsEngineAnalyze) {
14709                EditGameEvent();
14710                if (gameMode != EditGame) return 0;
14711         }
14712         if (!appData.showThinking) ToggleShowThinking();
14713         ResurrectChessProgram();
14714         SendToProgram("analyze\n", &first);
14715         first.analyzing = TRUE;
14716         /*first.maybeThinking = TRUE;*/
14717         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14718         EngineOutputPopUp();
14719     }
14720     if (!appData.icsEngineAnalyze) {
14721         gameMode = AnalyzeMode;
14722         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14723     }
14724     pausing = FALSE;
14725     ModeHighlight();
14726     SetGameInfo();
14727
14728     StartAnalysisClock();
14729     GetTimeMark(&lastNodeCountTime);
14730     lastNodeCount = 0;
14731     return 1;
14732 }
14733
14734 void
14735 AnalyzeFileEvent ()
14736 {
14737     if (appData.noChessProgram || gameMode == AnalyzeFile)
14738       return;
14739
14740     if (!first.analysisSupport) {
14741       char buf[MSG_SIZ];
14742       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14743       DisplayError(buf, 0);
14744       return;
14745     }
14746
14747     if (gameMode != AnalyzeMode) {
14748         keepInfo = 1; // mere annotating should not alter PGN tags
14749         EditGameEvent();
14750         keepInfo = 0;
14751         if (gameMode != EditGame) return;
14752         if (!appData.showThinking) ToggleShowThinking();
14753         ResurrectChessProgram();
14754         SendToProgram("analyze\n", &first);
14755         first.analyzing = TRUE;
14756         /*first.maybeThinking = TRUE;*/
14757         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14758         EngineOutputPopUp();
14759     }
14760     gameMode = AnalyzeFile;
14761     pausing = FALSE;
14762     ModeHighlight();
14763
14764     StartAnalysisClock();
14765     GetTimeMark(&lastNodeCountTime);
14766     lastNodeCount = 0;
14767     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14768     AnalysisPeriodicEvent(1);
14769 }
14770
14771 void
14772 MachineWhiteEvent ()
14773 {
14774     char buf[MSG_SIZ];
14775     char *bookHit = NULL;
14776
14777     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14778       return;
14779
14780
14781     if (gameMode == PlayFromGameFile ||
14782         gameMode == TwoMachinesPlay  ||
14783         gameMode == Training         ||
14784         gameMode == AnalyzeMode      ||
14785         gameMode == EndOfGame)
14786         EditGameEvent();
14787
14788     if (gameMode == EditPosition)
14789         EditPositionDone(TRUE);
14790
14791     if (!WhiteOnMove(currentMove)) {
14792         DisplayError(_("It is not White's turn"), 0);
14793         return;
14794     }
14795
14796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14797       ExitAnalyzeMode();
14798
14799     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14800         gameMode == AnalyzeFile)
14801         TruncateGame();
14802
14803     ResurrectChessProgram();    /* in case it isn't running */
14804     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14805         gameMode = MachinePlaysWhite;
14806         ResetClocks();
14807     } else
14808     gameMode = MachinePlaysWhite;
14809     pausing = FALSE;
14810     ModeHighlight();
14811     SetGameInfo();
14812     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14813     DisplayTitle(buf);
14814     if (first.sendName) {
14815       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14816       SendToProgram(buf, &first);
14817     }
14818     if (first.sendTime) {
14819       if (first.useColors) {
14820         SendToProgram("black\n", &first); /*gnu kludge*/
14821       }
14822       SendTimeRemaining(&first, TRUE);
14823     }
14824     if (first.useColors) {
14825       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14826     }
14827     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14828     SetMachineThinkingEnables();
14829     first.maybeThinking = TRUE;
14830     StartClocks();
14831     firstMove = FALSE;
14832
14833     if (appData.autoFlipView && !flipView) {
14834       flipView = !flipView;
14835       DrawPosition(FALSE, NULL);
14836       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14837     }
14838
14839     if(bookHit) { // [HGM] book: simulate book reply
14840         static char bookMove[MSG_SIZ]; // a bit generous?
14841
14842         programStats.nodes = programStats.depth = programStats.time =
14843         programStats.score = programStats.got_only_move = 0;
14844         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14845
14846         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14847         strcat(bookMove, bookHit);
14848         HandleMachineMove(bookMove, &first);
14849     }
14850 }
14851
14852 void
14853 MachineBlackEvent ()
14854 {
14855   char buf[MSG_SIZ];
14856   char *bookHit = NULL;
14857
14858     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14859         return;
14860
14861
14862     if (gameMode == PlayFromGameFile ||
14863         gameMode == TwoMachinesPlay  ||
14864         gameMode == Training         ||
14865         gameMode == AnalyzeMode      ||
14866         gameMode == EndOfGame)
14867         EditGameEvent();
14868
14869     if (gameMode == EditPosition)
14870         EditPositionDone(TRUE);
14871
14872     if (WhiteOnMove(currentMove)) {
14873         DisplayError(_("It is not Black's turn"), 0);
14874         return;
14875     }
14876
14877     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14878       ExitAnalyzeMode();
14879
14880     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14881         gameMode == AnalyzeFile)
14882         TruncateGame();
14883
14884     ResurrectChessProgram();    /* in case it isn't running */
14885     gameMode = MachinePlaysBlack;
14886     pausing = FALSE;
14887     ModeHighlight();
14888     SetGameInfo();
14889     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14890     DisplayTitle(buf);
14891     if (first.sendName) {
14892       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14893       SendToProgram(buf, &first);
14894     }
14895     if (first.sendTime) {
14896       if (first.useColors) {
14897         SendToProgram("white\n", &first); /*gnu kludge*/
14898       }
14899       SendTimeRemaining(&first, FALSE);
14900     }
14901     if (first.useColors) {
14902       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14903     }
14904     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14905     SetMachineThinkingEnables();
14906     first.maybeThinking = TRUE;
14907     StartClocks();
14908
14909     if (appData.autoFlipView && flipView) {
14910       flipView = !flipView;
14911       DrawPosition(FALSE, NULL);
14912       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14913     }
14914     if(bookHit) { // [HGM] book: simulate book reply
14915         static char bookMove[MSG_SIZ]; // a bit generous?
14916
14917         programStats.nodes = programStats.depth = programStats.time =
14918         programStats.score = programStats.got_only_move = 0;
14919         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14920
14921         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14922         strcat(bookMove, bookHit);
14923         HandleMachineMove(bookMove, &first);
14924     }
14925 }
14926
14927
14928 void
14929 DisplayTwoMachinesTitle ()
14930 {
14931     char buf[MSG_SIZ];
14932     if (appData.matchGames > 0) {
14933         if(appData.tourneyFile[0]) {
14934           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14935                    gameInfo.white, _("vs."), gameInfo.black,
14936                    nextGame+1, appData.matchGames+1,
14937                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14938         } else
14939         if (first.twoMachinesColor[0] == 'w') {
14940           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14941                    gameInfo.white, _("vs."),  gameInfo.black,
14942                    first.matchWins, second.matchWins,
14943                    matchGame - 1 - (first.matchWins + second.matchWins));
14944         } else {
14945           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14946                    gameInfo.white, _("vs."), gameInfo.black,
14947                    second.matchWins, first.matchWins,
14948                    matchGame - 1 - (first.matchWins + second.matchWins));
14949         }
14950     } else {
14951       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14952     }
14953     DisplayTitle(buf);
14954 }
14955
14956 void
14957 SettingsMenuIfReady ()
14958 {
14959   if (second.lastPing != second.lastPong) {
14960     DisplayMessage("", _("Waiting for second chess program"));
14961     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14962     return;
14963   }
14964   ThawUI();
14965   DisplayMessage("", "");
14966   SettingsPopUp(&second);
14967 }
14968
14969 int
14970 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14971 {
14972     char buf[MSG_SIZ];
14973     if (cps->pr == NoProc) {
14974         StartChessProgram(cps);
14975         if (cps->protocolVersion == 1) {
14976           retry();
14977           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14978         } else {
14979           /* kludge: allow timeout for initial "feature" command */
14980           if(retry != TwoMachinesEventIfReady) FreezeUI();
14981           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14982           DisplayMessage("", buf);
14983           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14984         }
14985         return 1;
14986     }
14987     return 0;
14988 }
14989
14990 void
14991 TwoMachinesEvent P((void))
14992 {
14993     int i;
14994     char buf[MSG_SIZ];
14995     ChessProgramState *onmove;
14996     char *bookHit = NULL;
14997     static int stalling = 0;
14998     TimeMark now;
14999     long wait;
15000
15001     if (appData.noChessProgram) return;
15002
15003     switch (gameMode) {
15004       case TwoMachinesPlay:
15005         return;
15006       case MachinePlaysWhite:
15007       case MachinePlaysBlack:
15008         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15009             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15010             return;
15011         }
15012         /* fall through */
15013       case BeginningOfGame:
15014       case PlayFromGameFile:
15015       case EndOfGame:
15016         EditGameEvent();
15017         if (gameMode != EditGame) return;
15018         break;
15019       case EditPosition:
15020         EditPositionDone(TRUE);
15021         break;
15022       case AnalyzeMode:
15023       case AnalyzeFile:
15024         ExitAnalyzeMode();
15025         break;
15026       case EditGame:
15027       default:
15028         break;
15029     }
15030
15031 //    forwardMostMove = currentMove;
15032     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15033     startingEngine = TRUE;
15034
15035     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15036
15037     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15038     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15039       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15040       return;
15041     }
15042   if(!appData.epd) {
15043     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15044
15045     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15046                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15047         startingEngine = matchMode = FALSE;
15048         DisplayError("second engine does not play this", 0);
15049         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15050         EditGameEvent(); // switch back to EditGame mode
15051         return;
15052     }
15053
15054     if(!stalling) {
15055       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15056       SendToProgram("force\n", &second);
15057       stalling = 1;
15058       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15059       return;
15060     }
15061   }
15062     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15063     if(appData.matchPause>10000 || appData.matchPause<10)
15064                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15065     wait = SubtractTimeMarks(&now, &pauseStart);
15066     if(wait < appData.matchPause) {
15067         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15068         return;
15069     }
15070     // we are now committed to starting the game
15071     stalling = 0;
15072     DisplayMessage("", "");
15073   if(!appData.epd) {
15074     if (startedFromSetupPosition) {
15075         SendBoard(&second, backwardMostMove);
15076     if (appData.debugMode) {
15077         fprintf(debugFP, "Two Machines\n");
15078     }
15079     }
15080     for (i = backwardMostMove; i < forwardMostMove; i++) {
15081         SendMoveToProgram(i, &second);
15082     }
15083   }
15084
15085     gameMode = TwoMachinesPlay;
15086     pausing = startingEngine = FALSE;
15087     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15088     SetGameInfo();
15089     DisplayTwoMachinesTitle();
15090     firstMove = TRUE;
15091     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15092         onmove = &first;
15093     } else {
15094         onmove = &second;
15095     }
15096     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15097     SendToProgram(first.computerString, &first);
15098     if (first.sendName) {
15099       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15100       SendToProgram(buf, &first);
15101     }
15102   if(!appData.epd) {
15103     SendToProgram(second.computerString, &second);
15104     if (second.sendName) {
15105       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15106       SendToProgram(buf, &second);
15107     }
15108   }
15109
15110     ResetClocks();
15111     if (!first.sendTime || !second.sendTime) {
15112         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15113         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15114     }
15115     if (onmove->sendTime) {
15116       if (onmove->useColors) {
15117         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15118       }
15119       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15120     }
15121     if (onmove->useColors) {
15122       SendToProgram(onmove->twoMachinesColor, onmove);
15123     }
15124     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15125 //    SendToProgram("go\n", onmove);
15126     onmove->maybeThinking = TRUE;
15127     SetMachineThinkingEnables();
15128
15129     StartClocks();
15130
15131     if(bookHit) { // [HGM] book: simulate book reply
15132         static char bookMove[MSG_SIZ]; // a bit generous?
15133
15134         programStats.nodes = programStats.depth = programStats.time =
15135         programStats.score = programStats.got_only_move = 0;
15136         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15137
15138         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15139         strcat(bookMove, bookHit);
15140         savedMessage = bookMove; // args for deferred call
15141         savedState = onmove;
15142         ScheduleDelayedEvent(DeferredBookMove, 1);
15143     }
15144 }
15145
15146 void
15147 TrainingEvent ()
15148 {
15149     if (gameMode == Training) {
15150       SetTrainingModeOff();
15151       gameMode = PlayFromGameFile;
15152       DisplayMessage("", _("Training mode off"));
15153     } else {
15154       gameMode = Training;
15155       animateTraining = appData.animate;
15156
15157       /* make sure we are not already at the end of the game */
15158       if (currentMove < forwardMostMove) {
15159         SetTrainingModeOn();
15160         DisplayMessage("", _("Training mode on"));
15161       } else {
15162         gameMode = PlayFromGameFile;
15163         DisplayError(_("Already at end of game"), 0);
15164       }
15165     }
15166     ModeHighlight();
15167 }
15168
15169 void
15170 IcsClientEvent ()
15171 {
15172     if (!appData.icsActive) return;
15173     switch (gameMode) {
15174       case IcsPlayingWhite:
15175       case IcsPlayingBlack:
15176       case IcsObserving:
15177       case IcsIdle:
15178       case BeginningOfGame:
15179       case IcsExamining:
15180         return;
15181
15182       case EditGame:
15183         break;
15184
15185       case EditPosition:
15186         EditPositionDone(TRUE);
15187         break;
15188
15189       case AnalyzeMode:
15190       case AnalyzeFile:
15191         ExitAnalyzeMode();
15192         break;
15193
15194       default:
15195         EditGameEvent();
15196         break;
15197     }
15198
15199     gameMode = IcsIdle;
15200     ModeHighlight();
15201     return;
15202 }
15203
15204 void
15205 EditGameEvent ()
15206 {
15207     int i;
15208
15209     switch (gameMode) {
15210       case Training:
15211         SetTrainingModeOff();
15212         break;
15213       case MachinePlaysWhite:
15214       case MachinePlaysBlack:
15215       case BeginningOfGame:
15216         SendToProgram("force\n", &first);
15217         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15218             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15219                 char buf[MSG_SIZ];
15220                 abortEngineThink = TRUE;
15221                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15222                 SendToProgram(buf, &first);
15223                 DisplayMessage("Aborting engine think", "");
15224                 FreezeUI();
15225             }
15226         }
15227         SetUserThinkingEnables();
15228         break;
15229       case PlayFromGameFile:
15230         (void) StopLoadGameTimer();
15231         if (gameFileFP != NULL) {
15232             gameFileFP = NULL;
15233         }
15234         break;
15235       case EditPosition:
15236         EditPositionDone(TRUE);
15237         break;
15238       case AnalyzeMode:
15239       case AnalyzeFile:
15240         ExitAnalyzeMode();
15241         SendToProgram("force\n", &first);
15242         break;
15243       case TwoMachinesPlay:
15244         GameEnds(EndOfFile, NULL, GE_PLAYER);
15245         ResurrectChessProgram();
15246         SetUserThinkingEnables();
15247         break;
15248       case EndOfGame:
15249         ResurrectChessProgram();
15250         break;
15251       case IcsPlayingBlack:
15252       case IcsPlayingWhite:
15253         DisplayError(_("Warning: You are still playing a game"), 0);
15254         break;
15255       case IcsObserving:
15256         DisplayError(_("Warning: You are still observing a game"), 0);
15257         break;
15258       case IcsExamining:
15259         DisplayError(_("Warning: You are still examining a game"), 0);
15260         break;
15261       case IcsIdle:
15262         break;
15263       case EditGame:
15264       default:
15265         return;
15266     }
15267
15268     pausing = FALSE;
15269     StopClocks();
15270     first.offeredDraw = second.offeredDraw = 0;
15271
15272     if (gameMode == PlayFromGameFile) {
15273         whiteTimeRemaining = timeRemaining[0][currentMove];
15274         blackTimeRemaining = timeRemaining[1][currentMove];
15275         DisplayTitle("");
15276     }
15277
15278     if (gameMode == MachinePlaysWhite ||
15279         gameMode == MachinePlaysBlack ||
15280         gameMode == TwoMachinesPlay ||
15281         gameMode == EndOfGame) {
15282         i = forwardMostMove;
15283         while (i > currentMove) {
15284             SendToProgram("undo\n", &first);
15285             i--;
15286         }
15287         if(!adjustedClock) {
15288         whiteTimeRemaining = timeRemaining[0][currentMove];
15289         blackTimeRemaining = timeRemaining[1][currentMove];
15290         DisplayBothClocks();
15291         }
15292         if (whiteFlag || blackFlag) {
15293             whiteFlag = blackFlag = 0;
15294         }
15295         DisplayTitle("");
15296     }
15297
15298     gameMode = EditGame;
15299     ModeHighlight();
15300     SetGameInfo();
15301 }
15302
15303 void
15304 EditPositionEvent ()
15305 {
15306     int i;
15307     if (gameMode == EditPosition) {
15308         EditGameEvent();
15309         return;
15310     }
15311
15312     EditGameEvent();
15313     if (gameMode != EditGame) return;
15314
15315     gameMode = EditPosition;
15316     ModeHighlight();
15317     SetGameInfo();
15318     CopyBoard(rightsBoard, nullBoard);
15319     if (currentMove > 0)
15320       CopyBoard(boards[0], boards[currentMove]);
15321     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15322       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15323
15324     blackPlaysFirst = !WhiteOnMove(currentMove);
15325     ResetClocks();
15326     currentMove = forwardMostMove = backwardMostMove = 0;
15327     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15328     DisplayMove(-1);
15329     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15330 }
15331
15332 void
15333 ExitAnalyzeMode ()
15334 {
15335     /* [DM] icsEngineAnalyze - possible call from other functions */
15336     if (appData.icsEngineAnalyze) {
15337         appData.icsEngineAnalyze = FALSE;
15338
15339         DisplayMessage("",_("Close ICS engine analyze..."));
15340     }
15341     if (first.analysisSupport && first.analyzing) {
15342       SendToBoth("exit\n");
15343       first.analyzing = second.analyzing = FALSE;
15344     }
15345     thinkOutput[0] = NULLCHAR;
15346 }
15347
15348 void
15349 EditPositionDone (Boolean fakeRights)
15350 {
15351     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15352
15353     startedFromSetupPosition = TRUE;
15354     InitChessProgram(&first, FALSE);
15355     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15356       boards[0][EP_STATUS] = EP_NONE;
15357       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15358       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15359         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15360         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15361       } else boards[0][CASTLING][2] = NoRights;
15362       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15363         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15364         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15365       } else boards[0][CASTLING][5] = NoRights;
15366       if(gameInfo.variant == VariantSChess) {
15367         int i;
15368         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15369           boards[0][VIRGIN][i] = 0;
15370           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15371           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15372         }
15373       }
15374     }
15375     SendToProgram("force\n", &first);
15376     if (blackPlaysFirst) {
15377         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15378         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15379         currentMove = forwardMostMove = backwardMostMove = 1;
15380         CopyBoard(boards[1], boards[0]);
15381     } else {
15382         currentMove = forwardMostMove = backwardMostMove = 0;
15383     }
15384     SendBoard(&first, forwardMostMove);
15385     if (appData.debugMode) {
15386         fprintf(debugFP, "EditPosDone\n");
15387     }
15388     DisplayTitle("");
15389     DisplayMessage("", "");
15390     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15391     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15392     gameMode = EditGame;
15393     ModeHighlight();
15394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15395     ClearHighlights(); /* [AS] */
15396 }
15397
15398 /* Pause for `ms' milliseconds */
15399 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15400 void
15401 TimeDelay (long ms)
15402 {
15403     TimeMark m1, m2;
15404
15405     GetTimeMark(&m1);
15406     do {
15407         GetTimeMark(&m2);
15408     } while (SubtractTimeMarks(&m2, &m1) < ms);
15409 }
15410
15411 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15412 void
15413 SendMultiLineToICS (char *buf)
15414 {
15415     char temp[MSG_SIZ+1], *p;
15416     int len;
15417
15418     len = strlen(buf);
15419     if (len > MSG_SIZ)
15420       len = MSG_SIZ;
15421
15422     strncpy(temp, buf, len);
15423     temp[len] = 0;
15424
15425     p = temp;
15426     while (*p) {
15427         if (*p == '\n' || *p == '\r')
15428           *p = ' ';
15429         ++p;
15430     }
15431
15432     strcat(temp, "\n");
15433     SendToICS(temp);
15434     SendToPlayer(temp, strlen(temp));
15435 }
15436
15437 void
15438 SetWhiteToPlayEvent ()
15439 {
15440     if (gameMode == EditPosition) {
15441         blackPlaysFirst = FALSE;
15442         DisplayBothClocks();    /* works because currentMove is 0 */
15443     } else if (gameMode == IcsExamining) {
15444         SendToICS(ics_prefix);
15445         SendToICS("tomove white\n");
15446     }
15447 }
15448
15449 void
15450 SetBlackToPlayEvent ()
15451 {
15452     if (gameMode == EditPosition) {
15453         blackPlaysFirst = TRUE;
15454         currentMove = 1;        /* kludge */
15455         DisplayBothClocks();
15456         currentMove = 0;
15457     } else if (gameMode == IcsExamining) {
15458         SendToICS(ics_prefix);
15459         SendToICS("tomove black\n");
15460     }
15461 }
15462
15463 void
15464 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15465 {
15466     char buf[MSG_SIZ];
15467     ChessSquare piece = boards[0][y][x];
15468     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15469     static int lastVariant;
15470     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15471
15472     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15473
15474     switch (selection) {
15475       case ClearBoard:
15476         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15477         MarkTargetSquares(1);
15478         CopyBoard(currentBoard, boards[0]);
15479         CopyBoard(menuBoard, initialPosition);
15480         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15481             SendToICS(ics_prefix);
15482             SendToICS("bsetup clear\n");
15483         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15484             SendToICS(ics_prefix);
15485             SendToICS("clearboard\n");
15486         } else {
15487             int nonEmpty = 0;
15488             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15489                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15490                 for (y = 0; y < BOARD_HEIGHT; y++) {
15491                     if (gameMode == IcsExamining) {
15492                         if (boards[currentMove][y][x] != EmptySquare) {
15493                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15494                                     AAA + x, ONE + y);
15495                             SendToICS(buf);
15496                         }
15497                     } else if(boards[0][y][x] != DarkSquare) {
15498                         if(boards[0][y][x] != p) nonEmpty++;
15499                         boards[0][y][x] = p;
15500                     }
15501                 }
15502             }
15503             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15504                 int r;
15505                 for(r = 0; r < BOARD_HEIGHT; r++) {
15506                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15507                     ChessSquare p = menuBoard[r][x];
15508                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15509                   }
15510                 }
15511                 DisplayMessage("Clicking clock again restores position", "");
15512                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15513                 if(!nonEmpty) { // asked to clear an empty board
15514                     CopyBoard(boards[0], menuBoard);
15515                 } else
15516                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15517                     CopyBoard(boards[0], initialPosition);
15518                 } else
15519                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15520                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15521                     CopyBoard(boards[0], erasedBoard);
15522                 } else
15523                     CopyBoard(erasedBoard, currentBoard);
15524
15525             }
15526         }
15527         if (gameMode == EditPosition) {
15528             DrawPosition(FALSE, boards[0]);
15529         }
15530         break;
15531
15532       case WhitePlay:
15533         SetWhiteToPlayEvent();
15534         break;
15535
15536       case BlackPlay:
15537         SetBlackToPlayEvent();
15538         break;
15539
15540       case EmptySquare:
15541         if (gameMode == IcsExamining) {
15542             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15543             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15544             SendToICS(buf);
15545         } else {
15546             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15547                 if(x == BOARD_LEFT-2) {
15548                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15549                     boards[0][y][1] = 0;
15550                 } else
15551                 if(x == BOARD_RGHT+1) {
15552                     if(y >= gameInfo.holdingsSize) break;
15553                     boards[0][y][BOARD_WIDTH-2] = 0;
15554                 } else break;
15555             }
15556             boards[0][y][x] = EmptySquare;
15557             DrawPosition(FALSE, boards[0]);
15558         }
15559         break;
15560
15561       case PromotePiece:
15562         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15563            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15564             selection = (ChessSquare) (PROMOTED(piece));
15565         } else if(piece == EmptySquare) selection = WhiteSilver;
15566         else selection = (ChessSquare)((int)piece - 1);
15567         goto defaultlabel;
15568
15569       case DemotePiece:
15570         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15571            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15572             selection = (ChessSquare) (DEMOTED(piece));
15573         } else if(piece == EmptySquare) selection = BlackSilver;
15574         else selection = (ChessSquare)((int)piece + 1);
15575         goto defaultlabel;
15576
15577       case WhiteQueen:
15578       case BlackQueen:
15579         if(gameInfo.variant == VariantShatranj ||
15580            gameInfo.variant == VariantXiangqi  ||
15581            gameInfo.variant == VariantCourier  ||
15582            gameInfo.variant == VariantASEAN    ||
15583            gameInfo.variant == VariantMakruk     )
15584             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15585         goto defaultlabel;
15586
15587       case WhiteRook:
15588         baseRank = 0;
15589       case BlackRook:
15590         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15591         goto defaultlabel;
15592
15593       case WhiteKing:
15594         baseRank = 0;
15595       case BlackKing:
15596         if(gameInfo.variant == VariantXiangqi)
15597             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15598         if(gameInfo.variant == VariantKnightmate)
15599             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15600         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15601       default:
15602         defaultlabel:
15603         if (gameMode == IcsExamining) {
15604             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15605             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15606                      PieceToChar(selection), AAA + x, ONE + y);
15607             SendToICS(buf);
15608         } else {
15609             rightsBoard[y][x] = hasRights;
15610             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15611                 int n;
15612                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15613                     n = PieceToNumber(selection - BlackPawn);
15614                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15615                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15616                     boards[0][BOARD_HEIGHT-1-n][1]++;
15617                 } else
15618                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15619                     n = PieceToNumber(selection);
15620                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15621                     boards[0][n][BOARD_WIDTH-1] = selection;
15622                     boards[0][n][BOARD_WIDTH-2]++;
15623                 }
15624             } else
15625             boards[0][y][x] = selection;
15626             DrawPosition(TRUE, boards[0]);
15627             ClearHighlights();
15628             fromX = fromY = -1;
15629         }
15630         break;
15631     }
15632 }
15633
15634
15635 void
15636 DropMenuEvent (ChessSquare selection, int x, int y)
15637 {
15638     ChessMove moveType;
15639
15640     switch (gameMode) {
15641       case IcsPlayingWhite:
15642       case MachinePlaysBlack:
15643         if (!WhiteOnMove(currentMove)) {
15644             DisplayMoveError(_("It is Black's turn"));
15645             return;
15646         }
15647         moveType = WhiteDrop;
15648         break;
15649       case IcsPlayingBlack:
15650       case MachinePlaysWhite:
15651         if (WhiteOnMove(currentMove)) {
15652             DisplayMoveError(_("It is White's turn"));
15653             return;
15654         }
15655         moveType = BlackDrop;
15656         break;
15657       case EditGame:
15658         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15659         break;
15660       default:
15661         return;
15662     }
15663
15664     if (moveType == BlackDrop && selection < BlackPawn) {
15665       selection = (ChessSquare) ((int) selection
15666                                  + (int) BlackPawn - (int) WhitePawn);
15667     }
15668     if (boards[currentMove][y][x] != EmptySquare) {
15669         DisplayMoveError(_("That square is occupied"));
15670         return;
15671     }
15672
15673     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15674 }
15675
15676 void
15677 AcceptEvent ()
15678 {
15679     /* Accept a pending offer of any kind from opponent */
15680
15681     if (appData.icsActive) {
15682         SendToICS(ics_prefix);
15683         SendToICS("accept\n");
15684     } else if (cmailMsgLoaded) {
15685         if (currentMove == cmailOldMove &&
15686             commentList[cmailOldMove] != NULL &&
15687             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15688                    "Black offers a draw" : "White offers a draw")) {
15689             TruncateGame();
15690             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15691             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15692         } else {
15693             DisplayError(_("There is no pending offer on this move"), 0);
15694             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15695         }
15696     } else {
15697         /* Not used for offers from chess program */
15698     }
15699 }
15700
15701 void
15702 DeclineEvent ()
15703 {
15704     /* Decline a pending offer of any kind from opponent */
15705
15706     if (appData.icsActive) {
15707         SendToICS(ics_prefix);
15708         SendToICS("decline\n");
15709     } else if (cmailMsgLoaded) {
15710         if (currentMove == cmailOldMove &&
15711             commentList[cmailOldMove] != NULL &&
15712             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15713                    "Black offers a draw" : "White offers a draw")) {
15714 #ifdef NOTDEF
15715             AppendComment(cmailOldMove, "Draw declined", TRUE);
15716             DisplayComment(cmailOldMove - 1, "Draw declined");
15717 #endif /*NOTDEF*/
15718         } else {
15719             DisplayError(_("There is no pending offer on this move"), 0);
15720         }
15721     } else {
15722         /* Not used for offers from chess program */
15723     }
15724 }
15725
15726 void
15727 RematchEvent ()
15728 {
15729     /* Issue ICS rematch command */
15730     if (appData.icsActive) {
15731         SendToICS(ics_prefix);
15732         SendToICS("rematch\n");
15733     }
15734 }
15735
15736 void
15737 CallFlagEvent ()
15738 {
15739     /* Call your opponent's flag (claim a win on time) */
15740     if (appData.icsActive) {
15741         SendToICS(ics_prefix);
15742         SendToICS("flag\n");
15743     } else {
15744         switch (gameMode) {
15745           default:
15746             return;
15747           case MachinePlaysWhite:
15748             if (whiteFlag) {
15749                 if (blackFlag)
15750                   GameEnds(GameIsDrawn, "Both players ran out of time",
15751                            GE_PLAYER);
15752                 else
15753                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15754             } else {
15755                 DisplayError(_("Your opponent is not out of time"), 0);
15756             }
15757             break;
15758           case MachinePlaysBlack:
15759             if (blackFlag) {
15760                 if (whiteFlag)
15761                   GameEnds(GameIsDrawn, "Both players ran out of time",
15762                            GE_PLAYER);
15763                 else
15764                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15765             } else {
15766                 DisplayError(_("Your opponent is not out of time"), 0);
15767             }
15768             break;
15769         }
15770     }
15771 }
15772
15773 void
15774 ClockClick (int which)
15775 {       // [HGM] code moved to back-end from winboard.c
15776         if(which) { // black clock
15777           if (gameMode == EditPosition || gameMode == IcsExamining) {
15778             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15779             SetBlackToPlayEvent();
15780           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15781                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15782           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15783           } else if (shiftKey) {
15784             AdjustClock(which, -1);
15785           } else if (gameMode == IcsPlayingWhite ||
15786                      gameMode == MachinePlaysBlack) {
15787             CallFlagEvent();
15788           }
15789         } else { // white clock
15790           if (gameMode == EditPosition || gameMode == IcsExamining) {
15791             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15792             SetWhiteToPlayEvent();
15793           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15794                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15795           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15796           } else if (shiftKey) {
15797             AdjustClock(which, -1);
15798           } else if (gameMode == IcsPlayingBlack ||
15799                    gameMode == MachinePlaysWhite) {
15800             CallFlagEvent();
15801           }
15802         }
15803 }
15804
15805 void
15806 DrawEvent ()
15807 {
15808     /* Offer draw or accept pending draw offer from opponent */
15809
15810     if (appData.icsActive) {
15811         /* Note: tournament rules require draw offers to be
15812            made after you make your move but before you punch
15813            your clock.  Currently ICS doesn't let you do that;
15814            instead, you immediately punch your clock after making
15815            a move, but you can offer a draw at any time. */
15816
15817         SendToICS(ics_prefix);
15818         SendToICS("draw\n");
15819         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15820     } else if (cmailMsgLoaded) {
15821         if (currentMove == cmailOldMove &&
15822             commentList[cmailOldMove] != NULL &&
15823             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15824                    "Black offers a draw" : "White offers a draw")) {
15825             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15826             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15827         } else if (currentMove == cmailOldMove + 1) {
15828             char *offer = WhiteOnMove(cmailOldMove) ?
15829               "White offers a draw" : "Black offers a draw";
15830             AppendComment(currentMove, offer, TRUE);
15831             DisplayComment(currentMove - 1, offer);
15832             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15833         } else {
15834             DisplayError(_("You must make your move before offering a draw"), 0);
15835             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15836         }
15837     } else if (first.offeredDraw) {
15838         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15839     } else {
15840         if (first.sendDrawOffers) {
15841             SendToProgram("draw\n", &first);
15842             userOfferedDraw = TRUE;
15843         }
15844     }
15845 }
15846
15847 void
15848 AdjournEvent ()
15849 {
15850     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15851
15852     if (appData.icsActive) {
15853         SendToICS(ics_prefix);
15854         SendToICS("adjourn\n");
15855     } else {
15856         /* Currently GNU Chess doesn't offer or accept Adjourns */
15857     }
15858 }
15859
15860
15861 void
15862 AbortEvent ()
15863 {
15864     /* Offer Abort or accept pending Abort offer from opponent */
15865
15866     if (appData.icsActive) {
15867         SendToICS(ics_prefix);
15868         SendToICS("abort\n");
15869     } else {
15870         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15871     }
15872 }
15873
15874 void
15875 ResignEvent ()
15876 {
15877     /* Resign.  You can do this even if it's not your turn. */
15878
15879     if (appData.icsActive) {
15880         SendToICS(ics_prefix);
15881         SendToICS("resign\n");
15882     } else {
15883         switch (gameMode) {
15884           case MachinePlaysWhite:
15885             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15886             break;
15887           case MachinePlaysBlack:
15888             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15889             break;
15890           case EditGame:
15891             if (cmailMsgLoaded) {
15892                 TruncateGame();
15893                 if (WhiteOnMove(cmailOldMove)) {
15894                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15895                 } else {
15896                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15897                 }
15898                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15899             }
15900             break;
15901           default:
15902             break;
15903         }
15904     }
15905 }
15906
15907
15908 void
15909 StopObservingEvent ()
15910 {
15911     /* Stop observing current games */
15912     SendToICS(ics_prefix);
15913     SendToICS("unobserve\n");
15914 }
15915
15916 void
15917 StopExaminingEvent ()
15918 {
15919     /* Stop observing current game */
15920     SendToICS(ics_prefix);
15921     SendToICS("unexamine\n");
15922 }
15923
15924 void
15925 ForwardInner (int target)
15926 {
15927     int limit; int oldSeekGraphUp = seekGraphUp;
15928
15929     if (appData.debugMode)
15930         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15931                 target, currentMove, forwardMostMove);
15932
15933     if (gameMode == EditPosition)
15934       return;
15935
15936     seekGraphUp = FALSE;
15937     MarkTargetSquares(1);
15938     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15939
15940     if (gameMode == PlayFromGameFile && !pausing)
15941       PauseEvent();
15942
15943     if (gameMode == IcsExamining && pausing)
15944       limit = pauseExamForwardMostMove;
15945     else
15946       limit = forwardMostMove;
15947
15948     if (target > limit) target = limit;
15949
15950     if (target > 0 && moveList[target - 1][0]) {
15951         int fromX, fromY, toX, toY;
15952         toX = moveList[target - 1][2] - AAA;
15953         toY = moveList[target - 1][3] - ONE;
15954         if (moveList[target - 1][1] == '@') {
15955             if (appData.highlightLastMove) {
15956                 SetHighlights(-1, -1, toX, toY);
15957             }
15958         } else {
15959             fromX = moveList[target - 1][0] - AAA;
15960             fromY = moveList[target - 1][1] - ONE;
15961             if (target == currentMove + 1) {
15962                 if(moveList[target - 1][4] == ';') { // multi-leg
15963                     killX = moveList[target - 1][5] - AAA;
15964                     killY = moveList[target - 1][6] - ONE;
15965                 }
15966                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15967                 killX = killY = -1;
15968             }
15969             if (appData.highlightLastMove) {
15970                 SetHighlights(fromX, fromY, toX, toY);
15971             }
15972         }
15973     }
15974     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15975         gameMode == Training || gameMode == PlayFromGameFile ||
15976         gameMode == AnalyzeFile) {
15977         while (currentMove < target) {
15978             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15979             SendMoveToProgram(currentMove++, &first);
15980         }
15981     } else {
15982         currentMove = target;
15983     }
15984
15985     if (gameMode == EditGame || gameMode == EndOfGame) {
15986         whiteTimeRemaining = timeRemaining[0][currentMove];
15987         blackTimeRemaining = timeRemaining[1][currentMove];
15988     }
15989     DisplayBothClocks();
15990     DisplayMove(currentMove - 1);
15991     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15992     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15993     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15994         DisplayComment(currentMove - 1, commentList[currentMove]);
15995     }
15996     ClearMap(); // [HGM] exclude: invalidate map
15997 }
15998
15999
16000 void
16001 ForwardEvent ()
16002 {
16003     if (gameMode == IcsExamining && !pausing) {
16004         SendToICS(ics_prefix);
16005         SendToICS("forward\n");
16006     } else {
16007         ForwardInner(currentMove + 1);
16008     }
16009 }
16010
16011 void
16012 ToEndEvent ()
16013 {
16014     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16015         /* to optimze, we temporarily turn off analysis mode while we feed
16016          * the remaining moves to the engine. Otherwise we get analysis output
16017          * after each move.
16018          */
16019         if (first.analysisSupport) {
16020           SendToProgram("exit\nforce\n", &first);
16021           first.analyzing = FALSE;
16022         }
16023     }
16024
16025     if (gameMode == IcsExamining && !pausing) {
16026         SendToICS(ics_prefix);
16027         SendToICS("forward 999999\n");
16028     } else {
16029         ForwardInner(forwardMostMove);
16030     }
16031
16032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16033         /* we have fed all the moves, so reactivate analysis mode */
16034         SendToProgram("analyze\n", &first);
16035         first.analyzing = TRUE;
16036         /*first.maybeThinking = TRUE;*/
16037         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16038     }
16039 }
16040
16041 void
16042 BackwardInner (int target)
16043 {
16044     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16045
16046     if (appData.debugMode)
16047         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16048                 target, currentMove, forwardMostMove);
16049
16050     if (gameMode == EditPosition) return;
16051     seekGraphUp = FALSE;
16052     MarkTargetSquares(1);
16053     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16054     if (currentMove <= backwardMostMove) {
16055         ClearHighlights();
16056         DrawPosition(full_redraw, boards[currentMove]);
16057         return;
16058     }
16059     if (gameMode == PlayFromGameFile && !pausing)
16060       PauseEvent();
16061
16062     if (moveList[target][0]) {
16063         int fromX, fromY, toX, toY;
16064         toX = moveList[target][2] - AAA;
16065         toY = moveList[target][3] - ONE;
16066         if (moveList[target][1] == '@') {
16067             if (appData.highlightLastMove) {
16068                 SetHighlights(-1, -1, toX, toY);
16069             }
16070         } else {
16071             fromX = moveList[target][0] - AAA;
16072             fromY = moveList[target][1] - ONE;
16073             if (target == currentMove - 1) {
16074                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16075             }
16076             if (appData.highlightLastMove) {
16077                 SetHighlights(fromX, fromY, toX, toY);
16078             }
16079         }
16080     }
16081     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16082         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16083         while (currentMove > target) {
16084             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16085                 // null move cannot be undone. Reload program with move history before it.
16086                 int i;
16087                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16088                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16089                 }
16090                 SendBoard(&first, i);
16091               if(second.analyzing) SendBoard(&second, i);
16092                 for(currentMove=i; currentMove<target; currentMove++) {
16093                     SendMoveToProgram(currentMove, &first);
16094                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16095                 }
16096                 break;
16097             }
16098             SendToBoth("undo\n");
16099             currentMove--;
16100         }
16101     } else {
16102         currentMove = target;
16103     }
16104
16105     if (gameMode == EditGame || gameMode == EndOfGame) {
16106         whiteTimeRemaining = timeRemaining[0][currentMove];
16107         blackTimeRemaining = timeRemaining[1][currentMove];
16108     }
16109     DisplayBothClocks();
16110     DisplayMove(currentMove - 1);
16111     DrawPosition(full_redraw, boards[currentMove]);
16112     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16113     // [HGM] PV info: routine tests if comment empty
16114     DisplayComment(currentMove - 1, commentList[currentMove]);
16115     ClearMap(); // [HGM] exclude: invalidate map
16116 }
16117
16118 void
16119 BackwardEvent ()
16120 {
16121     if (gameMode == IcsExamining && !pausing) {
16122         SendToICS(ics_prefix);
16123         SendToICS("backward\n");
16124     } else {
16125         BackwardInner(currentMove - 1);
16126     }
16127 }
16128
16129 void
16130 ToStartEvent ()
16131 {
16132     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16133         /* to optimize, we temporarily turn off analysis mode while we undo
16134          * all the moves. Otherwise we get analysis output after each undo.
16135          */
16136         if (first.analysisSupport) {
16137           SendToProgram("exit\nforce\n", &first);
16138           first.analyzing = FALSE;
16139         }
16140     }
16141
16142     if (gameMode == IcsExamining && !pausing) {
16143         SendToICS(ics_prefix);
16144         SendToICS("backward 999999\n");
16145     } else {
16146         BackwardInner(backwardMostMove);
16147     }
16148
16149     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16150         /* we have fed all the moves, so reactivate analysis mode */
16151         SendToProgram("analyze\n", &first);
16152         first.analyzing = TRUE;
16153         /*first.maybeThinking = TRUE;*/
16154         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16155     }
16156 }
16157
16158 void
16159 ToNrEvent (int to)
16160 {
16161   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16162   if (to >= forwardMostMove) to = forwardMostMove;
16163   if (to <= backwardMostMove) to = backwardMostMove;
16164   if (to < currentMove) {
16165     BackwardInner(to);
16166   } else {
16167     ForwardInner(to);
16168   }
16169 }
16170
16171 void
16172 RevertEvent (Boolean annotate)
16173 {
16174     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16175         return;
16176     }
16177     if (gameMode != IcsExamining) {
16178         DisplayError(_("You are not examining a game"), 0);
16179         return;
16180     }
16181     if (pausing) {
16182         DisplayError(_("You can't revert while pausing"), 0);
16183         return;
16184     }
16185     SendToICS(ics_prefix);
16186     SendToICS("revert\n");
16187 }
16188
16189 void
16190 RetractMoveEvent ()
16191 {
16192     switch (gameMode) {
16193       case MachinePlaysWhite:
16194       case MachinePlaysBlack:
16195         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16196             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16197             return;
16198         }
16199         if (forwardMostMove < 2) return;
16200         currentMove = forwardMostMove = forwardMostMove - 2;
16201         whiteTimeRemaining = timeRemaining[0][currentMove];
16202         blackTimeRemaining = timeRemaining[1][currentMove];
16203         DisplayBothClocks();
16204         DisplayMove(currentMove - 1);
16205         ClearHighlights();/*!! could figure this out*/
16206         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16207         SendToProgram("remove\n", &first);
16208         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16209         break;
16210
16211       case BeginningOfGame:
16212       default:
16213         break;
16214
16215       case IcsPlayingWhite:
16216       case IcsPlayingBlack:
16217         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16218             SendToICS(ics_prefix);
16219             SendToICS("takeback 2\n");
16220         } else {
16221             SendToICS(ics_prefix);
16222             SendToICS("takeback 1\n");
16223         }
16224         break;
16225     }
16226 }
16227
16228 void
16229 MoveNowEvent ()
16230 {
16231     ChessProgramState *cps;
16232
16233     switch (gameMode) {
16234       case MachinePlaysWhite:
16235         if (!WhiteOnMove(forwardMostMove)) {
16236             DisplayError(_("It is your turn"), 0);
16237             return;
16238         }
16239         cps = &first;
16240         break;
16241       case MachinePlaysBlack:
16242         if (WhiteOnMove(forwardMostMove)) {
16243             DisplayError(_("It is your turn"), 0);
16244             return;
16245         }
16246         cps = &first;
16247         break;
16248       case TwoMachinesPlay:
16249         if (WhiteOnMove(forwardMostMove) ==
16250             (first.twoMachinesColor[0] == 'w')) {
16251             cps = &first;
16252         } else {
16253             cps = &second;
16254         }
16255         break;
16256       case BeginningOfGame:
16257       default:
16258         return;
16259     }
16260     SendToProgram("?\n", cps);
16261 }
16262
16263 void
16264 TruncateGameEvent ()
16265 {
16266     EditGameEvent();
16267     if (gameMode != EditGame) return;
16268     TruncateGame();
16269 }
16270
16271 void
16272 TruncateGame ()
16273 {
16274     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16275     if (forwardMostMove > currentMove) {
16276         if (gameInfo.resultDetails != NULL) {
16277             free(gameInfo.resultDetails);
16278             gameInfo.resultDetails = NULL;
16279             gameInfo.result = GameUnfinished;
16280         }
16281         forwardMostMove = currentMove;
16282         HistorySet(parseList, backwardMostMove, forwardMostMove,
16283                    currentMove-1);
16284     }
16285 }
16286
16287 void
16288 HintEvent ()
16289 {
16290     if (appData.noChessProgram) return;
16291     switch (gameMode) {
16292       case MachinePlaysWhite:
16293         if (WhiteOnMove(forwardMostMove)) {
16294             DisplayError(_("Wait until your turn."), 0);
16295             return;
16296         }
16297         break;
16298       case BeginningOfGame:
16299       case MachinePlaysBlack:
16300         if (!WhiteOnMove(forwardMostMove)) {
16301             DisplayError(_("Wait until your turn."), 0);
16302             return;
16303         }
16304         break;
16305       default:
16306         DisplayError(_("No hint available"), 0);
16307         return;
16308     }
16309     SendToProgram("hint\n", &first);
16310     hintRequested = TRUE;
16311 }
16312
16313 int
16314 SaveSelected (FILE *g, int dummy, char *dummy2)
16315 {
16316     ListGame * lg = (ListGame *) gameList.head;
16317     int nItem, cnt=0;
16318     FILE *f;
16319
16320     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16321         DisplayError(_("Game list not loaded or empty"), 0);
16322         return 0;
16323     }
16324
16325     creatingBook = TRUE; // suppresses stuff during load game
16326
16327     /* Get list size */
16328     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16329         if(lg->position >= 0) { // selected?
16330             LoadGame(f, nItem, "", TRUE);
16331             SaveGamePGN2(g); // leaves g open
16332             cnt++; DoEvents();
16333         }
16334         lg = (ListGame *) lg->node.succ;
16335     }
16336
16337     fclose(g);
16338     creatingBook = FALSE;
16339
16340     return cnt;
16341 }
16342
16343 void
16344 CreateBookEvent ()
16345 {
16346     ListGame * lg = (ListGame *) gameList.head;
16347     FILE *f, *g;
16348     int nItem;
16349     static int secondTime = FALSE;
16350
16351     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16352         DisplayError(_("Game list not loaded or empty"), 0);
16353         return;
16354     }
16355
16356     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16357         fclose(g);
16358         secondTime++;
16359         DisplayNote(_("Book file exists! Try again for overwrite."));
16360         return;
16361     }
16362
16363     creatingBook = TRUE;
16364     secondTime = FALSE;
16365
16366     /* Get list size */
16367     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16368         if(lg->position >= 0) {
16369             LoadGame(f, nItem, "", TRUE);
16370             AddGameToBook(TRUE);
16371             DoEvents();
16372         }
16373         lg = (ListGame *) lg->node.succ;
16374     }
16375
16376     creatingBook = FALSE;
16377     FlushBook();
16378 }
16379
16380 void
16381 BookEvent ()
16382 {
16383     if (appData.noChessProgram) return;
16384     switch (gameMode) {
16385       case MachinePlaysWhite:
16386         if (WhiteOnMove(forwardMostMove)) {
16387             DisplayError(_("Wait until your turn."), 0);
16388             return;
16389         }
16390         break;
16391       case BeginningOfGame:
16392       case MachinePlaysBlack:
16393         if (!WhiteOnMove(forwardMostMove)) {
16394             DisplayError(_("Wait until your turn."), 0);
16395             return;
16396         }
16397         break;
16398       case EditPosition:
16399         EditPositionDone(TRUE);
16400         break;
16401       case TwoMachinesPlay:
16402         return;
16403       default:
16404         break;
16405     }
16406     SendToProgram("bk\n", &first);
16407     bookOutput[0] = NULLCHAR;
16408     bookRequested = TRUE;
16409 }
16410
16411 void
16412 AboutGameEvent ()
16413 {
16414     char *tags = PGNTags(&gameInfo);
16415     TagsPopUp(tags, CmailMsg());
16416     free(tags);
16417 }
16418
16419 /* end button procedures */
16420
16421 void
16422 PrintPosition (FILE *fp, int move)
16423 {
16424     int i, j;
16425
16426     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16427         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16428             char c = PieceToChar(boards[move][i][j]);
16429             fputc(c == '?' ? '.' : c, fp);
16430             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16431         }
16432     }
16433     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16434       fprintf(fp, "white to play\n");
16435     else
16436       fprintf(fp, "black to play\n");
16437 }
16438
16439 void
16440 PrintOpponents (FILE *fp)
16441 {
16442     if (gameInfo.white != NULL) {
16443         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16444     } else {
16445         fprintf(fp, "\n");
16446     }
16447 }
16448
16449 /* Find last component of program's own name, using some heuristics */
16450 void
16451 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16452 {
16453     char *p, *q, c;
16454     int local = (strcmp(host, "localhost") == 0);
16455     while (!local && (p = strchr(prog, ';')) != NULL) {
16456         p++;
16457         while (*p == ' ') p++;
16458         prog = p;
16459     }
16460     if (*prog == '"' || *prog == '\'') {
16461         q = strchr(prog + 1, *prog);
16462     } else {
16463         q = strchr(prog, ' ');
16464     }
16465     if (q == NULL) q = prog + strlen(prog);
16466     p = q;
16467     while (p >= prog && *p != '/' && *p != '\\') p--;
16468     p++;
16469     if(p == prog && *p == '"') p++;
16470     c = *q; *q = 0;
16471     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16472     memcpy(buf, p, q - p);
16473     buf[q - p] = NULLCHAR;
16474     if (!local) {
16475         strcat(buf, "@");
16476         strcat(buf, host);
16477     }
16478 }
16479
16480 char *
16481 TimeControlTagValue ()
16482 {
16483     char buf[MSG_SIZ];
16484     if (!appData.clockMode) {
16485       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16486     } else if (movesPerSession > 0) {
16487       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16488     } else if (timeIncrement == 0) {
16489       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16490     } else {
16491       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16492     }
16493     return StrSave(buf);
16494 }
16495
16496 void
16497 SetGameInfo ()
16498 {
16499     /* This routine is used only for certain modes */
16500     VariantClass v = gameInfo.variant;
16501     ChessMove r = GameUnfinished;
16502     char *p = NULL;
16503
16504     if(keepInfo) return;
16505
16506     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16507         r = gameInfo.result;
16508         p = gameInfo.resultDetails;
16509         gameInfo.resultDetails = NULL;
16510     }
16511     ClearGameInfo(&gameInfo);
16512     gameInfo.variant = v;
16513
16514     switch (gameMode) {
16515       case MachinePlaysWhite:
16516         gameInfo.event = StrSave( appData.pgnEventHeader );
16517         gameInfo.site = StrSave(HostName());
16518         gameInfo.date = PGNDate();
16519         gameInfo.round = StrSave("-");
16520         gameInfo.white = StrSave(first.tidy);
16521         gameInfo.black = StrSave(UserName());
16522         gameInfo.timeControl = TimeControlTagValue();
16523         break;
16524
16525       case MachinePlaysBlack:
16526         gameInfo.event = StrSave( appData.pgnEventHeader );
16527         gameInfo.site = StrSave(HostName());
16528         gameInfo.date = PGNDate();
16529         gameInfo.round = StrSave("-");
16530         gameInfo.white = StrSave(UserName());
16531         gameInfo.black = StrSave(first.tidy);
16532         gameInfo.timeControl = TimeControlTagValue();
16533         break;
16534
16535       case TwoMachinesPlay:
16536         gameInfo.event = StrSave( appData.pgnEventHeader );
16537         gameInfo.site = StrSave(HostName());
16538         gameInfo.date = PGNDate();
16539         if (roundNr > 0) {
16540             char buf[MSG_SIZ];
16541             snprintf(buf, MSG_SIZ, "%d", roundNr);
16542             gameInfo.round = StrSave(buf);
16543         } else {
16544             gameInfo.round = StrSave("-");
16545         }
16546         if (first.twoMachinesColor[0] == 'w') {
16547             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16548             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16549         } else {
16550             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16551             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16552         }
16553         gameInfo.timeControl = TimeControlTagValue();
16554         break;
16555
16556       case EditGame:
16557         gameInfo.event = StrSave("Edited game");
16558         gameInfo.site = StrSave(HostName());
16559         gameInfo.date = PGNDate();
16560         gameInfo.round = StrSave("-");
16561         gameInfo.white = StrSave("-");
16562         gameInfo.black = StrSave("-");
16563         gameInfo.result = r;
16564         gameInfo.resultDetails = p;
16565         break;
16566
16567       case EditPosition:
16568         gameInfo.event = StrSave("Edited position");
16569         gameInfo.site = StrSave(HostName());
16570         gameInfo.date = PGNDate();
16571         gameInfo.round = StrSave("-");
16572         gameInfo.white = StrSave("-");
16573         gameInfo.black = StrSave("-");
16574         break;
16575
16576       case IcsPlayingWhite:
16577       case IcsPlayingBlack:
16578       case IcsObserving:
16579       case IcsExamining:
16580         break;
16581
16582       case PlayFromGameFile:
16583         gameInfo.event = StrSave("Game from non-PGN file");
16584         gameInfo.site = StrSave(HostName());
16585         gameInfo.date = PGNDate();
16586         gameInfo.round = StrSave("-");
16587         gameInfo.white = StrSave("?");
16588         gameInfo.black = StrSave("?");
16589         break;
16590
16591       default:
16592         break;
16593     }
16594 }
16595
16596 void
16597 ReplaceComment (int index, char *text)
16598 {
16599     int len;
16600     char *p;
16601     float score;
16602
16603     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16604        pvInfoList[index-1].depth == len &&
16605        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16606        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16607     while (*text == '\n') text++;
16608     len = strlen(text);
16609     while (len > 0 && text[len - 1] == '\n') len--;
16610
16611     if (commentList[index] != NULL)
16612       free(commentList[index]);
16613
16614     if (len == 0) {
16615         commentList[index] = NULL;
16616         return;
16617     }
16618   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16619       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16620       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16621     commentList[index] = (char *) malloc(len + 2);
16622     strncpy(commentList[index], text, len);
16623     commentList[index][len] = '\n';
16624     commentList[index][len + 1] = NULLCHAR;
16625   } else {
16626     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16627     char *p;
16628     commentList[index] = (char *) malloc(len + 7);
16629     safeStrCpy(commentList[index], "{\n", 3);
16630     safeStrCpy(commentList[index]+2, text, len+1);
16631     commentList[index][len+2] = NULLCHAR;
16632     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16633     strcat(commentList[index], "\n}\n");
16634   }
16635 }
16636
16637 void
16638 CrushCRs (char *text)
16639 {
16640   char *p = text;
16641   char *q = text;
16642   char ch;
16643
16644   do {
16645     ch = *p++;
16646     if (ch == '\r') continue;
16647     *q++ = ch;
16648   } while (ch != '\0');
16649 }
16650
16651 void
16652 AppendComment (int index, char *text, Boolean addBraces)
16653 /* addBraces  tells if we should add {} */
16654 {
16655     int oldlen, len;
16656     char *old;
16657
16658 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16659     if(addBraces == 3) addBraces = 0; else // force appending literally
16660     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16661
16662     CrushCRs(text);
16663     while (*text == '\n') text++;
16664     len = strlen(text);
16665     while (len > 0 && text[len - 1] == '\n') len--;
16666     text[len] = NULLCHAR;
16667
16668     if (len == 0) return;
16669
16670     if (commentList[index] != NULL) {
16671       Boolean addClosingBrace = addBraces;
16672         old = commentList[index];
16673         oldlen = strlen(old);
16674         while(commentList[index][oldlen-1] ==  '\n')
16675           commentList[index][--oldlen] = NULLCHAR;
16676         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16677         safeStrCpy(commentList[index], old, oldlen + len + 6);
16678         free(old);
16679         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16680         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16681           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16682           while (*text == '\n') { text++; len--; }
16683           commentList[index][--oldlen] = NULLCHAR;
16684       }
16685         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16686         else          strcat(commentList[index], "\n");
16687         strcat(commentList[index], text);
16688         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16689         else          strcat(commentList[index], "\n");
16690     } else {
16691         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16692         if(addBraces)
16693           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16694         else commentList[index][0] = NULLCHAR;
16695         strcat(commentList[index], text);
16696         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16697         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16698     }
16699 }
16700
16701 static char *
16702 FindStr (char * text, char * sub_text)
16703 {
16704     char * result = strstr( text, sub_text );
16705
16706     if( result != NULL ) {
16707         result += strlen( sub_text );
16708     }
16709
16710     return result;
16711 }
16712
16713 /* [AS] Try to extract PV info from PGN comment */
16714 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16715 char *
16716 GetInfoFromComment (int index, char * text)
16717 {
16718     char * sep = text, *p;
16719
16720     if( text != NULL && index > 0 ) {
16721         int score = 0;
16722         int depth = 0;
16723         int time = -1, sec = 0, deci;
16724         char * s_eval = FindStr( text, "[%eval " );
16725         char * s_emt = FindStr( text, "[%emt " );
16726 #if 0
16727         if( s_eval != NULL || s_emt != NULL ) {
16728 #else
16729         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16730 #endif
16731             /* New style */
16732             char delim;
16733
16734             if( s_eval != NULL ) {
16735                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16736                     return text;
16737                 }
16738
16739                 if( delim != ']' ) {
16740                     return text;
16741                 }
16742             }
16743
16744             if( s_emt != NULL ) {
16745             }
16746                 return text;
16747         }
16748         else {
16749             /* We expect something like: [+|-]nnn.nn/dd */
16750             int score_lo = 0;
16751
16752             if(*text != '{') return text; // [HGM] braces: must be normal comment
16753
16754             sep = strchr( text, '/' );
16755             if( sep == NULL || sep < (text+4) ) {
16756                 return text;
16757             }
16758
16759             p = text;
16760             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16761             if(p[1] == '(') { // comment starts with PV
16762                p = strchr(p, ')'); // locate end of PV
16763                if(p == NULL || sep < p+5) return text;
16764                // at this point we have something like "{(.*) +0.23/6 ..."
16765                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16766                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16767                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16768             }
16769             time = -1; sec = -1; deci = -1;
16770             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16771                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16772                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16773                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16774                 return text;
16775             }
16776
16777             if( score_lo < 0 || score_lo >= 100 ) {
16778                 return text;
16779             }
16780
16781             if(sec >= 0) time = 600*time + 10*sec; else
16782             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16783
16784             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16785
16786             /* [HGM] PV time: now locate end of PV info */
16787             while( *++sep >= '0' && *sep <= '9'); // strip depth
16788             if(time >= 0)
16789             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16790             if(sec >= 0)
16791             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16792             if(deci >= 0)
16793             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16794             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16795         }
16796
16797         if( depth <= 0 ) {
16798             return text;
16799         }
16800
16801         if( time < 0 ) {
16802             time = -1;
16803         }
16804
16805         pvInfoList[index-1].depth = depth;
16806         pvInfoList[index-1].score = score;
16807         pvInfoList[index-1].time  = 10*time; // centi-sec
16808         if(*sep == '}') *sep = 0; else *--sep = '{';
16809         if(p != text) {
16810             while(*p++ = *sep++)
16811                                 ;
16812             sep = text;
16813         } // squeeze out space between PV and comment, and return both
16814     }
16815     return sep;
16816 }
16817
16818 void
16819 SendToProgram (char *message, ChessProgramState *cps)
16820 {
16821     int count, outCount, error;
16822     char buf[MSG_SIZ];
16823
16824     if (cps->pr == NoProc) return;
16825     Attention(cps);
16826
16827     if (appData.debugMode) {
16828         TimeMark now;
16829         GetTimeMark(&now);
16830         fprintf(debugFP, "%ld >%-6s: %s",
16831                 SubtractTimeMarks(&now, &programStartTime),
16832                 cps->which, message);
16833         if(serverFP)
16834             fprintf(serverFP, "%ld >%-6s: %s",
16835                 SubtractTimeMarks(&now, &programStartTime),
16836                 cps->which, message), fflush(serverFP);
16837     }
16838
16839     count = strlen(message);
16840     outCount = OutputToProcess(cps->pr, message, count, &error);
16841     if (outCount < count && !exiting
16842                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16843       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16844       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16845         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16846             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16847                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16848                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16849                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16850             } else {
16851                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16852                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16853                 gameInfo.result = res;
16854             }
16855             gameInfo.resultDetails = StrSave(buf);
16856         }
16857         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16858         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16859     }
16860 }
16861
16862 void
16863 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16864 {
16865     char *end_str;
16866     char buf[MSG_SIZ];
16867     ChessProgramState *cps = (ChessProgramState *)closure;
16868
16869     if (isr != cps->isr) return; /* Killed intentionally */
16870     if (count <= 0) {
16871         if (count == 0) {
16872             RemoveInputSource(cps->isr);
16873             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16874                     _(cps->which), cps->program);
16875             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16876             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16877                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16878                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16879                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16880                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16881                 } else {
16882                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16883                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16884                     gameInfo.result = res;
16885                 }
16886                 gameInfo.resultDetails = StrSave(buf);
16887             }
16888             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16889             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16890         } else {
16891             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16892                     _(cps->which), cps->program);
16893             RemoveInputSource(cps->isr);
16894
16895             /* [AS] Program is misbehaving badly... kill it */
16896             if( count == -2 ) {
16897                 DestroyChildProcess( cps->pr, 9 );
16898                 cps->pr = NoProc;
16899             }
16900
16901             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16902         }
16903         return;
16904     }
16905
16906     if ((end_str = strchr(message, '\r')) != NULL)
16907       *end_str = NULLCHAR;
16908     if ((end_str = strchr(message, '\n')) != NULL)
16909       *end_str = NULLCHAR;
16910
16911     if (appData.debugMode) {
16912         TimeMark now; int print = 1;
16913         char *quote = ""; char c; int i;
16914
16915         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16916                 char start = message[0];
16917                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16918                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16919                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16920                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16921                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16922                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16923                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16924                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16925                    sscanf(message, "hint: %c", &c)!=1 &&
16926                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16927                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16928                     print = (appData.engineComments >= 2);
16929                 }
16930                 message[0] = start; // restore original message
16931         }
16932         if(print) {
16933                 GetTimeMark(&now);
16934                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16935                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16936                         quote,
16937                         message);
16938                 if(serverFP)
16939                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16940                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16941                         quote,
16942                         message), fflush(serverFP);
16943         }
16944     }
16945
16946     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16947     if (appData.icsEngineAnalyze) {
16948         if (strstr(message, "whisper") != NULL ||
16949              strstr(message, "kibitz") != NULL ||
16950             strstr(message, "tellics") != NULL) return;
16951     }
16952
16953     HandleMachineMove(message, cps);
16954 }
16955
16956
16957 void
16958 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16959 {
16960     char buf[MSG_SIZ];
16961     int seconds;
16962
16963     if( timeControl_2 > 0 ) {
16964         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16965             tc = timeControl_2;
16966         }
16967     }
16968     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16969     inc /= cps->timeOdds;
16970     st  /= cps->timeOdds;
16971
16972     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16973
16974     if (st > 0) {
16975       /* Set exact time per move, normally using st command */
16976       if (cps->stKludge) {
16977         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16978         seconds = st % 60;
16979         if (seconds == 0) {
16980           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16981         } else {
16982           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16983         }
16984       } else {
16985         snprintf(buf, MSG_SIZ, "st %d\n", st);
16986       }
16987     } else {
16988       /* Set conventional or incremental time control, using level command */
16989       if (seconds == 0) {
16990         /* Note old gnuchess bug -- minutes:seconds used to not work.
16991            Fixed in later versions, but still avoid :seconds
16992            when seconds is 0. */
16993         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16994       } else {
16995         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16996                  seconds, inc/1000.);
16997       }
16998     }
16999     SendToProgram(buf, cps);
17000
17001     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17002     /* Orthogonally, limit search to given depth */
17003     if (sd > 0) {
17004       if (cps->sdKludge) {
17005         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17006       } else {
17007         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17008       }
17009       SendToProgram(buf, cps);
17010     }
17011
17012     if(cps->nps >= 0) { /* [HGM] nps */
17013         if(cps->supportsNPS == FALSE)
17014           cps->nps = -1; // don't use if engine explicitly says not supported!
17015         else {
17016           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17017           SendToProgram(buf, cps);
17018         }
17019     }
17020 }
17021
17022 ChessProgramState *
17023 WhitePlayer ()
17024 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17025 {
17026     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17027        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17028         return &second;
17029     return &first;
17030 }
17031
17032 void
17033 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17034 {
17035     char message[MSG_SIZ];
17036     long time, otime;
17037
17038     /* Note: this routine must be called when the clocks are stopped
17039        or when they have *just* been set or switched; otherwise
17040        it will be off by the time since the current tick started.
17041     */
17042     if (machineWhite) {
17043         time = whiteTimeRemaining / 10;
17044         otime = blackTimeRemaining / 10;
17045     } else {
17046         time = blackTimeRemaining / 10;
17047         otime = whiteTimeRemaining / 10;
17048     }
17049     /* [HGM] translate opponent's time by time-odds factor */
17050     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17051
17052     if (time <= 0) time = 1;
17053     if (otime <= 0) otime = 1;
17054
17055     snprintf(message, MSG_SIZ, "time %ld\n", time);
17056     SendToProgram(message, cps);
17057
17058     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17059     SendToProgram(message, cps);
17060 }
17061
17062 char *
17063 EngineDefinedVariant (ChessProgramState *cps, int n)
17064 {   // return name of n-th unknown variant that engine supports
17065     static char buf[MSG_SIZ];
17066     char *p, *s = cps->variants;
17067     if(!s) return NULL;
17068     do { // parse string from variants feature
17069       VariantClass v;
17070         p = strchr(s, ',');
17071         if(p) *p = NULLCHAR;
17072       v = StringToVariant(s);
17073       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17074         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17075             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17076                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17077                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17078                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17079             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17080         }
17081         if(p) *p++ = ',';
17082         if(n < 0) return buf;
17083     } while(s = p);
17084     return NULL;
17085 }
17086
17087 int
17088 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17089 {
17090   char buf[MSG_SIZ];
17091   int len = strlen(name);
17092   int val;
17093
17094   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17095     (*p) += len + 1;
17096     sscanf(*p, "%d", &val);
17097     *loc = (val != 0);
17098     while (**p && **p != ' ')
17099       (*p)++;
17100     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17101     SendToProgram(buf, cps);
17102     return TRUE;
17103   }
17104   return FALSE;
17105 }
17106
17107 int
17108 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17109 {
17110   char buf[MSG_SIZ];
17111   int len = strlen(name);
17112   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17113     (*p) += len + 1;
17114     sscanf(*p, "%d", loc);
17115     while (**p && **p != ' ') (*p)++;
17116     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17117     SendToProgram(buf, cps);
17118     return TRUE;
17119   }
17120   return FALSE;
17121 }
17122
17123 int
17124 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17125 {
17126   char buf[MSG_SIZ];
17127   int len = strlen(name);
17128   if (strncmp((*p), name, len) == 0
17129       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17130     (*p) += len + 2;
17131     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17132     sscanf(*p, "%[^\"]", *loc);
17133     while (**p && **p != '\"') (*p)++;
17134     if (**p == '\"') (*p)++;
17135     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17136     SendToProgram(buf, cps);
17137     return TRUE;
17138   }
17139   return FALSE;
17140 }
17141
17142 int
17143 ParseOption (Option *opt, ChessProgramState *cps)
17144 // [HGM] options: process the string that defines an engine option, and determine
17145 // name, type, default value, and allowed value range
17146 {
17147         char *p, *q, buf[MSG_SIZ];
17148         int n, min = (-1)<<31, max = 1<<31, def;
17149
17150         opt->target = &opt->value;   // OK for spin/slider and checkbox
17151         if(p = strstr(opt->name, " -spin ")) {
17152             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17153             if(max < min) max = min; // enforce consistency
17154             if(def < min) def = min;
17155             if(def > max) def = max;
17156             opt->value = def;
17157             opt->min = min;
17158             opt->max = max;
17159             opt->type = Spin;
17160         } else if((p = strstr(opt->name, " -slider "))) {
17161             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17162             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17163             if(max < min) max = min; // enforce consistency
17164             if(def < min) def = min;
17165             if(def > max) def = max;
17166             opt->value = def;
17167             opt->min = min;
17168             opt->max = max;
17169             opt->type = Spin; // Slider;
17170         } else if((p = strstr(opt->name, " -string "))) {
17171             opt->textValue = p+9;
17172             opt->type = TextBox;
17173             opt->target = &opt->textValue;
17174         } else if((p = strstr(opt->name, " -file "))) {
17175             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17176             opt->target = opt->textValue = p+7;
17177             opt->type = FileName; // FileName;
17178             opt->target = &opt->textValue;
17179         } else if((p = strstr(opt->name, " -path "))) {
17180             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17181             opt->target = opt->textValue = p+7;
17182             opt->type = PathName; // PathName;
17183             opt->target = &opt->textValue;
17184         } else if(p = strstr(opt->name, " -check ")) {
17185             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17186             opt->value = (def != 0);
17187             opt->type = CheckBox;
17188         } else if(p = strstr(opt->name, " -combo ")) {
17189             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17190             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17191             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17192             opt->value = n = 0;
17193             while(q = StrStr(q, " /// ")) {
17194                 n++; *q = 0;    // count choices, and null-terminate each of them
17195                 q += 5;
17196                 if(*q == '*') { // remember default, which is marked with * prefix
17197                     q++;
17198                     opt->value = n;
17199                 }
17200                 cps->comboList[cps->comboCnt++] = q;
17201             }
17202             cps->comboList[cps->comboCnt++] = NULL;
17203             opt->max = n + 1;
17204             opt->type = ComboBox;
17205         } else if(p = strstr(opt->name, " -button")) {
17206             opt->type = Button;
17207         } else if(p = strstr(opt->name, " -save")) {
17208             opt->type = SaveButton;
17209         } else return FALSE;
17210         *p = 0; // terminate option name
17211         // now look if the command-line options define a setting for this engine option.
17212         if(cps->optionSettings && cps->optionSettings[0])
17213             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17214         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17215           snprintf(buf, MSG_SIZ, "option %s", p);
17216                 if(p = strstr(buf, ",")) *p = 0;
17217                 if(q = strchr(buf, '=')) switch(opt->type) {
17218                     case ComboBox:
17219                         for(n=0; n<opt->max; n++)
17220                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17221                         break;
17222                     case TextBox:
17223                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17224                         break;
17225                     case Spin:
17226                     case CheckBox:
17227                         opt->value = atoi(q+1);
17228                     default:
17229                         break;
17230                 }
17231                 strcat(buf, "\n");
17232                 SendToProgram(buf, cps);
17233         }
17234         return TRUE;
17235 }
17236
17237 void
17238 FeatureDone (ChessProgramState *cps, int val)
17239 {
17240   DelayedEventCallback cb = GetDelayedEvent();
17241   if ((cb == InitBackEnd3 && cps == &first) ||
17242       (cb == SettingsMenuIfReady && cps == &second) ||
17243       (cb == LoadEngine) ||
17244       (cb == TwoMachinesEventIfReady)) {
17245     CancelDelayedEvent();
17246     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17247   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17248   cps->initDone = val;
17249   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17250 }
17251
17252 /* Parse feature command from engine */
17253 void
17254 ParseFeatures (char *args, ChessProgramState *cps)
17255 {
17256   char *p = args;
17257   char *q = NULL;
17258   int val;
17259   char buf[MSG_SIZ];
17260
17261   for (;;) {
17262     while (*p == ' ') p++;
17263     if (*p == NULLCHAR) return;
17264
17265     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17266     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17267     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17268     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17269     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17270     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17271     if (BoolFeature(&p, "reuse", &val, cps)) {
17272       /* Engine can disable reuse, but can't enable it if user said no */
17273       if (!val) cps->reuse = FALSE;
17274       continue;
17275     }
17276     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17277     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17278       if (gameMode == TwoMachinesPlay) {
17279         DisplayTwoMachinesTitle();
17280       } else {
17281         DisplayTitle("");
17282       }
17283       continue;
17284     }
17285     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17286     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17287     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17288     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17289     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17290     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17291     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17292     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17293     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17294     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17295     if (IntFeature(&p, "done", &val, cps)) {
17296       FeatureDone(cps, val);
17297       continue;
17298     }
17299     /* Added by Tord: */
17300     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17301     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17302     /* End of additions by Tord */
17303
17304     /* [HGM] added features: */
17305     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17306     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17307     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17308     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17309     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17310     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17311     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17312     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17313         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17314         FREE(cps->option[cps->nrOptions].name);
17315         cps->option[cps->nrOptions].name = q; q = NULL;
17316         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17317           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17318             SendToProgram(buf, cps);
17319             continue;
17320         }
17321         if(cps->nrOptions >= MAX_OPTIONS) {
17322             cps->nrOptions--;
17323             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17324             DisplayError(buf, 0);
17325         }
17326         continue;
17327     }
17328     /* End of additions by HGM */
17329
17330     /* unknown feature: complain and skip */
17331     q = p;
17332     while (*q && *q != '=') q++;
17333     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17334     SendToProgram(buf, cps);
17335     p = q;
17336     if (*p == '=') {
17337       p++;
17338       if (*p == '\"') {
17339         p++;
17340         while (*p && *p != '\"') p++;
17341         if (*p == '\"') p++;
17342       } else {
17343         while (*p && *p != ' ') p++;
17344       }
17345     }
17346   }
17347
17348 }
17349
17350 void
17351 PeriodicUpdatesEvent (int newState)
17352 {
17353     if (newState == appData.periodicUpdates)
17354       return;
17355
17356     appData.periodicUpdates=newState;
17357
17358     /* Display type changes, so update it now */
17359 //    DisplayAnalysis();
17360
17361     /* Get the ball rolling again... */
17362     if (newState) {
17363         AnalysisPeriodicEvent(1);
17364         StartAnalysisClock();
17365     }
17366 }
17367
17368 void
17369 PonderNextMoveEvent (int newState)
17370 {
17371     if (newState == appData.ponderNextMove) return;
17372     if (gameMode == EditPosition) EditPositionDone(TRUE);
17373     if (newState) {
17374         SendToProgram("hard\n", &first);
17375         if (gameMode == TwoMachinesPlay) {
17376             SendToProgram("hard\n", &second);
17377         }
17378     } else {
17379         SendToProgram("easy\n", &first);
17380         thinkOutput[0] = NULLCHAR;
17381         if (gameMode == TwoMachinesPlay) {
17382             SendToProgram("easy\n", &second);
17383         }
17384     }
17385     appData.ponderNextMove = newState;
17386 }
17387
17388 void
17389 NewSettingEvent (int option, int *feature, char *command, int value)
17390 {
17391     char buf[MSG_SIZ];
17392
17393     if (gameMode == EditPosition) EditPositionDone(TRUE);
17394     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17395     if(feature == NULL || *feature) SendToProgram(buf, &first);
17396     if (gameMode == TwoMachinesPlay) {
17397         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17398     }
17399 }
17400
17401 void
17402 ShowThinkingEvent ()
17403 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17404 {
17405     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17406     int newState = appData.showThinking
17407         // [HGM] thinking: other features now need thinking output as well
17408         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17409
17410     if (oldState == newState) return;
17411     oldState = newState;
17412     if (gameMode == EditPosition) EditPositionDone(TRUE);
17413     if (oldState) {
17414         SendToProgram("post\n", &first);
17415         if (gameMode == TwoMachinesPlay) {
17416             SendToProgram("post\n", &second);
17417         }
17418     } else {
17419         SendToProgram("nopost\n", &first);
17420         thinkOutput[0] = NULLCHAR;
17421         if (gameMode == TwoMachinesPlay) {
17422             SendToProgram("nopost\n", &second);
17423         }
17424     }
17425 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17426 }
17427
17428 void
17429 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17430 {
17431   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17432   if (pr == NoProc) return;
17433   AskQuestion(title, question, replyPrefix, pr);
17434 }
17435
17436 void
17437 TypeInEvent (char firstChar)
17438 {
17439     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17440         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17441         gameMode == AnalyzeMode || gameMode == EditGame ||
17442         gameMode == EditPosition || gameMode == IcsExamining ||
17443         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17444         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17445                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17446                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17447         gameMode == Training) PopUpMoveDialog(firstChar);
17448 }
17449
17450 void
17451 TypeInDoneEvent (char *move)
17452 {
17453         Board board;
17454         int n, fromX, fromY, toX, toY;
17455         char promoChar;
17456         ChessMove moveType;
17457
17458         // [HGM] FENedit
17459         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17460                 EditPositionPasteFEN(move);
17461                 return;
17462         }
17463         // [HGM] movenum: allow move number to be typed in any mode
17464         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17465           ToNrEvent(2*n-1);
17466           return;
17467         }
17468         // undocumented kludge: allow command-line option to be typed in!
17469         // (potentially fatal, and does not implement the effect of the option.)
17470         // should only be used for options that are values on which future decisions will be made,
17471         // and definitely not on options that would be used during initialization.
17472         if(strstr(move, "!!! -") == move) {
17473             ParseArgsFromString(move+4);
17474             return;
17475         }
17476
17477       if (gameMode != EditGame && currentMove != forwardMostMove &&
17478         gameMode != Training) {
17479         DisplayMoveError(_("Displayed move is not current"));
17480       } else {
17481         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17482           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17483         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17484         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17485           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17486           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17487         } else {
17488           DisplayMoveError(_("Could not parse move"));
17489         }
17490       }
17491 }
17492
17493 void
17494 DisplayMove (int moveNumber)
17495 {
17496     char message[MSG_SIZ];
17497     char res[MSG_SIZ];
17498     char cpThinkOutput[MSG_SIZ];
17499
17500     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17501
17502     if (moveNumber == forwardMostMove - 1 ||
17503         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17504
17505         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17506
17507         if (strchr(cpThinkOutput, '\n')) {
17508             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17509         }
17510     } else {
17511         *cpThinkOutput = NULLCHAR;
17512     }
17513
17514     /* [AS] Hide thinking from human user */
17515     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17516         *cpThinkOutput = NULLCHAR;
17517         if( thinkOutput[0] != NULLCHAR ) {
17518             int i;
17519
17520             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17521                 cpThinkOutput[i] = '.';
17522             }
17523             cpThinkOutput[i] = NULLCHAR;
17524             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17525         }
17526     }
17527
17528     if (moveNumber == forwardMostMove - 1 &&
17529         gameInfo.resultDetails != NULL) {
17530         if (gameInfo.resultDetails[0] == NULLCHAR) {
17531           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17532         } else {
17533           snprintf(res, MSG_SIZ, " {%s} %s",
17534                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17535         }
17536     } else {
17537         res[0] = NULLCHAR;
17538     }
17539
17540     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17541         DisplayMessage(res, cpThinkOutput);
17542     } else {
17543       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17544                 WhiteOnMove(moveNumber) ? " " : ".. ",
17545                 parseList[moveNumber], res);
17546         DisplayMessage(message, cpThinkOutput);
17547     }
17548 }
17549
17550 void
17551 DisplayComment (int moveNumber, char *text)
17552 {
17553     char title[MSG_SIZ];
17554
17555     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17556       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17557     } else {
17558       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17559               WhiteOnMove(moveNumber) ? " " : ".. ",
17560               parseList[moveNumber]);
17561     }
17562     if (text != NULL && (appData.autoDisplayComment || commentUp))
17563         CommentPopUp(title, text);
17564 }
17565
17566 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17567  * might be busy thinking or pondering.  It can be omitted if your
17568  * gnuchess is configured to stop thinking immediately on any user
17569  * input.  However, that gnuchess feature depends on the FIONREAD
17570  * ioctl, which does not work properly on some flavors of Unix.
17571  */
17572 void
17573 Attention (ChessProgramState *cps)
17574 {
17575 #if ATTENTION
17576     if (!cps->useSigint) return;
17577     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17578     switch (gameMode) {
17579       case MachinePlaysWhite:
17580       case MachinePlaysBlack:
17581       case TwoMachinesPlay:
17582       case IcsPlayingWhite:
17583       case IcsPlayingBlack:
17584       case AnalyzeMode:
17585       case AnalyzeFile:
17586         /* Skip if we know it isn't thinking */
17587         if (!cps->maybeThinking) return;
17588         if (appData.debugMode)
17589           fprintf(debugFP, "Interrupting %s\n", cps->which);
17590         InterruptChildProcess(cps->pr);
17591         cps->maybeThinking = FALSE;
17592         break;
17593       default:
17594         break;
17595     }
17596 #endif /*ATTENTION*/
17597 }
17598
17599 int
17600 CheckFlags ()
17601 {
17602     if (whiteTimeRemaining <= 0) {
17603         if (!whiteFlag) {
17604             whiteFlag = TRUE;
17605             if (appData.icsActive) {
17606                 if (appData.autoCallFlag &&
17607                     gameMode == IcsPlayingBlack && !blackFlag) {
17608                   SendToICS(ics_prefix);
17609                   SendToICS("flag\n");
17610                 }
17611             } else {
17612                 if (blackFlag) {
17613                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17614                 } else {
17615                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17616                     if (appData.autoCallFlag) {
17617                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17618                         return TRUE;
17619                     }
17620                 }
17621             }
17622         }
17623     }
17624     if (blackTimeRemaining <= 0) {
17625         if (!blackFlag) {
17626             blackFlag = TRUE;
17627             if (appData.icsActive) {
17628                 if (appData.autoCallFlag &&
17629                     gameMode == IcsPlayingWhite && !whiteFlag) {
17630                   SendToICS(ics_prefix);
17631                   SendToICS("flag\n");
17632                 }
17633             } else {
17634                 if (whiteFlag) {
17635                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17636                 } else {
17637                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17638                     if (appData.autoCallFlag) {
17639                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17640                         return TRUE;
17641                     }
17642                 }
17643             }
17644         }
17645     }
17646     return FALSE;
17647 }
17648
17649 void
17650 CheckTimeControl ()
17651 {
17652     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17653         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17654
17655     /*
17656      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17657      */
17658     if ( !WhiteOnMove(forwardMostMove) ) {
17659         /* White made time control */
17660         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17661         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17662         /* [HGM] time odds: correct new time quota for time odds! */
17663                                             / WhitePlayer()->timeOdds;
17664         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17665     } else {
17666         lastBlack -= blackTimeRemaining;
17667         /* Black made time control */
17668         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17669                                             / WhitePlayer()->other->timeOdds;
17670         lastWhite = whiteTimeRemaining;
17671     }
17672 }
17673
17674 void
17675 DisplayBothClocks ()
17676 {
17677     int wom = gameMode == EditPosition ?
17678       !blackPlaysFirst : WhiteOnMove(currentMove);
17679     DisplayWhiteClock(whiteTimeRemaining, wom);
17680     DisplayBlackClock(blackTimeRemaining, !wom);
17681 }
17682
17683
17684 /* Timekeeping seems to be a portability nightmare.  I think everyone
17685    has ftime(), but I'm really not sure, so I'm including some ifdefs
17686    to use other calls if you don't.  Clocks will be less accurate if
17687    you have neither ftime nor gettimeofday.
17688 */
17689
17690 /* VS 2008 requires the #include outside of the function */
17691 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17692 #include <sys/timeb.h>
17693 #endif
17694
17695 /* Get the current time as a TimeMark */
17696 void
17697 GetTimeMark (TimeMark *tm)
17698 {
17699 #if HAVE_GETTIMEOFDAY
17700
17701     struct timeval timeVal;
17702     struct timezone timeZone;
17703
17704     gettimeofday(&timeVal, &timeZone);
17705     tm->sec = (long) timeVal.tv_sec;
17706     tm->ms = (int) (timeVal.tv_usec / 1000L);
17707
17708 #else /*!HAVE_GETTIMEOFDAY*/
17709 #if HAVE_FTIME
17710
17711 // include <sys/timeb.h> / moved to just above start of function
17712     struct timeb timeB;
17713
17714     ftime(&timeB);
17715     tm->sec = (long) timeB.time;
17716     tm->ms = (int) timeB.millitm;
17717
17718 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17719     tm->sec = (long) time(NULL);
17720     tm->ms = 0;
17721 #endif
17722 #endif
17723 }
17724
17725 /* Return the difference in milliseconds between two
17726    time marks.  We assume the difference will fit in a long!
17727 */
17728 long
17729 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17730 {
17731     return 1000L*(tm2->sec - tm1->sec) +
17732            (long) (tm2->ms - tm1->ms);
17733 }
17734
17735
17736 /*
17737  * Code to manage the game clocks.
17738  *
17739  * In tournament play, black starts the clock and then white makes a move.
17740  * We give the human user a slight advantage if he is playing white---the
17741  * clocks don't run until he makes his first move, so it takes zero time.
17742  * Also, we don't account for network lag, so we could get out of sync
17743  * with GNU Chess's clock -- but then, referees are always right.
17744  */
17745
17746 static TimeMark tickStartTM;
17747 static long intendedTickLength;
17748
17749 long
17750 NextTickLength (long timeRemaining)
17751 {
17752     long nominalTickLength, nextTickLength;
17753
17754     if (timeRemaining > 0L && timeRemaining <= 10000L)
17755       nominalTickLength = 100L;
17756     else
17757       nominalTickLength = 1000L;
17758     nextTickLength = timeRemaining % nominalTickLength;
17759     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17760
17761     return nextTickLength;
17762 }
17763
17764 /* Adjust clock one minute up or down */
17765 void
17766 AdjustClock (Boolean which, int dir)
17767 {
17768     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17769     if(which) blackTimeRemaining += 60000*dir;
17770     else      whiteTimeRemaining += 60000*dir;
17771     DisplayBothClocks();
17772     adjustedClock = TRUE;
17773 }
17774
17775 /* Stop clocks and reset to a fresh time control */
17776 void
17777 ResetClocks ()
17778 {
17779     (void) StopClockTimer();
17780     if (appData.icsActive) {
17781         whiteTimeRemaining = blackTimeRemaining = 0;
17782     } else if (searchTime) {
17783         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17784         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17785     } else { /* [HGM] correct new time quote for time odds */
17786         whiteTC = blackTC = fullTimeControlString;
17787         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17788         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17789     }
17790     if (whiteFlag || blackFlag) {
17791         DisplayTitle("");
17792         whiteFlag = blackFlag = FALSE;
17793     }
17794     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17795     DisplayBothClocks();
17796     adjustedClock = FALSE;
17797 }
17798
17799 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17800
17801 /* Decrement running clock by amount of time that has passed */
17802 void
17803 DecrementClocks ()
17804 {
17805     long timeRemaining;
17806     long lastTickLength, fudge;
17807     TimeMark now;
17808
17809     if (!appData.clockMode) return;
17810     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17811
17812     GetTimeMark(&now);
17813
17814     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17815
17816     /* Fudge if we woke up a little too soon */
17817     fudge = intendedTickLength - lastTickLength;
17818     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17819
17820     if (WhiteOnMove(forwardMostMove)) {
17821         if(whiteNPS >= 0) lastTickLength = 0;
17822         timeRemaining = whiteTimeRemaining -= lastTickLength;
17823         if(timeRemaining < 0 && !appData.icsActive) {
17824             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17825             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17826                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17827                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17828             }
17829         }
17830         DisplayWhiteClock(whiteTimeRemaining - fudge,
17831                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17832     } else {
17833         if(blackNPS >= 0) lastTickLength = 0;
17834         timeRemaining = blackTimeRemaining -= lastTickLength;
17835         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17836             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17837             if(suddenDeath) {
17838                 blackStartMove = forwardMostMove;
17839                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17840             }
17841         }
17842         DisplayBlackClock(blackTimeRemaining - fudge,
17843                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17844     }
17845     if (CheckFlags()) return;
17846
17847     if(twoBoards) { // count down secondary board's clocks as well
17848         activePartnerTime -= lastTickLength;
17849         partnerUp = 1;
17850         if(activePartner == 'W')
17851             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17852         else
17853             DisplayBlackClock(activePartnerTime, TRUE);
17854         partnerUp = 0;
17855     }
17856
17857     tickStartTM = now;
17858     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17859     StartClockTimer(intendedTickLength);
17860
17861     /* if the time remaining has fallen below the alarm threshold, sound the
17862      * alarm. if the alarm has sounded and (due to a takeback or time control
17863      * with increment) the time remaining has increased to a level above the
17864      * threshold, reset the alarm so it can sound again.
17865      */
17866
17867     if (appData.icsActive && appData.icsAlarm) {
17868
17869         /* make sure we are dealing with the user's clock */
17870         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17871                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17872            )) return;
17873
17874         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17875             alarmSounded = FALSE;
17876         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17877             PlayAlarmSound();
17878             alarmSounded = TRUE;
17879         }
17880     }
17881 }
17882
17883
17884 /* A player has just moved, so stop the previously running
17885    clock and (if in clock mode) start the other one.
17886    We redisplay both clocks in case we're in ICS mode, because
17887    ICS gives us an update to both clocks after every move.
17888    Note that this routine is called *after* forwardMostMove
17889    is updated, so the last fractional tick must be subtracted
17890    from the color that is *not* on move now.
17891 */
17892 void
17893 SwitchClocks (int newMoveNr)
17894 {
17895     long lastTickLength;
17896     TimeMark now;
17897     int flagged = FALSE;
17898
17899     GetTimeMark(&now);
17900
17901     if (StopClockTimer() && appData.clockMode) {
17902         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17903         if (!WhiteOnMove(forwardMostMove)) {
17904             if(blackNPS >= 0) lastTickLength = 0;
17905             blackTimeRemaining -= lastTickLength;
17906            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17907 //         if(pvInfoList[forwardMostMove].time == -1)
17908                  pvInfoList[forwardMostMove].time =               // use GUI time
17909                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17910         } else {
17911            if(whiteNPS >= 0) lastTickLength = 0;
17912            whiteTimeRemaining -= lastTickLength;
17913            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17914 //         if(pvInfoList[forwardMostMove].time == -1)
17915                  pvInfoList[forwardMostMove].time =
17916                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17917         }
17918         flagged = CheckFlags();
17919     }
17920     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17921     CheckTimeControl();
17922
17923     if (flagged || !appData.clockMode) return;
17924
17925     switch (gameMode) {
17926       case MachinePlaysBlack:
17927       case MachinePlaysWhite:
17928       case BeginningOfGame:
17929         if (pausing) return;
17930         break;
17931
17932       case EditGame:
17933       case PlayFromGameFile:
17934       case IcsExamining:
17935         return;
17936
17937       default:
17938         break;
17939     }
17940
17941     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17942         if(WhiteOnMove(forwardMostMove))
17943              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17944         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17945     }
17946
17947     tickStartTM = now;
17948     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17949       whiteTimeRemaining : blackTimeRemaining);
17950     StartClockTimer(intendedTickLength);
17951 }
17952
17953
17954 /* Stop both clocks */
17955 void
17956 StopClocks ()
17957 {
17958     long lastTickLength;
17959     TimeMark now;
17960
17961     if (!StopClockTimer()) return;
17962     if (!appData.clockMode) return;
17963
17964     GetTimeMark(&now);
17965
17966     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17967     if (WhiteOnMove(forwardMostMove)) {
17968         if(whiteNPS >= 0) lastTickLength = 0;
17969         whiteTimeRemaining -= lastTickLength;
17970         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17971     } else {
17972         if(blackNPS >= 0) lastTickLength = 0;
17973         blackTimeRemaining -= lastTickLength;
17974         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17975     }
17976     CheckFlags();
17977 }
17978
17979 /* Start clock of player on move.  Time may have been reset, so
17980    if clock is already running, stop and restart it. */
17981 void
17982 StartClocks ()
17983 {
17984     (void) StopClockTimer(); /* in case it was running already */
17985     DisplayBothClocks();
17986     if (CheckFlags()) return;
17987
17988     if (!appData.clockMode) return;
17989     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17990
17991     GetTimeMark(&tickStartTM);
17992     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17993       whiteTimeRemaining : blackTimeRemaining);
17994
17995    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17996     whiteNPS = blackNPS = -1;
17997     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17998        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17999         whiteNPS = first.nps;
18000     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18001        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18002         blackNPS = first.nps;
18003     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18004         whiteNPS = second.nps;
18005     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18006         blackNPS = second.nps;
18007     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18008
18009     StartClockTimer(intendedTickLength);
18010 }
18011
18012 char *
18013 TimeString (long ms)
18014 {
18015     long second, minute, hour, day;
18016     char *sign = "";
18017     static char buf[32];
18018
18019     if (ms > 0 && ms <= 9900) {
18020       /* convert milliseconds to tenths, rounding up */
18021       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18022
18023       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18024       return buf;
18025     }
18026
18027     /* convert milliseconds to seconds, rounding up */
18028     /* use floating point to avoid strangeness of integer division
18029        with negative dividends on many machines */
18030     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18031
18032     if (second < 0) {
18033         sign = "-";
18034         second = -second;
18035     }
18036
18037     day = second / (60 * 60 * 24);
18038     second = second % (60 * 60 * 24);
18039     hour = second / (60 * 60);
18040     second = second % (60 * 60);
18041     minute = second / 60;
18042     second = second % 60;
18043
18044     if (day > 0)
18045       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18046               sign, day, hour, minute, second);
18047     else if (hour > 0)
18048       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18049     else
18050       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18051
18052     return buf;
18053 }
18054
18055
18056 /*
18057  * This is necessary because some C libraries aren't ANSI C compliant yet.
18058  */
18059 char *
18060 StrStr (char *string, char *match)
18061 {
18062     int i, length;
18063
18064     length = strlen(match);
18065
18066     for (i = strlen(string) - length; i >= 0; i--, string++)
18067       if (!strncmp(match, string, length))
18068         return string;
18069
18070     return NULL;
18071 }
18072
18073 char *
18074 StrCaseStr (char *string, char *match)
18075 {
18076     int i, j, length;
18077
18078     length = strlen(match);
18079
18080     for (i = strlen(string) - length; i >= 0; i--, string++) {
18081         for (j = 0; j < length; j++) {
18082             if (ToLower(match[j]) != ToLower(string[j]))
18083               break;
18084         }
18085         if (j == length) return string;
18086     }
18087
18088     return NULL;
18089 }
18090
18091 #ifndef _amigados
18092 int
18093 StrCaseCmp (char *s1, char *s2)
18094 {
18095     char c1, c2;
18096
18097     for (;;) {
18098         c1 = ToLower(*s1++);
18099         c2 = ToLower(*s2++);
18100         if (c1 > c2) return 1;
18101         if (c1 < c2) return -1;
18102         if (c1 == NULLCHAR) return 0;
18103     }
18104 }
18105
18106
18107 int
18108 ToLower (int c)
18109 {
18110     return isupper(c) ? tolower(c) : c;
18111 }
18112
18113
18114 int
18115 ToUpper (int c)
18116 {
18117     return islower(c) ? toupper(c) : c;
18118 }
18119 #endif /* !_amigados    */
18120
18121 char *
18122 StrSave (char *s)
18123 {
18124   char *ret;
18125
18126   if ((ret = (char *) malloc(strlen(s) + 1)))
18127     {
18128       safeStrCpy(ret, s, strlen(s)+1);
18129     }
18130   return ret;
18131 }
18132
18133 char *
18134 StrSavePtr (char *s, char **savePtr)
18135 {
18136     if (*savePtr) {
18137         free(*savePtr);
18138     }
18139     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18140       safeStrCpy(*savePtr, s, strlen(s)+1);
18141     }
18142     return(*savePtr);
18143 }
18144
18145 char *
18146 PGNDate ()
18147 {
18148     time_t clock;
18149     struct tm *tm;
18150     char buf[MSG_SIZ];
18151
18152     clock = time((time_t *)NULL);
18153     tm = localtime(&clock);
18154     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18155             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18156     return StrSave(buf);
18157 }
18158
18159
18160 char *
18161 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18162 {
18163     int i, j, fromX, fromY, toX, toY;
18164     int whiteToPlay, haveRights = nrCastlingRights;
18165     char buf[MSG_SIZ];
18166     char *p, *q;
18167     int emptycount;
18168     ChessSquare piece;
18169
18170     whiteToPlay = (gameMode == EditPosition) ?
18171       !blackPlaysFirst : (move % 2 == 0);
18172     p = buf;
18173
18174     /* Piece placement data */
18175     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18176         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18177         emptycount = 0;
18178         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18179             if (boards[move][i][j] == EmptySquare) {
18180                 emptycount++;
18181             } else { ChessSquare piece = boards[move][i][j];
18182                 if (emptycount > 0) {
18183                     if(emptycount<10) /* [HGM] can be >= 10 */
18184                         *p++ = '0' + emptycount;
18185                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18186                     emptycount = 0;
18187                 }
18188                 if(PieceToChar(piece) == '+') {
18189                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18190                     *p++ = '+';
18191                     piece = (ChessSquare)(CHUDEMOTED(piece));
18192                 }
18193                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18194                 if(*p = PieceSuffix(piece)) p++;
18195                 if(p[-1] == '~') {
18196                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18197                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18198                     *p++ = '~';
18199                 }
18200             }
18201         }
18202         if (emptycount > 0) {
18203             if(emptycount<10) /* [HGM] can be >= 10 */
18204                 *p++ = '0' + emptycount;
18205             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18206             emptycount = 0;
18207         }
18208         *p++ = '/';
18209     }
18210     *(p - 1) = ' ';
18211
18212     /* [HGM] print Crazyhouse or Shogi holdings */
18213     if( gameInfo.holdingsWidth ) {
18214         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18215         q = p;
18216         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18217             piece = boards[move][i][BOARD_WIDTH-1];
18218             if( piece != EmptySquare )
18219               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18220                   *p++ = PieceToChar(piece);
18221         }
18222         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18223             piece = boards[move][BOARD_HEIGHT-i-1][0];
18224             if( piece != EmptySquare )
18225               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18226                   *p++ = PieceToChar(piece);
18227         }
18228
18229         if( q == p ) *p++ = '-';
18230         *p++ = ']';
18231         *p++ = ' ';
18232     }
18233
18234     /* Active color */
18235     *p++ = whiteToPlay ? 'w' : 'b';
18236     *p++ = ' ';
18237
18238   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18239     haveRights = 0; q = p;
18240     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18241       piece = boards[move][0][i];
18242       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18243         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18244       }
18245     }
18246     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18247       piece = boards[move][BOARD_HEIGHT-1][i];
18248       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18249         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18250       }
18251     }
18252     if(p == q) *p++ = '-';
18253     *p++ = ' ';
18254   }
18255
18256   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18257     while(*p++ = *q++)
18258                       ;
18259     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18260   } else {
18261   if(haveRights) {
18262      int handW=0, handB=0;
18263      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18264         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18265         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18266      }
18267      q = p;
18268      if(appData.fischerCastling) {
18269         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18270            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18271                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18272         } else {
18273        /* [HGM] write directly from rights */
18274            if(boards[move][CASTLING][2] != NoRights &&
18275               boards[move][CASTLING][0] != NoRights   )
18276                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18277            if(boards[move][CASTLING][2] != NoRights &&
18278               boards[move][CASTLING][1] != NoRights   )
18279                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18280         }
18281         if(handB) {
18282            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18283                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18284         } else {
18285            if(boards[move][CASTLING][5] != NoRights &&
18286               boards[move][CASTLING][3] != NoRights   )
18287                 *p++ = boards[move][CASTLING][3] + AAA;
18288            if(boards[move][CASTLING][5] != NoRights &&
18289               boards[move][CASTLING][4] != NoRights   )
18290                 *p++ = boards[move][CASTLING][4] + AAA;
18291         }
18292      } else {
18293
18294         /* [HGM] write true castling rights */
18295         if( nrCastlingRights == 6 ) {
18296             int q, k=0;
18297             if(boards[move][CASTLING][0] != NoRights &&
18298                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18299             q = (boards[move][CASTLING][1] != NoRights &&
18300                  boards[move][CASTLING][2] != NoRights  );
18301             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18302                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18303                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18304                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18305             }
18306             if(q) *p++ = 'Q';
18307             k = 0;
18308             if(boards[move][CASTLING][3] != NoRights &&
18309                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18310             q = (boards[move][CASTLING][4] != NoRights &&
18311                  boards[move][CASTLING][5] != NoRights  );
18312             if(handB) {
18313                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18314                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18315                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18316             }
18317             if(q) *p++ = 'q';
18318         }
18319      }
18320      if (q == p) *p++ = '-'; /* No castling rights */
18321      *p++ = ' ';
18322   }
18323
18324   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18325      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18326      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18327     /* En passant target square */
18328     if (move > backwardMostMove) {
18329         fromX = moveList[move - 1][0] - AAA;
18330         fromY = moveList[move - 1][1] - ONE;
18331         toX = moveList[move - 1][2] - AAA;
18332         toY = moveList[move - 1][3] - ONE;
18333         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18334             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18335             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18336             fromX == toX) {
18337             /* 2-square pawn move just happened */
18338             *p++ = toX + AAA;
18339             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18340         } else {
18341             *p++ = '-';
18342         }
18343     } else if(move == backwardMostMove) {
18344         // [HGM] perhaps we should always do it like this, and forget the above?
18345         if((signed char)boards[move][EP_STATUS] >= 0) {
18346             *p++ = boards[move][EP_STATUS] + AAA;
18347             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18348         } else {
18349             *p++ = '-';
18350         }
18351     } else {
18352         *p++ = '-';
18353     }
18354     *p++ = ' ';
18355   }
18356   }
18357
18358     if(moveCounts)
18359     {   int i = 0, j=move;
18360
18361         /* [HGM] find reversible plies */
18362         if (appData.debugMode) { int k;
18363             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18364             for(k=backwardMostMove; k<=forwardMostMove; k++)
18365                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18366
18367         }
18368
18369         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18370         if( j == backwardMostMove ) i += initialRulePlies;
18371         sprintf(p, "%d ", i);
18372         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18373
18374         /* Fullmove number */
18375         sprintf(p, "%d", (move / 2) + 1);
18376     } else *--p = NULLCHAR;
18377
18378     return StrSave(buf);
18379 }
18380
18381 Boolean
18382 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18383 {
18384     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18385     char *p, c;
18386     int emptycount, virgin[BOARD_FILES];
18387     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18388
18389     p = fen;
18390
18391     /* Piece placement data */
18392     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18393         j = 0;
18394         for (;;) {
18395             if (*p == '/' || *p == ' ' || *p == '[' ) {
18396                 if(j > w) w = j;
18397                 emptycount = gameInfo.boardWidth - j;
18398                 while (emptycount--)
18399                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18400                 if (*p == '/') p++;
18401                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18402                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18403                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18404                     }
18405                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18406                 }
18407                 break;
18408 #if(BOARD_FILES >= 10)*0
18409             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18410                 p++; emptycount=10;
18411                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18412                 while (emptycount--)
18413                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18414 #endif
18415             } else if (*p == '*') {
18416                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18417             } else if (isdigit(*p)) {
18418                 emptycount = *p++ - '0';
18419                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18420                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18421                 while (emptycount--)
18422                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18423             } else if (*p == '<') {
18424                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18425                 else if (i != 0 || !shuffle) return FALSE;
18426                 p++;
18427             } else if (shuffle && *p == '>') {
18428                 p++; // for now ignore closing shuffle range, and assume rank-end
18429             } else if (*p == '?') {
18430                 if (j >= gameInfo.boardWidth) return FALSE;
18431                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18432                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18433             } else if (*p == '+' || isalpha(*p)) {
18434                 char *q, *s = SUFFIXES;
18435                 if (j >= gameInfo.boardWidth) return FALSE;
18436                 if(*p=='+') {
18437                     char c = *++p;
18438                     if(q = strchr(s, p[1])) p++;
18439                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18440                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18441                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18442                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18443                 } else {
18444                     char c = *p++;
18445                     if(q = strchr(s, *p)) p++;
18446                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18447                 }
18448
18449                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18450                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18451                     piece = (ChessSquare) (PROMOTED(piece));
18452                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18453                     p++;
18454                 }
18455                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18456                 if(piece == king) wKingRank = i;
18457                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18458             } else {
18459                 return FALSE;
18460             }
18461         }
18462     }
18463     while (*p == '/' || *p == ' ') p++;
18464
18465     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18466
18467     /* [HGM] by default clear Crazyhouse holdings, if present */
18468     if(gameInfo.holdingsWidth) {
18469        for(i=0; i<BOARD_HEIGHT; i++) {
18470            board[i][0]             = EmptySquare; /* black holdings */
18471            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18472            board[i][1]             = (ChessSquare) 0; /* black counts */
18473            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18474        }
18475     }
18476
18477     /* [HGM] look for Crazyhouse holdings here */
18478     while(*p==' ') p++;
18479     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18480         int swap=0, wcnt=0, bcnt=0;
18481         if(*p == '[') p++;
18482         if(*p == '<') swap++, p++;
18483         if(*p == '-' ) p++; /* empty holdings */ else {
18484             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18485             /* if we would allow FEN reading to set board size, we would   */
18486             /* have to add holdings and shift the board read so far here   */
18487             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18488                 p++;
18489                 if((int) piece >= (int) BlackPawn ) {
18490                     i = (int)piece - (int)BlackPawn;
18491                     i = PieceToNumber((ChessSquare)i);
18492                     if( i >= gameInfo.holdingsSize ) return FALSE;
18493                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18494                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18495                     bcnt++;
18496                 } else {
18497                     i = (int)piece - (int)WhitePawn;
18498                     i = PieceToNumber((ChessSquare)i);
18499                     if( i >= gameInfo.holdingsSize ) return FALSE;
18500                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18501                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18502                     wcnt++;
18503                 }
18504             }
18505             if(subst) { // substitute back-rank question marks by holdings pieces
18506                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18507                     int k, m, n = bcnt + 1;
18508                     if(board[0][j] == ClearBoard) {
18509                         if(!wcnt) return FALSE;
18510                         n = rand() % wcnt;
18511                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18512                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18513                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18514                             break;
18515                         }
18516                     }
18517                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18518                         if(!bcnt) return FALSE;
18519                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18520                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18521                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18522                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18523                             break;
18524                         }
18525                     }
18526                 }
18527                 subst = 0;
18528             }
18529         }
18530         if(*p == ']') p++;
18531     }
18532
18533     if(subst) return FALSE; // substitution requested, but no holdings
18534
18535     while(*p == ' ') p++;
18536
18537     /* Active color */
18538     c = *p++;
18539     if(appData.colorNickNames) {
18540       if( c == appData.colorNickNames[0] ) c = 'w'; else
18541       if( c == appData.colorNickNames[1] ) c = 'b';
18542     }
18543     switch (c) {
18544       case 'w':
18545         *blackPlaysFirst = FALSE;
18546         break;
18547       case 'b':
18548         *blackPlaysFirst = TRUE;
18549         break;
18550       default:
18551         return FALSE;
18552     }
18553
18554     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18555     /* return the extra info in global variiables             */
18556
18557     while(*p==' ') p++;
18558
18559     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18560         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18561         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18562     }
18563
18564     /* set defaults in case FEN is incomplete */
18565     board[EP_STATUS] = EP_UNKNOWN;
18566     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18567     for(i=0; i<nrCastlingRights; i++ ) {
18568         board[CASTLING][i] =
18569             appData.fischerCastling ? NoRights : initialRights[i];
18570     }   /* assume possible unless obviously impossible */
18571     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18572     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18573     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18574                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18575     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18576     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18577     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18578                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18579     FENrulePlies = 0;
18580
18581     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18582       char *q = p;
18583       int w=0, b=0;
18584       while(isalpha(*p)) {
18585         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18586         if(islower(*p)) b |= 1 << (*p++ - 'a');
18587       }
18588       if(*p == '-') p++;
18589       if(p != q) {
18590         board[TOUCHED_W] = ~w;
18591         board[TOUCHED_B] = ~b;
18592         while(*p == ' ') p++;
18593       }
18594     } else
18595
18596     if(nrCastlingRights) {
18597       int fischer = 0;
18598       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18599       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18600           /* castling indicator present, so default becomes no castlings */
18601           for(i=0; i<nrCastlingRights; i++ ) {
18602                  board[CASTLING][i] = NoRights;
18603           }
18604       }
18605       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18606              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18607              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18608              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18609         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18610
18611         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18612             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18613             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18614         }
18615         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18616             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18617         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18618                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18619         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18620                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18621         switch(c) {
18622           case'K':
18623               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18624               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18625               board[CASTLING][2] = whiteKingFile;
18626               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18627               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18628               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18629               break;
18630           case'Q':
18631               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18632               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18633               board[CASTLING][2] = whiteKingFile;
18634               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18635               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18636               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18637               break;
18638           case'k':
18639               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18640               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18641               board[CASTLING][5] = blackKingFile;
18642               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18643               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18644               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18645               break;
18646           case'q':
18647               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18648               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18649               board[CASTLING][5] = blackKingFile;
18650               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18651               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18652               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18653           case '-':
18654               break;
18655           default: /* FRC castlings */
18656               if(c >= 'a') { /* black rights */
18657                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18658                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18659                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18660                   if(i == BOARD_RGHT) break;
18661                   board[CASTLING][5] = i;
18662                   c -= AAA;
18663                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18664                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18665                   if(c > i)
18666                       board[CASTLING][3] = c;
18667                   else
18668                       board[CASTLING][4] = c;
18669               } else { /* white rights */
18670                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18671                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18672                     if(board[0][i] == WhiteKing) break;
18673                   if(i == BOARD_RGHT) break;
18674                   board[CASTLING][2] = i;
18675                   c -= AAA - 'a' + 'A';
18676                   if(board[0][c] >= WhiteKing) break;
18677                   if(c > i)
18678                       board[CASTLING][0] = c;
18679                   else
18680                       board[CASTLING][1] = c;
18681               }
18682         }
18683       }
18684       for(i=0; i<nrCastlingRights; i++)
18685         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18686       if(gameInfo.variant == VariantSChess)
18687         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18688       if(fischer && shuffle) appData.fischerCastling = TRUE;
18689     if (appData.debugMode) {
18690         fprintf(debugFP, "FEN castling rights:");
18691         for(i=0; i<nrCastlingRights; i++)
18692         fprintf(debugFP, " %d", board[CASTLING][i]);
18693         fprintf(debugFP, "\n");
18694     }
18695
18696       while(*p==' ') p++;
18697     }
18698
18699     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18700
18701     /* read e.p. field in games that know e.p. capture */
18702     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18703        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18704        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18705       if(*p=='-') {
18706         p++; board[EP_STATUS] = EP_NONE;
18707       } else {
18708          char c = *p++ - AAA;
18709
18710          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18711          if(*p >= '0' && *p <='9') p++;
18712          board[EP_STATUS] = c;
18713       }
18714     }
18715
18716
18717     if(sscanf(p, "%d", &i) == 1) {
18718         FENrulePlies = i; /* 50-move ply counter */
18719         /* (The move number is still ignored)    */
18720     }
18721
18722     return TRUE;
18723 }
18724
18725 void
18726 EditPositionPasteFEN (char *fen)
18727 {
18728   if (fen != NULL) {
18729     Board initial_position;
18730
18731     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18732       DisplayError(_("Bad FEN position in clipboard"), 0);
18733       return ;
18734     } else {
18735       int savedBlackPlaysFirst = blackPlaysFirst;
18736       EditPositionEvent();
18737       blackPlaysFirst = savedBlackPlaysFirst;
18738       CopyBoard(boards[0], initial_position);
18739       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18740       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18741       DisplayBothClocks();
18742       DrawPosition(FALSE, boards[currentMove]);
18743     }
18744   }
18745 }
18746
18747 static char cseq[12] = "\\   ";
18748
18749 Boolean
18750 set_cont_sequence (char *new_seq)
18751 {
18752     int len;
18753     Boolean ret;
18754
18755     // handle bad attempts to set the sequence
18756         if (!new_seq)
18757                 return 0; // acceptable error - no debug
18758
18759     len = strlen(new_seq);
18760     ret = (len > 0) && (len < sizeof(cseq));
18761     if (ret)
18762       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18763     else if (appData.debugMode)
18764       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18765     return ret;
18766 }
18767
18768 /*
18769     reformat a source message so words don't cross the width boundary.  internal
18770     newlines are not removed.  returns the wrapped size (no null character unless
18771     included in source message).  If dest is NULL, only calculate the size required
18772     for the dest buffer.  lp argument indicats line position upon entry, and it's
18773     passed back upon exit.
18774 */
18775 int
18776 wrap (char *dest, char *src, int count, int width, int *lp)
18777 {
18778     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18779
18780     cseq_len = strlen(cseq);
18781     old_line = line = *lp;
18782     ansi = len = clen = 0;
18783
18784     for (i=0; i < count; i++)
18785     {
18786         if (src[i] == '\033')
18787             ansi = 1;
18788
18789         // if we hit the width, back up
18790         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18791         {
18792             // store i & len in case the word is too long
18793             old_i = i, old_len = len;
18794
18795             // find the end of the last word
18796             while (i && src[i] != ' ' && src[i] != '\n')
18797             {
18798                 i--;
18799                 len--;
18800             }
18801
18802             // word too long?  restore i & len before splitting it
18803             if ((old_i-i+clen) >= width)
18804             {
18805                 i = old_i;
18806                 len = old_len;
18807             }
18808
18809             // extra space?
18810             if (i && src[i-1] == ' ')
18811                 len--;
18812
18813             if (src[i] != ' ' && src[i] != '\n')
18814             {
18815                 i--;
18816                 if (len)
18817                     len--;
18818             }
18819
18820             // now append the newline and continuation sequence
18821             if (dest)
18822                 dest[len] = '\n';
18823             len++;
18824             if (dest)
18825                 strncpy(dest+len, cseq, cseq_len);
18826             len += cseq_len;
18827             line = cseq_len;
18828             clen = cseq_len;
18829             continue;
18830         }
18831
18832         if (dest)
18833             dest[len] = src[i];
18834         len++;
18835         if (!ansi)
18836             line++;
18837         if (src[i] == '\n')
18838             line = 0;
18839         if (src[i] == 'm')
18840             ansi = 0;
18841     }
18842     if (dest && appData.debugMode)
18843     {
18844         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18845             count, width, line, len, *lp);
18846         show_bytes(debugFP, src, count);
18847         fprintf(debugFP, "\ndest: ");
18848         show_bytes(debugFP, dest, len);
18849         fprintf(debugFP, "\n");
18850     }
18851     *lp = dest ? line : old_line;
18852
18853     return len;
18854 }
18855
18856 // [HGM] vari: routines for shelving variations
18857 Boolean modeRestore = FALSE;
18858
18859 void
18860 PushInner (int firstMove, int lastMove)
18861 {
18862         int i, j, nrMoves = lastMove - firstMove;
18863
18864         // push current tail of game on stack
18865         savedResult[storedGames] = gameInfo.result;
18866         savedDetails[storedGames] = gameInfo.resultDetails;
18867         gameInfo.resultDetails = NULL;
18868         savedFirst[storedGames] = firstMove;
18869         savedLast [storedGames] = lastMove;
18870         savedFramePtr[storedGames] = framePtr;
18871         framePtr -= nrMoves; // reserve space for the boards
18872         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18873             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18874             for(j=0; j<MOVE_LEN; j++)
18875                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18876             for(j=0; j<2*MOVE_LEN; j++)
18877                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18878             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18879             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18880             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18881             pvInfoList[firstMove+i-1].depth = 0;
18882             commentList[framePtr+i] = commentList[firstMove+i];
18883             commentList[firstMove+i] = NULL;
18884         }
18885
18886         storedGames++;
18887         forwardMostMove = firstMove; // truncate game so we can start variation
18888 }
18889
18890 void
18891 PushTail (int firstMove, int lastMove)
18892 {
18893         if(appData.icsActive) { // only in local mode
18894                 forwardMostMove = currentMove; // mimic old ICS behavior
18895                 return;
18896         }
18897         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18898
18899         PushInner(firstMove, lastMove);
18900         if(storedGames == 1) GreyRevert(FALSE);
18901         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18902 }
18903
18904 void
18905 PopInner (Boolean annotate)
18906 {
18907         int i, j, nrMoves;
18908         char buf[8000], moveBuf[20];
18909
18910         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18911         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18912         nrMoves = savedLast[storedGames] - currentMove;
18913         if(annotate) {
18914                 int cnt = 10;
18915                 if(!WhiteOnMove(currentMove))
18916                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18917                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18918                 for(i=currentMove; i<forwardMostMove; i++) {
18919                         if(WhiteOnMove(i))
18920                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18921                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18922                         strcat(buf, moveBuf);
18923                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18924                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18925                 }
18926                 strcat(buf, ")");
18927         }
18928         for(i=1; i<=nrMoves; i++) { // copy last variation back
18929             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18930             for(j=0; j<MOVE_LEN; j++)
18931                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18932             for(j=0; j<2*MOVE_LEN; j++)
18933                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18934             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18935             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18936             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18937             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18938             commentList[currentMove+i] = commentList[framePtr+i];
18939             commentList[framePtr+i] = NULL;
18940         }
18941         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18942         framePtr = savedFramePtr[storedGames];
18943         gameInfo.result = savedResult[storedGames];
18944         if(gameInfo.resultDetails != NULL) {
18945             free(gameInfo.resultDetails);
18946       }
18947         gameInfo.resultDetails = savedDetails[storedGames];
18948         forwardMostMove = currentMove + nrMoves;
18949 }
18950
18951 Boolean
18952 PopTail (Boolean annotate)
18953 {
18954         if(appData.icsActive) return FALSE; // only in local mode
18955         if(!storedGames) return FALSE; // sanity
18956         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18957
18958         PopInner(annotate);
18959         if(currentMove < forwardMostMove) ForwardEvent(); else
18960         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18961
18962         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18963         return TRUE;
18964 }
18965
18966 void
18967 CleanupTail ()
18968 {       // remove all shelved variations
18969         int i;
18970         for(i=0; i<storedGames; i++) {
18971             if(savedDetails[i])
18972                 free(savedDetails[i]);
18973             savedDetails[i] = NULL;
18974         }
18975         for(i=framePtr; i<MAX_MOVES; i++) {
18976                 if(commentList[i]) free(commentList[i]);
18977                 commentList[i] = NULL;
18978         }
18979         framePtr = MAX_MOVES-1;
18980         storedGames = 0;
18981 }
18982
18983 void
18984 LoadVariation (int index, char *text)
18985 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18986         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18987         int level = 0, move;
18988
18989         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18990         // first find outermost bracketing variation
18991         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18992             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18993                 if(*p == '{') wait = '}'; else
18994                 if(*p == '[') wait = ']'; else
18995                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18996                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18997             }
18998             if(*p == wait) wait = NULLCHAR; // closing ]} found
18999             p++;
19000         }
19001         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19002         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19003         end[1] = NULLCHAR; // clip off comment beyond variation
19004         ToNrEvent(currentMove-1);
19005         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19006         // kludge: use ParsePV() to append variation to game
19007         move = currentMove;
19008         ParsePV(start, TRUE, TRUE);
19009         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19010         ClearPremoveHighlights();
19011         CommentPopDown();
19012         ToNrEvent(currentMove+1);
19013 }
19014
19015 void
19016 LoadTheme ()
19017 {
19018     char *p, *q, buf[MSG_SIZ];
19019     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19020         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19021         ParseArgsFromString(buf);
19022         ActivateTheme(TRUE); // also redo colors
19023         return;
19024     }
19025     p = nickName;
19026     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19027     {
19028         int len;
19029         q = appData.themeNames;
19030         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19031       if(appData.useBitmaps) {
19032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19033                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19034                 appData.liteBackTextureMode,
19035                 appData.darkBackTextureMode );
19036       } else {
19037         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19038                 Col2Text(2),   // lightSquareColor
19039                 Col2Text(3) ); // darkSquareColor
19040       }
19041       if(appData.useBorder) {
19042         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19043                 appData.border);
19044       } else {
19045         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19046       }
19047       if(appData.useFont) {
19048         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19049                 appData.renderPiecesWithFont,
19050                 appData.fontToPieceTable,
19051                 Col2Text(9),    // appData.fontBackColorWhite
19052                 Col2Text(10) ); // appData.fontForeColorBlack
19053       } else {
19054         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19055                 appData.pieceDirectory);
19056         if(!appData.pieceDirectory[0])
19057           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19058                 Col2Text(0),   // whitePieceColor
19059                 Col2Text(1) ); // blackPieceColor
19060       }
19061       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19062                 Col2Text(4),   // highlightSquareColor
19063                 Col2Text(5) ); // premoveHighlightColor
19064         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19065         if(insert != q) insert[-1] = NULLCHAR;
19066         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19067         if(q)   free(q);
19068     }
19069     ActivateTheme(FALSE);
19070 }