f5e2b858bb9847829f8a02d616ad1ba5d7cfb272
[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, BlackDragon, BlackKing, BlackTower,
568         BlackTower, 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, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
696     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
698     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
699       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
700     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
701       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
702     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
704     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
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       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6286                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6287       break;
6288     case VariantCourier:
6289       pieces = CourierArray;
6290       gameInfo.boardWidth  = 12;
6291       nrCastlingRights = 0;
6292       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6293       break;
6294     case VariantKnightmate:
6295       pieces = KnightmateArray;
6296       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6297       break;
6298     case VariantSpartan:
6299       pieces = SpartanArray;
6300       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6301       break;
6302     case VariantLion:
6303       pieces = lionArray;
6304       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6305       break;
6306     case VariantChuChess:
6307       pieces = ChuChessArray;
6308       gameInfo.boardWidth = 10;
6309       gameInfo.boardHeight = 10;
6310       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6311       break;
6312     case VariantFairy:
6313       pieces = fairyArray;
6314       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6315       break;
6316     case VariantGreat:
6317       pieces = GreatArray;
6318       gameInfo.boardWidth = 10;
6319       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6320       gameInfo.holdingsSize = 8;
6321       break;
6322     case VariantSuper:
6323       pieces = FIDEArray;
6324       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6325       gameInfo.holdingsSize = 8;
6326       startedFromSetupPosition = TRUE;
6327       break;
6328     case VariantCrazyhouse:
6329     case VariantBughouse:
6330       pieces = FIDEArray;
6331       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6332       gameInfo.holdingsSize = 5;
6333       break;
6334     case VariantWildCastle:
6335       pieces = FIDEArray;
6336       /* !!?shuffle with kings guaranteed to be on d or e file */
6337       shuffleOpenings = 1;
6338       break;
6339     case VariantNoCastle:
6340       pieces = FIDEArray;
6341       nrCastlingRights = 0;
6342       /* !!?unconstrained back-rank shuffle */
6343       shuffleOpenings = 1;
6344       break;
6345     }
6346
6347     overrule = 0;
6348     if(appData.NrFiles >= 0) {
6349         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6350         gameInfo.boardWidth = appData.NrFiles;
6351     }
6352     if(appData.NrRanks >= 0) {
6353         gameInfo.boardHeight = appData.NrRanks;
6354     }
6355     if(appData.holdingsSize >= 0) {
6356         i = appData.holdingsSize;
6357         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6358         gameInfo.holdingsSize = i;
6359     }
6360     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6361     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6362         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6363
6364     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6365     if(pawnRow < 1) pawnRow = 1;
6366     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6367        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6368     if(gameInfo.variant == VariantChu) pawnRow = 3;
6369
6370     /* User pieceToChar list overrules defaults */
6371     if(appData.pieceToCharTable != NULL)
6372         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6373
6374     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6375
6376         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6377             s = (ChessSquare) 0; /* account holding counts in guard band */
6378         for( i=0; i<BOARD_HEIGHT; i++ )
6379             initialPosition[i][j] = s;
6380
6381         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6382         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6383         initialPosition[pawnRow][j] = WhitePawn;
6384         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6385         if(gameInfo.variant == VariantXiangqi) {
6386             if(j&1) {
6387                 initialPosition[pawnRow][j] =
6388                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6389                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6390                    initialPosition[2][j] = WhiteCannon;
6391                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6392                 }
6393             }
6394         }
6395         if(gameInfo.variant == VariantChu) {
6396              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6397                initialPosition[pawnRow+1][j] = WhiteCobra,
6398                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6399              for(i=1; i<pieceRows; i++) {
6400                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6401                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6402              }
6403         }
6404         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6405             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6406                initialPosition[0][j] = WhiteRook;
6407                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6408             }
6409         }
6410         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6411     }
6412     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6413     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6414
6415             j=BOARD_LEFT+1;
6416             initialPosition[1][j] = WhiteBishop;
6417             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6418             j=BOARD_RGHT-2;
6419             initialPosition[1][j] = WhiteRook;
6420             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6421     }
6422
6423     if( nrCastlingRights == -1) {
6424         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6425         /*       This sets default castling rights from none to normal corners   */
6426         /* Variants with other castling rights must set them themselves above    */
6427         nrCastlingRights = 6;
6428
6429         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6430         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6431         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6432         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6433         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6434         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6435      }
6436
6437      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6438      if(gameInfo.variant == VariantGreat) { // promotion commoners
6439         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6440         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6441         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6442         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6443      }
6444      if( gameInfo.variant == VariantSChess ) {
6445       initialPosition[1][0] = BlackMarshall;
6446       initialPosition[2][0] = BlackAngel;
6447       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6448       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6449       initialPosition[1][1] = initialPosition[2][1] =
6450       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6451      }
6452   if (appData.debugMode) {
6453     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6454   }
6455     if(shuffleOpenings) {
6456         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6457         startedFromSetupPosition = TRUE;
6458     }
6459     if(startedFromPositionFile) {
6460       /* [HGM] loadPos: use PositionFile for every new game */
6461       CopyBoard(initialPosition, filePosition);
6462       for(i=0; i<nrCastlingRights; i++)
6463           initialRights[i] = filePosition[CASTLING][i];
6464       startedFromSetupPosition = TRUE;
6465     }
6466     if(*appData.men) LoadPieceDesc(appData.men);
6467
6468     CopyBoard(boards[0], initialPosition);
6469
6470     if(oldx != gameInfo.boardWidth ||
6471        oldy != gameInfo.boardHeight ||
6472        oldv != gameInfo.variant ||
6473        oldh != gameInfo.holdingsWidth
6474                                          )
6475             InitDrawingSizes(-2 ,0);
6476
6477     oldv = gameInfo.variant;
6478     if (redraw)
6479       DrawPosition(TRUE, boards[currentMove]);
6480 }
6481
6482 void
6483 SendBoard (ChessProgramState *cps, int moveNum)
6484 {
6485     char message[MSG_SIZ];
6486
6487     if (cps->useSetboard) {
6488       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6489       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6490       SendToProgram(message, cps);
6491       free(fen);
6492
6493     } else {
6494       ChessSquare *bp;
6495       int i, j, left=0, right=BOARD_WIDTH;
6496       /* Kludge to set black to move, avoiding the troublesome and now
6497        * deprecated "black" command.
6498        */
6499       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6500         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6501
6502       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6503
6504       SendToProgram("edit\n", cps);
6505       SendToProgram("#\n", cps);
6506       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6507         bp = &boards[moveNum][i][left];
6508         for (j = left; j < right; j++, bp++) {
6509           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6510           if ((int) *bp < (int) BlackPawn) {
6511             if(j == BOARD_RGHT+1)
6512                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6513             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6514             if(message[0] == '+' || message[0] == '~') {
6515               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6516                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6517                         AAA + j, ONE + i - '0');
6518             }
6519             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6520                 message[1] = BOARD_RGHT   - 1 - j + '1';
6521                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6522             }
6523             SendToProgram(message, cps);
6524           }
6525         }
6526       }
6527
6528       SendToProgram("c\n", cps);
6529       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6530         bp = &boards[moveNum][i][left];
6531         for (j = left; j < right; j++, bp++) {
6532           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6533           if (((int) *bp != (int) EmptySquare)
6534               && ((int) *bp >= (int) BlackPawn)) {
6535             if(j == BOARD_LEFT-2)
6536                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6537             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6538                     AAA + j, ONE + i - '0');
6539             if(message[0] == '+' || message[0] == '~') {
6540               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6541                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6542                         AAA + j, ONE + i - '0');
6543             }
6544             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6545                 message[1] = BOARD_RGHT   - 1 - j + '1';
6546                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6547             }
6548             SendToProgram(message, cps);
6549           }
6550         }
6551       }
6552
6553       SendToProgram(".\n", cps);
6554     }
6555     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6556 }
6557
6558 char exclusionHeader[MSG_SIZ];
6559 int exCnt, excludePtr;
6560 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6561 static Exclusion excluTab[200];
6562 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6563
6564 static void
6565 WriteMap (int s)
6566 {
6567     int j;
6568     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6569     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6570 }
6571
6572 static void
6573 ClearMap ()
6574 {
6575     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6576     excludePtr = 24; exCnt = 0;
6577     WriteMap(0);
6578 }
6579
6580 static void
6581 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6582 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6583     char buf[2*MOVE_LEN], *p;
6584     Exclusion *e = excluTab;
6585     int i;
6586     for(i=0; i<exCnt; i++)
6587         if(e[i].ff == fromX && e[i].fr == fromY &&
6588            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6589     if(i == exCnt) { // was not in exclude list; add it
6590         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6591         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6592             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6593             return; // abort
6594         }
6595         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6596         excludePtr++; e[i].mark = excludePtr++;
6597         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6598         exCnt++;
6599     }
6600     exclusionHeader[e[i].mark] = state;
6601 }
6602
6603 static int
6604 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6605 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6606     char buf[MSG_SIZ];
6607     int j, k;
6608     ChessMove moveType;
6609     if((signed char)promoChar == -1) { // kludge to indicate best move
6610         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6611             return 1; // if unparsable, abort
6612     }
6613     // update exclusion map (resolving toggle by consulting existing state)
6614     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6615     j = k%8; k >>= 3;
6616     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6617     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6618          excludeMap[k] |=   1<<j;
6619     else excludeMap[k] &= ~(1<<j);
6620     // update header
6621     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6622     // inform engine
6623     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6624     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6625     SendToBoth(buf);
6626     return (state == '+');
6627 }
6628
6629 static void
6630 ExcludeClick (int index)
6631 {
6632     int i, j;
6633     Exclusion *e = excluTab;
6634     if(index < 25) { // none, best or tail clicked
6635         if(index < 13) { // none: include all
6636             WriteMap(0); // clear map
6637             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6638             SendToBoth("include all\n"); // and inform engine
6639         } else if(index > 18) { // tail
6640             if(exclusionHeader[19] == '-') { // tail was excluded
6641                 SendToBoth("include all\n");
6642                 WriteMap(0); // clear map completely
6643                 // now re-exclude selected moves
6644                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6645                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6646             } else { // tail was included or in mixed state
6647                 SendToBoth("exclude all\n");
6648                 WriteMap(0xFF); // fill map completely
6649                 // now re-include selected moves
6650                 j = 0; // count them
6651                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6652                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6653                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6654             }
6655         } else { // best
6656             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6657         }
6658     } else {
6659         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6660             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6661             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6662             break;
6663         }
6664     }
6665 }
6666
6667 ChessSquare
6668 DefaultPromoChoice (int white)
6669 {
6670     ChessSquare result;
6671     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672        gameInfo.variant == VariantMakruk)
6673         result = WhiteFerz; // no choice
6674     else if(gameInfo.variant == VariantASEAN)
6675         result = WhiteRook; // no choice
6676     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6677         result= WhiteKing; // in Suicide Q is the last thing we want
6678     else if(gameInfo.variant == VariantSpartan)
6679         result = white ? WhiteQueen : WhiteAngel;
6680     else result = WhiteQueen;
6681     if(!white) result = WHITE_TO_BLACK result;
6682     return result;
6683 }
6684
6685 static int autoQueen; // [HGM] oneclick
6686
6687 int
6688 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6689 {
6690     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6691     /* [HGM] add Shogi promotions */
6692     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6693     ChessSquare piece, partner;
6694     ChessMove moveType;
6695     Boolean premove;
6696
6697     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6698     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6699
6700     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6701       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6702         return FALSE;
6703
6704     piece = boards[currentMove][fromY][fromX];
6705     if(gameInfo.variant == VariantChu) {
6706         promotionZoneSize = BOARD_HEIGHT/3;
6707         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6708     } else if(gameInfo.variant == VariantShogi) {
6709         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6710         highestPromotingPiece = (int)WhiteAlfil;
6711     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6712         promotionZoneSize = 3;
6713     }
6714
6715     // Treat Lance as Pawn when it is not representing Amazon or Lance
6716     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6717         if(piece == WhiteLance) piece = WhitePawn; else
6718         if(piece == BlackLance) piece = BlackPawn;
6719     }
6720
6721     // next weed out all moves that do not touch the promotion zone at all
6722     if((int)piece >= BlackPawn) {
6723         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6724              return FALSE;
6725         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6726         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6727     } else {
6728         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6729            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6730         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6731              return FALSE;
6732     }
6733
6734     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6735
6736     // weed out mandatory Shogi promotions
6737     if(gameInfo.variant == VariantShogi) {
6738         if(piece >= BlackPawn) {
6739             if(toY == 0 && piece == BlackPawn ||
6740                toY == 0 && piece == BlackQueen ||
6741                toY <= 1 && piece == BlackKnight) {
6742                 *promoChoice = '+';
6743                 return FALSE;
6744             }
6745         } else {
6746             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6747                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6748                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6749                 *promoChoice = '+';
6750                 return FALSE;
6751             }
6752         }
6753     }
6754
6755     // weed out obviously illegal Pawn moves
6756     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6757         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6758         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6759         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6760         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6761         // note we are not allowed to test for valid (non-)capture, due to premove
6762     }
6763
6764     // we either have a choice what to promote to, or (in Shogi) whether to promote
6765     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6766        gameInfo.variant == VariantMakruk) {
6767         ChessSquare p=BlackFerz;  // no choice
6768         while(p < EmptySquare) {  //but make sure we use piece that exists
6769             *promoChoice = PieceToChar(p++);
6770             if(*promoChoice != '.') break;
6771         }
6772         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6773     }
6774     // no sense asking what we must promote to if it is going to explode...
6775     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6776         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6777         return FALSE;
6778     }
6779     // give caller the default choice even if we will not make it
6780     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6781     partner = piece; // pieces can promote if the pieceToCharTable says so
6782     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6783     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6784     if(        sweepSelect && gameInfo.variant != VariantGreat
6785                            && gameInfo.variant != VariantGrand
6786                            && gameInfo.variant != VariantSuper) return FALSE;
6787     if(autoQueen) return FALSE; // predetermined
6788
6789     // suppress promotion popup on illegal moves that are not premoves
6790     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6791               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6792     if(appData.testLegality && !premove) {
6793         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6794                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6795         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6796         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6797             return FALSE;
6798     }
6799
6800     return TRUE;
6801 }
6802
6803 int
6804 InPalace (int row, int column)
6805 {   /* [HGM] for Xiangqi */
6806     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6807          column < (BOARD_WIDTH + 4)/2 &&
6808          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6809     return FALSE;
6810 }
6811
6812 int
6813 PieceForSquare (int x, int y)
6814 {
6815   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6816      return -1;
6817   else
6818      return boards[currentMove][y][x];
6819 }
6820
6821 int
6822 OKToStartUserMove (int x, int y)
6823 {
6824     ChessSquare from_piece;
6825     int white_piece;
6826
6827     if (matchMode) return FALSE;
6828     if (gameMode == EditPosition) return TRUE;
6829
6830     if (x >= 0 && y >= 0)
6831       from_piece = boards[currentMove][y][x];
6832     else
6833       from_piece = EmptySquare;
6834
6835     if (from_piece == EmptySquare) return FALSE;
6836
6837     white_piece = (int)from_piece >= (int)WhitePawn &&
6838       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6839
6840     switch (gameMode) {
6841       case AnalyzeFile:
6842       case TwoMachinesPlay:
6843       case EndOfGame:
6844         return FALSE;
6845
6846       case IcsObserving:
6847       case IcsIdle:
6848         return FALSE;
6849
6850       case MachinePlaysWhite:
6851       case IcsPlayingBlack:
6852         if (appData.zippyPlay) return FALSE;
6853         if (white_piece) {
6854             DisplayMoveError(_("You are playing Black"));
6855             return FALSE;
6856         }
6857         break;
6858
6859       case MachinePlaysBlack:
6860       case IcsPlayingWhite:
6861         if (appData.zippyPlay) return FALSE;
6862         if (!white_piece) {
6863             DisplayMoveError(_("You are playing White"));
6864             return FALSE;
6865         }
6866         break;
6867
6868       case PlayFromGameFile:
6869             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6870       case EditGame:
6871       case AnalyzeMode:
6872         if (!white_piece && WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is White's turn"));
6874             return FALSE;
6875         }
6876         if (white_piece && !WhiteOnMove(currentMove)) {
6877             DisplayMoveError(_("It is Black's turn"));
6878             return FALSE;
6879         }
6880         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6881             /* Editing correspondence game history */
6882             /* Could disallow this or prompt for confirmation */
6883             cmailOldMove = -1;
6884         }
6885         break;
6886
6887       case BeginningOfGame:
6888         if (appData.icsActive) return FALSE;
6889         if (!appData.noChessProgram) {
6890             if (!white_piece) {
6891                 DisplayMoveError(_("You are playing White"));
6892                 return FALSE;
6893             }
6894         }
6895         break;
6896
6897       case Training:
6898         if (!white_piece && WhiteOnMove(currentMove)) {
6899             DisplayMoveError(_("It is White's turn"));
6900             return FALSE;
6901         }
6902         if (white_piece && !WhiteOnMove(currentMove)) {
6903             DisplayMoveError(_("It is Black's turn"));
6904             return FALSE;
6905         }
6906         break;
6907
6908       default:
6909       case IcsExamining:
6910         break;
6911     }
6912     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6913         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6914         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6915         && gameMode != AnalyzeFile && gameMode != Training) {
6916         DisplayMoveError(_("Displayed position is not current"));
6917         return FALSE;
6918     }
6919     return TRUE;
6920 }
6921
6922 Boolean
6923 OnlyMove (int *x, int *y, Boolean captures)
6924 {
6925     DisambiguateClosure cl;
6926     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6927     switch(gameMode) {
6928       case MachinePlaysBlack:
6929       case IcsPlayingWhite:
6930       case BeginningOfGame:
6931         if(!WhiteOnMove(currentMove)) return FALSE;
6932         break;
6933       case MachinePlaysWhite:
6934       case IcsPlayingBlack:
6935         if(WhiteOnMove(currentMove)) return FALSE;
6936         break;
6937       case EditGame:
6938         break;
6939       default:
6940         return FALSE;
6941     }
6942     cl.pieceIn = EmptySquare;
6943     cl.rfIn = *y;
6944     cl.ffIn = *x;
6945     cl.rtIn = -1;
6946     cl.ftIn = -1;
6947     cl.promoCharIn = NULLCHAR;
6948     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6949     if( cl.kind == NormalMove ||
6950         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6951         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6952         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6953       fromX = cl.ff;
6954       fromY = cl.rf;
6955       *x = cl.ft;
6956       *y = cl.rt;
6957       return TRUE;
6958     }
6959     if(cl.kind != ImpossibleMove) return FALSE;
6960     cl.pieceIn = EmptySquare;
6961     cl.rfIn = -1;
6962     cl.ffIn = -1;
6963     cl.rtIn = *y;
6964     cl.ftIn = *x;
6965     cl.promoCharIn = NULLCHAR;
6966     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6967     if( cl.kind == NormalMove ||
6968         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6969         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6970         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6971       fromX = cl.ff;
6972       fromY = cl.rf;
6973       *x = cl.ft;
6974       *y = cl.rt;
6975       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6976       return TRUE;
6977     }
6978     return FALSE;
6979 }
6980
6981 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6982 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6983 int lastLoadGameUseList = FALSE;
6984 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6985 ChessMove lastLoadGameStart = EndOfFile;
6986 int doubleClick;
6987 Boolean addToBookFlag;
6988 static Board rightsBoard, nullBoard;
6989
6990 void
6991 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6992 {
6993     ChessMove moveType;
6994     ChessSquare pup;
6995     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6996
6997     /* Check if the user is playing in turn.  This is complicated because we
6998        let the user "pick up" a piece before it is his turn.  So the piece he
6999        tried to pick up may have been captured by the time he puts it down!
7000        Therefore we use the color the user is supposed to be playing in this
7001        test, not the color of the piece that is currently on the starting
7002        square---except in EditGame mode, where the user is playing both
7003        sides; fortunately there the capture race can't happen.  (It can
7004        now happen in IcsExamining mode, but that's just too bad.  The user
7005        will get a somewhat confusing message in that case.)
7006        */
7007
7008     switch (gameMode) {
7009       case AnalyzeFile:
7010       case TwoMachinesPlay:
7011       case EndOfGame:
7012       case IcsObserving:
7013       case IcsIdle:
7014         /* We switched into a game mode where moves are not accepted,
7015            perhaps while the mouse button was down. */
7016         return;
7017
7018       case MachinePlaysWhite:
7019         /* User is moving for Black */
7020         if (WhiteOnMove(currentMove)) {
7021             DisplayMoveError(_("It is White's turn"));
7022             return;
7023         }
7024         break;
7025
7026       case MachinePlaysBlack:
7027         /* User is moving for White */
7028         if (!WhiteOnMove(currentMove)) {
7029             DisplayMoveError(_("It is Black's turn"));
7030             return;
7031         }
7032         break;
7033
7034       case PlayFromGameFile:
7035             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7036       case EditGame:
7037       case IcsExamining:
7038       case BeginningOfGame:
7039       case AnalyzeMode:
7040       case Training:
7041         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7042         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7043             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7044             /* User is moving for Black */
7045             if (WhiteOnMove(currentMove)) {
7046                 DisplayMoveError(_("It is White's turn"));
7047                 return;
7048             }
7049         } else {
7050             /* User is moving for White */
7051             if (!WhiteOnMove(currentMove)) {
7052                 DisplayMoveError(_("It is Black's turn"));
7053                 return;
7054             }
7055         }
7056         break;
7057
7058       case IcsPlayingBlack:
7059         /* User is moving for Black */
7060         if (WhiteOnMove(currentMove)) {
7061             if (!appData.premove) {
7062                 DisplayMoveError(_("It is White's turn"));
7063             } else if (toX >= 0 && toY >= 0) {
7064                 premoveToX = toX;
7065                 premoveToY = toY;
7066                 premoveFromX = fromX;
7067                 premoveFromY = fromY;
7068                 premovePromoChar = promoChar;
7069                 gotPremove = 1;
7070                 if (appData.debugMode)
7071                     fprintf(debugFP, "Got premove: fromX %d,"
7072                             "fromY %d, toX %d, toY %d\n",
7073                             fromX, fromY, toX, toY);
7074             }
7075             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7076             return;
7077         }
7078         break;
7079
7080       case IcsPlayingWhite:
7081         /* User is moving for White */
7082         if (!WhiteOnMove(currentMove)) {
7083             if (!appData.premove) {
7084                 DisplayMoveError(_("It is Black's turn"));
7085             } else if (toX >= 0 && toY >= 0) {
7086                 premoveToX = toX;
7087                 premoveToY = toY;
7088                 premoveFromX = fromX;
7089                 premoveFromY = fromY;
7090                 premovePromoChar = promoChar;
7091                 gotPremove = 1;
7092                 if (appData.debugMode)
7093                     fprintf(debugFP, "Got premove: fromX %d,"
7094                             "fromY %d, toX %d, toY %d\n",
7095                             fromX, fromY, toX, toY);
7096             }
7097             DrawPosition(TRUE, boards[currentMove]);
7098             return;
7099         }
7100         break;
7101
7102       default:
7103         break;
7104
7105       case EditPosition:
7106         /* EditPosition, empty square, or different color piece;
7107            click-click move is possible */
7108         if (toX == -2 || toY == -2) {
7109             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7110             DrawPosition(FALSE, boards[currentMove]);
7111             return;
7112         } else if (toX >= 0 && toY >= 0) {
7113             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7114                 ChessSquare p = boards[0][rf][ff];
7115                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7116                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7117                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7118                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7119                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7120                     gatingPiece = p;
7121                 }
7122             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7123             boards[0][toY][toX] = boards[0][fromY][fromX];
7124             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7125                 if(boards[0][fromY][0] != EmptySquare) {
7126                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7127                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7128                 }
7129             } else
7130             if(fromX == BOARD_RGHT+1) {
7131                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7132                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7133                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7134                 }
7135             } else
7136             boards[0][fromY][fromX] = gatingPiece;
7137             ClearHighlights();
7138             DrawPosition(FALSE, boards[currentMove]);
7139             return;
7140         }
7141         return;
7142     }
7143
7144     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7145     pup = boards[currentMove][toY][toX];
7146
7147     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7148     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7149          if( pup != EmptySquare ) return;
7150          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7151            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7152                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7153            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7154            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7155            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7156            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7157          fromY = DROP_RANK;
7158     }
7159
7160     /* [HGM] always test for legality, to get promotion info */
7161     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7162                                          fromY, fromX, toY, toX, promoChar);
7163
7164     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7165
7166     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7167
7168     /* [HGM] but possibly ignore an IllegalMove result */
7169     if (appData.testLegality) {
7170         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7171             DisplayMoveError(_("Illegal move"));
7172             return;
7173         }
7174     }
7175
7176     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7177         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7178              ClearPremoveHighlights(); // was included
7179         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7180         DrawPosition(FALSE, NULL);
7181         return;
7182     }
7183
7184     if(addToBookFlag) { // adding moves to book
7185         char buf[MSG_SIZ], move[MSG_SIZ];
7186         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7187         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7188                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7189         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7190         AddBookMove(buf);
7191         addToBookFlag = FALSE;
7192         ClearHighlights();
7193         return;
7194     }
7195
7196     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7197 }
7198
7199 /* Common tail of UserMoveEvent and DropMenuEvent */
7200 int
7201 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7202 {
7203     char *bookHit = 0;
7204
7205     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7206         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7207         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7208         if(WhiteOnMove(currentMove)) {
7209             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7210         } else {
7211             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7212         }
7213     }
7214
7215     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7216        move type in caller when we know the move is a legal promotion */
7217     if(moveType == NormalMove && promoChar)
7218         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7219
7220     /* [HGM] <popupFix> The following if has been moved here from
7221        UserMoveEvent(). Because it seemed to belong here (why not allow
7222        piece drops in training games?), and because it can only be
7223        performed after it is known to what we promote. */
7224     if (gameMode == Training) {
7225       /* compare the move played on the board to the next move in the
7226        * game. If they match, display the move and the opponent's response.
7227        * If they don't match, display an error message.
7228        */
7229       int saveAnimate;
7230       Board testBoard;
7231       CopyBoard(testBoard, boards[currentMove]);
7232       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7233
7234       if (CompareBoards(testBoard, boards[currentMove+1])) {
7235         ForwardInner(currentMove+1);
7236
7237         /* Autoplay the opponent's response.
7238          * if appData.animate was TRUE when Training mode was entered,
7239          * the response will be animated.
7240          */
7241         saveAnimate = appData.animate;
7242         appData.animate = animateTraining;
7243         ForwardInner(currentMove+1);
7244         appData.animate = saveAnimate;
7245
7246         /* check for the end of the game */
7247         if (currentMove >= forwardMostMove) {
7248           gameMode = PlayFromGameFile;
7249           ModeHighlight();
7250           SetTrainingModeOff();
7251           DisplayInformation(_("End of game"));
7252         }
7253       } else {
7254         DisplayError(_("Incorrect move"), 0);
7255       }
7256       return 1;
7257     }
7258
7259   /* Ok, now we know that the move is good, so we can kill
7260      the previous line in Analysis Mode */
7261   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7262                                 && currentMove < forwardMostMove) {
7263     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7264     else forwardMostMove = currentMove;
7265   }
7266
7267   ClearMap();
7268
7269   /* If we need the chess program but it's dead, restart it */
7270   ResurrectChessProgram();
7271
7272   /* A user move restarts a paused game*/
7273   if (pausing)
7274     PauseEvent();
7275
7276   thinkOutput[0] = NULLCHAR;
7277
7278   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7279
7280   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7281     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7282     return 1;
7283   }
7284
7285   if (gameMode == BeginningOfGame) {
7286     if (appData.noChessProgram) {
7287       gameMode = EditGame;
7288       SetGameInfo();
7289     } else {
7290       char buf[MSG_SIZ];
7291       gameMode = MachinePlaysBlack;
7292       StartClocks();
7293       SetGameInfo();
7294       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7295       DisplayTitle(buf);
7296       if (first.sendName) {
7297         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7298         SendToProgram(buf, &first);
7299       }
7300       StartClocks();
7301     }
7302     ModeHighlight();
7303   }
7304
7305   /* Relay move to ICS or chess engine */
7306   if (appData.icsActive) {
7307     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7308         gameMode == IcsExamining) {
7309       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7310         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7311         SendToICS("draw ");
7312         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7313       }
7314       // also send plain move, in case ICS does not understand atomic claims
7315       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7316       ics_user_moved = 1;
7317     }
7318   } else {
7319     if (first.sendTime && (gameMode == BeginningOfGame ||
7320                            gameMode == MachinePlaysWhite ||
7321                            gameMode == MachinePlaysBlack)) {
7322       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7323     }
7324     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7325          // [HGM] book: if program might be playing, let it use book
7326         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7327         first.maybeThinking = TRUE;
7328     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7329         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7330         SendBoard(&first, currentMove+1);
7331         if(second.analyzing) {
7332             if(!second.useSetboard) SendToProgram("undo\n", &second);
7333             SendBoard(&second, currentMove+1);
7334         }
7335     } else {
7336         SendMoveToProgram(forwardMostMove-1, &first);
7337         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7338     }
7339     if (currentMove == cmailOldMove + 1) {
7340       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7341     }
7342   }
7343
7344   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7345
7346   switch (gameMode) {
7347   case EditGame:
7348     if(appData.testLegality)
7349     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7350     case MT_NONE:
7351     case MT_CHECK:
7352       break;
7353     case MT_CHECKMATE:
7354     case MT_STAINMATE:
7355       if (WhiteOnMove(currentMove)) {
7356         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7357       } else {
7358         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7359       }
7360       break;
7361     case MT_STALEMATE:
7362       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7363       break;
7364     }
7365     break;
7366
7367   case MachinePlaysBlack:
7368   case MachinePlaysWhite:
7369     /* disable certain menu options while machine is thinking */
7370     SetMachineThinkingEnables();
7371     break;
7372
7373   default:
7374     break;
7375   }
7376
7377   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7378   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7379
7380   if(bookHit) { // [HGM] book: simulate book reply
7381         static char bookMove[MSG_SIZ]; // a bit generous?
7382
7383         programStats.nodes = programStats.depth = programStats.time =
7384         programStats.score = programStats.got_only_move = 0;
7385         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7386
7387         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7388         strcat(bookMove, bookHit);
7389         HandleMachineMove(bookMove, &first);
7390   }
7391   return 1;
7392 }
7393
7394 void
7395 MarkByFEN(char *fen)
7396 {
7397         int r, f;
7398         if(!appData.markers || !appData.highlightDragging) return;
7399         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7400         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7401         while(*fen) {
7402             int s = 0;
7403             marker[r][f] = 0;
7404             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7405             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7406             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7407             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7408             if(*fen == 'T') marker[r][f++] = 0; else
7409             if(*fen == 'Y') marker[r][f++] = 1; else
7410             if(*fen == 'G') marker[r][f++] = 3; else
7411             if(*fen == 'B') marker[r][f++] = 4; else
7412             if(*fen == 'C') marker[r][f++] = 5; else
7413             if(*fen == 'M') marker[r][f++] = 6; else
7414             if(*fen == 'W') marker[r][f++] = 7; else
7415             if(*fen == 'D') marker[r][f++] = 8; else
7416             if(*fen == 'R') marker[r][f++] = 2; else {
7417                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7418               f += s; fen -= s>0;
7419             }
7420             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7421             if(r < 0) break;
7422             fen++;
7423         }
7424         DrawPosition(TRUE, NULL);
7425 }
7426
7427 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7428
7429 void
7430 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7431 {
7432     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7433     Markers *m = (Markers *) closure;
7434     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7435                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7436         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7437                          || kind == WhiteCapturesEnPassant
7438                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7439     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7440 }
7441
7442 static int hoverSavedValid;
7443
7444 void
7445 MarkTargetSquares (int clear)
7446 {
7447   int x, y, sum=0;
7448   if(clear) { // no reason to ever suppress clearing
7449     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7450     hoverSavedValid = 0;
7451     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7452   } else {
7453     int capt = 0;
7454     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7455        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7456     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7457     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7458       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7459       if(capt)
7460       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7461     }
7462   }
7463   DrawPosition(FALSE, NULL);
7464 }
7465
7466 int
7467 Explode (Board board, int fromX, int fromY, int toX, int toY)
7468 {
7469     if(gameInfo.variant == VariantAtomic &&
7470        (board[toY][toX] != EmptySquare ||                     // capture?
7471         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7472                          board[fromY][fromX] == BlackPawn   )
7473       )) {
7474         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7475         return TRUE;
7476     }
7477     return FALSE;
7478 }
7479
7480 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7481
7482 int
7483 CanPromote (ChessSquare piece, int y)
7484 {
7485         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7486         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7487         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7488         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7489            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7490           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7491            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7492         return (piece == BlackPawn && y <= zone ||
7493                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7494                 piece == BlackLance && y <= zone ||
7495                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7496 }
7497
7498 void
7499 HoverEvent (int xPix, int yPix, int x, int y)
7500 {
7501         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7502         int r, f;
7503         if(!first.highlight) return;
7504         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7505         if(x == oldX && y == oldY) return; // only do something if we enter new square
7506         oldFromX = fromX; oldFromY = fromY;
7507         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7508           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7509             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7510           hoverSavedValid = 1;
7511         } else if(oldX != x || oldY != y) {
7512           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7513           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7516           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7517             char buf[MSG_SIZ];
7518             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7519             SendToProgram(buf, &first);
7520           }
7521           oldX = x; oldY = y;
7522 //        SetHighlights(fromX, fromY, x, y);
7523         }
7524 }
7525
7526 void ReportClick(char *action, int x, int y)
7527 {
7528         char buf[MSG_SIZ]; // Inform engine of what user does
7529         int r, f;
7530         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7531           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7532             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7533         if(!first.highlight || gameMode == EditPosition) return;
7534         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7535         SendToProgram(buf, &first);
7536 }
7537
7538 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7539
7540 void
7541 LeftClick (ClickType clickType, int xPix, int yPix)
7542 {
7543     int x, y;
7544     Boolean saveAnimate;
7545     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7546     char promoChoice = NULLCHAR;
7547     ChessSquare piece;
7548     static TimeMark lastClickTime, prevClickTime;
7549
7550     if(flashing) return;
7551
7552     x = EventToSquare(xPix, BOARD_WIDTH);
7553     y = EventToSquare(yPix, BOARD_HEIGHT);
7554     if (!flipView && y >= 0) {
7555         y = BOARD_HEIGHT - 1 - y;
7556     }
7557     if (flipView && x >= 0) {
7558         x = BOARD_WIDTH - 1 - x;
7559     }
7560
7561     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7562         static int dummy;
7563         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7564         right = TRUE;
7565         return;
7566     }
7567
7568     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7569
7570     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7571
7572     if (clickType == Press) ErrorPopDown();
7573     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7574
7575     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7576         defaultPromoChoice = promoSweep;
7577         promoSweep = EmptySquare;   // terminate sweep
7578         promoDefaultAltered = TRUE;
7579         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7580     }
7581
7582     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7583         if(clickType == Release) return; // ignore upclick of click-click destination
7584         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7585         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7586         if(gameInfo.holdingsWidth &&
7587                 (WhiteOnMove(currentMove)
7588                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7589                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7590             // click in right holdings, for determining promotion piece
7591             ChessSquare p = boards[currentMove][y][x];
7592             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7593             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7594             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7595                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7596                 fromX = fromY = -1;
7597                 return;
7598             }
7599         }
7600         DrawPosition(FALSE, boards[currentMove]);
7601         return;
7602     }
7603
7604     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7605     if(clickType == Press
7606             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7607               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7608               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7609         return;
7610
7611     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7612         // could be static click on premove from-square: abort premove
7613         gotPremove = 0;
7614         ClearPremoveHighlights();
7615     }
7616
7617     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7618         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7619
7620     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7621         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7622                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7623         defaultPromoChoice = DefaultPromoChoice(side);
7624     }
7625
7626     autoQueen = appData.alwaysPromoteToQueen;
7627
7628     if (fromX == -1) {
7629       int originalY = y;
7630       gatingPiece = EmptySquare;
7631       if (clickType != Press) {
7632         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7633             DragPieceEnd(xPix, yPix); dragging = 0;
7634             DrawPosition(FALSE, NULL);
7635         }
7636         return;
7637       }
7638       doubleClick = FALSE;
7639       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7640         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7641       }
7642       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7643       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7644          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7645          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7646             /* First square */
7647             if (OKToStartUserMove(fromX, fromY)) {
7648                 second = 0;
7649                 ReportClick("lift", x, y);
7650                 MarkTargetSquares(0);
7651                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7652                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7653                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7654                     promoSweep = defaultPromoChoice;
7655                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7656                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7657                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7658                 }
7659                 if (appData.highlightDragging) {
7660                     SetHighlights(fromX, fromY, -1, -1);
7661                 } else {
7662                     ClearHighlights();
7663                 }
7664             } else fromX = fromY = -1;
7665             return;
7666         }
7667     }
7668
7669     /* fromX != -1 */
7670     if (clickType == Press && gameMode != EditPosition) {
7671         ChessSquare fromP;
7672         ChessSquare toP;
7673         int frc;
7674
7675         // ignore off-board to clicks
7676         if(y < 0 || x < 0) return;
7677
7678         /* Check if clicking again on the same color piece */
7679         fromP = boards[currentMove][fromY][fromX];
7680         toP = boards[currentMove][y][x];
7681         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7682         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7683             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7684            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7685              WhitePawn <= toP && toP <= WhiteKing &&
7686              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7687              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7688             (BlackPawn <= fromP && fromP <= BlackKing &&
7689              BlackPawn <= toP && toP <= BlackKing &&
7690              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7691              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7692             /* Clicked again on same color piece -- changed his mind */
7693             second = (x == fromX && y == fromY);
7694             killX = killY = kill2X = kill2Y = -1;
7695             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7696                 second = FALSE; // first double-click rather than scond click
7697                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7698             }
7699             promoDefaultAltered = FALSE;
7700            if(!second) MarkTargetSquares(1);
7701            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7702             if (appData.highlightDragging) {
7703                 SetHighlights(x, y, -1, -1);
7704             } else {
7705                 ClearHighlights();
7706             }
7707             if (OKToStartUserMove(x, y)) {
7708                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7709                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7710                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7711                  gatingPiece = boards[currentMove][fromY][fromX];
7712                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7713                 fromX = x;
7714                 fromY = y; dragging = 1;
7715                 if(!second) ReportClick("lift", x, y);
7716                 MarkTargetSquares(0);
7717                 DragPieceBegin(xPix, yPix, FALSE);
7718                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7719                     promoSweep = defaultPromoChoice;
7720                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7721                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7722                 }
7723             }
7724            }
7725            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7726            second = FALSE;
7727         }
7728         // ignore clicks on holdings
7729         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7730     }
7731
7732     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7733         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7734         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7735         return;
7736     }
7737
7738     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7739         DragPieceEnd(xPix, yPix); dragging = 0;
7740         if(clearFlag) {
7741             // a deferred attempt to click-click move an empty square on top of a piece
7742             boards[currentMove][y][x] = EmptySquare;
7743             ClearHighlights();
7744             DrawPosition(FALSE, boards[currentMove]);
7745             fromX = fromY = -1; clearFlag = 0;
7746             return;
7747         }
7748         if (appData.animateDragging) {
7749             /* Undo animation damage if any */
7750             DrawPosition(FALSE, NULL);
7751         }
7752         if (second) {
7753             /* Second up/down in same square; just abort move */
7754             second = 0;
7755             fromX = fromY = -1;
7756             gatingPiece = EmptySquare;
7757             ClearHighlights();
7758             gotPremove = 0;
7759             ClearPremoveHighlights();
7760             MarkTargetSquares(-1);
7761             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7762         } else {
7763             /* First upclick in same square; start click-click mode */
7764             SetHighlights(x, y, -1, -1);
7765         }
7766         return;
7767     }
7768
7769     clearFlag = 0;
7770
7771     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7772        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7773         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7774         DisplayMessage(_("only marked squares are legal"),"");
7775         DrawPosition(TRUE, NULL);
7776         return; // ignore to-click
7777     }
7778
7779     /* we now have a different from- and (possibly off-board) to-square */
7780     /* Completed move */
7781     if(!sweepSelecting) {
7782         toX = x;
7783         toY = y;
7784     }
7785
7786     piece = boards[currentMove][fromY][fromX];
7787
7788     saveAnimate = appData.animate;
7789     if (clickType == Press) {
7790         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7791         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7792             // must be Edit Position mode with empty-square selected
7793             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7794             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7795             return;
7796         }
7797         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7798             return;
7799         }
7800         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7801             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7802         } else
7803         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7804         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7805           if(appData.sweepSelect) {
7806             promoSweep = defaultPromoChoice;
7807             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7808             selectFlag = 0; lastX = xPix; lastY = yPix;
7809             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7810             saveFlash = appData.flashCount; appData.flashCount = 0;
7811             Sweep(0); // Pawn that is going to promote: preview promotion piece
7812             sweepSelecting = 1;
7813             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7814             MarkTargetSquares(1);
7815           }
7816           return; // promo popup appears on up-click
7817         }
7818         /* Finish clickclick move */
7819         if (appData.animate || appData.highlightLastMove) {
7820             SetHighlights(fromX, fromY, toX, toY);
7821         } else {
7822             ClearHighlights();
7823         }
7824         MarkTargetSquares(1);
7825     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7826         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7827         *promoRestrict = 0; appData.flashCount = saveFlash;
7828         if (appData.animate || appData.highlightLastMove) {
7829             SetHighlights(fromX, fromY, toX, toY);
7830         } else {
7831             ClearHighlights();
7832         }
7833         MarkTargetSquares(1);
7834     } else {
7835 #if 0
7836 // [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
7837         /* Finish drag move */
7838         if (appData.highlightLastMove) {
7839             SetHighlights(fromX, fromY, toX, toY);
7840         } else {
7841             ClearHighlights();
7842         }
7843 #endif
7844         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7845           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7846         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7847         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7848           dragging *= 2;            // flag button-less dragging if we are dragging
7849           MarkTargetSquares(1);
7850           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7851           else {
7852             kill2X = killX; kill2Y = killY;
7853             killX = x; killY = y;     // remember this square as intermediate
7854             ReportClick("put", x, y); // and inform engine
7855             ReportClick("lift", x, y);
7856             MarkTargetSquares(0);
7857             return;
7858           }
7859         }
7860         DragPieceEnd(xPix, yPix); dragging = 0;
7861         /* Don't animate move and drag both */
7862         appData.animate = FALSE;
7863         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7864     }
7865
7866     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7867     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7868         ChessSquare piece = boards[currentMove][fromY][fromX];
7869         if(gameMode == EditPosition && piece != EmptySquare &&
7870            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7871             int n;
7872
7873             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7874                 n = PieceToNumber(piece - (int)BlackPawn);
7875                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7876                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7877                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7878             } else
7879             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7880                 n = PieceToNumber(piece);
7881                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7882                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7883                 boards[currentMove][n][BOARD_WIDTH-2]++;
7884             }
7885             boards[currentMove][fromY][fromX] = EmptySquare;
7886         }
7887         ClearHighlights();
7888         fromX = fromY = -1;
7889         MarkTargetSquares(1);
7890         DrawPosition(TRUE, boards[currentMove]);
7891         return;
7892     }
7893
7894     // off-board moves should not be highlighted
7895     if(x < 0 || y < 0) ClearHighlights();
7896     else ReportClick("put", x, y);
7897
7898     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7899
7900     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7901
7902     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7903         SetHighlights(fromX, fromY, toX, toY);
7904         MarkTargetSquares(1);
7905         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7906             // [HGM] super: promotion to captured piece selected from holdings
7907             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7908             promotionChoice = TRUE;
7909             // kludge follows to temporarily execute move on display, without promoting yet
7910             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7911             boards[currentMove][toY][toX] = p;
7912             DrawPosition(FALSE, boards[currentMove]);
7913             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7914             boards[currentMove][toY][toX] = q;
7915             DisplayMessage("Click in holdings to choose piece", "");
7916             return;
7917         }
7918         PromotionPopUp(promoChoice);
7919     } else {
7920         int oldMove = currentMove;
7921         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7922         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7923         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7924         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7925         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7926            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7927             DrawPosition(TRUE, boards[currentMove]);
7928         fromX = fromY = -1;
7929         flashing = 0;
7930     }
7931     appData.animate = saveAnimate;
7932     if (appData.animate || appData.animateDragging) {
7933         /* Undo animation damage if needed */
7934 //      DrawPosition(FALSE, NULL);
7935     }
7936 }
7937
7938 int
7939 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7940 {   // front-end-free part taken out of PieceMenuPopup
7941     int whichMenu; int xSqr, ySqr;
7942
7943     if(seekGraphUp) { // [HGM] seekgraph
7944         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7945         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7946         return -2;
7947     }
7948
7949     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7950          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7951         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7952         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7953         if(action == Press)   {
7954             originalFlip = flipView;
7955             flipView = !flipView; // temporarily flip board to see game from partners perspective
7956             DrawPosition(TRUE, partnerBoard);
7957             DisplayMessage(partnerStatus, "");
7958             partnerUp = TRUE;
7959         } else if(action == Release) {
7960             flipView = originalFlip;
7961             DrawPosition(TRUE, boards[currentMove]);
7962             partnerUp = FALSE;
7963         }
7964         return -2;
7965     }
7966
7967     xSqr = EventToSquare(x, BOARD_WIDTH);
7968     ySqr = EventToSquare(y, BOARD_HEIGHT);
7969     if (action == Release) {
7970         if(pieceSweep != EmptySquare) {
7971             EditPositionMenuEvent(pieceSweep, toX, toY);
7972             pieceSweep = EmptySquare;
7973         } else UnLoadPV(); // [HGM] pv
7974     }
7975     if (action != Press) return -2; // return code to be ignored
7976     switch (gameMode) {
7977       case IcsExamining:
7978         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7979       case EditPosition:
7980         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7981         if (xSqr < 0 || ySqr < 0) return -1;
7982         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7983         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7984         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7985         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7986         NextPiece(0);
7987         return 2; // grab
7988       case IcsObserving:
7989         if(!appData.icsEngineAnalyze) return -1;
7990       case IcsPlayingWhite:
7991       case IcsPlayingBlack:
7992         if(!appData.zippyPlay) goto noZip;
7993       case AnalyzeMode:
7994       case AnalyzeFile:
7995       case MachinePlaysWhite:
7996       case MachinePlaysBlack:
7997       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7998         if (!appData.dropMenu) {
7999           LoadPV(x, y);
8000           return 2; // flag front-end to grab mouse events
8001         }
8002         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8003            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8004       case EditGame:
8005       noZip:
8006         if (xSqr < 0 || ySqr < 0) return -1;
8007         if (!appData.dropMenu || appData.testLegality &&
8008             gameInfo.variant != VariantBughouse &&
8009             gameInfo.variant != VariantCrazyhouse) return -1;
8010         whichMenu = 1; // drop menu
8011         break;
8012       default:
8013         return -1;
8014     }
8015
8016     if (((*fromX = xSqr) < 0) ||
8017         ((*fromY = ySqr) < 0)) {
8018         *fromX = *fromY = -1;
8019         return -1;
8020     }
8021     if (flipView)
8022       *fromX = BOARD_WIDTH - 1 - *fromX;
8023     else
8024       *fromY = BOARD_HEIGHT - 1 - *fromY;
8025
8026     return whichMenu;
8027 }
8028
8029 void
8030 Wheel (int dir, int x, int y)
8031 {
8032     if(gameMode == EditPosition) {
8033         int xSqr = EventToSquare(x, BOARD_WIDTH);
8034         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8035         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8036         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8037         do {
8038             boards[currentMove][ySqr][xSqr] += dir;
8039             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8040             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8041         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8042         DrawPosition(FALSE, boards[currentMove]);
8043     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8044 }
8045
8046 void
8047 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8048 {
8049 //    char * hint = lastHint;
8050     FrontEndProgramStats stats;
8051
8052     stats.which = cps == &first ? 0 : 1;
8053     stats.depth = cpstats->depth;
8054     stats.nodes = cpstats->nodes;
8055     stats.score = cpstats->score;
8056     stats.time = cpstats->time;
8057     stats.pv = cpstats->movelist;
8058     stats.hint = lastHint;
8059     stats.an_move_index = 0;
8060     stats.an_move_count = 0;
8061
8062     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8063         stats.hint = cpstats->move_name;
8064         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8065         stats.an_move_count = cpstats->nr_moves;
8066     }
8067
8068     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
8069
8070     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8071         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8072
8073     SetProgramStats( &stats );
8074 }
8075
8076 void
8077 ClearEngineOutputPane (int which)
8078 {
8079     static FrontEndProgramStats dummyStats;
8080     dummyStats.which = which;
8081     dummyStats.pv = "#";
8082     SetProgramStats( &dummyStats );
8083 }
8084
8085 #define MAXPLAYERS 500
8086
8087 char *
8088 TourneyStandings (int display)
8089 {
8090     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8091     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8092     char result, *p, *names[MAXPLAYERS];
8093
8094     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8095         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8096     names[0] = p = strdup(appData.participants);
8097     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8098
8099     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8100
8101     while(result = appData.results[nr]) {
8102         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8103         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8104         wScore = bScore = 0;
8105         switch(result) {
8106           case '+': wScore = 2; break;
8107           case '-': bScore = 2; break;
8108           case '=': wScore = bScore = 1; break;
8109           case ' ':
8110           case '*': return strdup("busy"); // tourney not finished
8111         }
8112         score[w] += wScore;
8113         score[b] += bScore;
8114         games[w]++;
8115         games[b]++;
8116         nr++;
8117     }
8118     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8119     for(w=0; w<nPlayers; w++) {
8120         bScore = -1;
8121         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8122         ranking[w] = b; points[w] = bScore; score[b] = -2;
8123     }
8124     p = malloc(nPlayers*34+1);
8125     for(w=0; w<nPlayers && w<display; w++)
8126         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8127     free(names[0]);
8128     return p;
8129 }
8130
8131 void
8132 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8133 {       // count all piece types
8134         int p, f, r;
8135         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8136         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8137         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8138                 p = board[r][f];
8139                 pCnt[p]++;
8140                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8141                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8142                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8143                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8144                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8145                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8146         }
8147 }
8148
8149 int
8150 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8151 {
8152         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8153         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8154
8155         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8156         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8157         if(myPawns == 2 && nMine == 3) // KPP
8158             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8159         if(myPawns == 1 && nMine == 2) // KP
8160             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8161         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8162             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8163         if(myPawns) return FALSE;
8164         if(pCnt[WhiteRook+side])
8165             return pCnt[BlackRook-side] ||
8166                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8167                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8168                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8169         if(pCnt[WhiteCannon+side]) {
8170             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8171             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8172         }
8173         if(pCnt[WhiteKnight+side])
8174             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8175         return FALSE;
8176 }
8177
8178 int
8179 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8180 {
8181         VariantClass v = gameInfo.variant;
8182
8183         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8184         if(v == VariantShatranj) return TRUE; // always winnable through baring
8185         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8186         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8187
8188         if(v == VariantXiangqi) {
8189                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8190
8191                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8192                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8193                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8194                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8195                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8196                 if(stale) // we have at least one last-rank P plus perhaps C
8197                     return majors // KPKX
8198                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8199                 else // KCA*E*
8200                     return pCnt[WhiteFerz+side] // KCAK
8201                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8202                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8203                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8204
8205         } else if(v == VariantKnightmate) {
8206                 if(nMine == 1) return FALSE;
8207                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8208         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8209                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8210
8211                 if(nMine == 1) return FALSE; // bare King
8212                 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
8213                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8214                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8215                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8216                 if(pCnt[WhiteKnight+side])
8217                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8218                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8219                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8220                 if(nBishops)
8221                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8222                 if(pCnt[WhiteAlfil+side])
8223                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8224                 if(pCnt[WhiteWazir+side])
8225                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8226         }
8227
8228         return TRUE;
8229 }
8230
8231 int
8232 CompareWithRights (Board b1, Board b2)
8233 {
8234     int rights = 0;
8235     if(!CompareBoards(b1, b2)) return FALSE;
8236     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8237     /* compare castling rights */
8238     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8239            rights++; /* King lost rights, while rook still had them */
8240     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8241         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8242            rights++; /* but at least one rook lost them */
8243     }
8244     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8245            rights++;
8246     if( b1[CASTLING][5] != NoRights ) {
8247         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8248            rights++;
8249     }
8250     return rights == 0;
8251 }
8252
8253 int
8254 Adjudicate (ChessProgramState *cps)
8255 {       // [HGM] some adjudications useful with buggy engines
8256         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8257         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8258         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8259         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8260         int k, drop, count = 0; static int bare = 1;
8261         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8262         Boolean canAdjudicate = !appData.icsActive;
8263
8264         // most tests only when we understand the game, i.e. legality-checking on
8265             if( appData.testLegality )
8266             {   /* [HGM] Some more adjudications for obstinate engines */
8267                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8268                 static int moveCount = 6;
8269                 ChessMove result;
8270                 char *reason = NULL;
8271
8272                 /* Count what is on board. */
8273                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8274
8275                 /* Some material-based adjudications that have to be made before stalemate test */
8276                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8277                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8278                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8279                      if(canAdjudicate && appData.checkMates) {
8280                          if(engineOpponent)
8281                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8282                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8283                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8284                          return 1;
8285                      }
8286                 }
8287
8288                 /* Bare King in Shatranj (loses) or Losers (wins) */
8289                 if( nrW == 1 || nrB == 1) {
8290                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8291                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8292                      if(canAdjudicate && appData.checkMates) {
8293                          if(engineOpponent)
8294                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8295                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8296                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8297                          return 1;
8298                      }
8299                   } else
8300                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8301                   {    /* bare King */
8302                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8303                         if(canAdjudicate && appData.checkMates) {
8304                             /* but only adjudicate if adjudication enabled */
8305                             if(engineOpponent)
8306                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8307                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8308                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8309                             return 1;
8310                         }
8311                   }
8312                 } else bare = 1;
8313
8314
8315             // don't wait for engine to announce game end if we can judge ourselves
8316             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8317               case MT_CHECK:
8318                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8319                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8320                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8321                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8322                             checkCnt++;
8323                         if(checkCnt >= 2) {
8324                             reason = "Xboard adjudication: 3rd check";
8325                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8326                             break;
8327                         }
8328                     }
8329                 }
8330               case MT_NONE:
8331               default:
8332                 break;
8333               case MT_STEALMATE:
8334               case MT_STALEMATE:
8335               case MT_STAINMATE:
8336                 reason = "Xboard adjudication: Stalemate";
8337                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8338                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8339                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8340                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8341                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8342                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8343                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8344                                                                         EP_CHECKMATE : EP_WINS);
8345                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8346                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8347                 }
8348                 break;
8349               case MT_CHECKMATE:
8350                 reason = "Xboard adjudication: Checkmate";
8351                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8352                 if(gameInfo.variant == VariantShogi) {
8353                     if(forwardMostMove > backwardMostMove
8354                        && moveList[forwardMostMove-1][1] == '@'
8355                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8356                         reason = "XBoard adjudication: pawn-drop mate";
8357                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8358                     }
8359                 }
8360                 break;
8361             }
8362
8363                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8364                     case EP_STALEMATE:
8365                         result = GameIsDrawn; break;
8366                     case EP_CHECKMATE:
8367                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8368                     case EP_WINS:
8369                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8370                     default:
8371                         result = EndOfFile;
8372                 }
8373                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8374                     if(engineOpponent)
8375                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8376                     GameEnds( result, reason, GE_XBOARD );
8377                     return 1;
8378                 }
8379
8380                 /* Next absolutely insufficient mating material. */
8381                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8382                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8383                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8384
8385                      /* always flag draws, for judging claims */
8386                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8387
8388                      if(canAdjudicate && appData.materialDraws) {
8389                          /* but only adjudicate them if adjudication enabled */
8390                          if(engineOpponent) {
8391                            SendToProgram("force\n", engineOpponent); // suppress reply
8392                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8393                          }
8394                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8395                          return 1;
8396                      }
8397                 }
8398
8399                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8400                 if(gameInfo.variant == VariantXiangqi ?
8401                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8402                  : nrW + nrB == 4 &&
8403                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8404                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8405                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8406                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8407                    ) ) {
8408                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8409                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8410                           if(engineOpponent) {
8411                             SendToProgram("force\n", engineOpponent); // suppress reply
8412                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8413                           }
8414                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8415                           return 1;
8416                      }
8417                 } else moveCount = 6;
8418             }
8419
8420         // Repetition draws and 50-move rule can be applied independently of legality testing
8421
8422                 /* Check for rep-draws */
8423                 count = 0;
8424                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8425                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8426                 for(k = forwardMostMove-2;
8427                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8428                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8429                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8430                     k-=2)
8431                 {   int rights=0;
8432                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8433                         /* compare castling rights */
8434                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8435                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8436                                 rights++; /* King lost rights, while rook still had them */
8437                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8438                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8439                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8440                                    rights++; /* but at least one rook lost them */
8441                         }
8442                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8443                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8444                                 rights++;
8445                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8446                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8447                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8448                                    rights++;
8449                         }
8450                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8451                             && appData.drawRepeats > 1) {
8452                              /* adjudicate after user-specified nr of repeats */
8453                              int result = GameIsDrawn;
8454                              char *details = "XBoard adjudication: repetition draw";
8455                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8456                                 // [HGM] xiangqi: check for forbidden perpetuals
8457                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8458                                 for(m=forwardMostMove; m>k; m-=2) {
8459                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8460                                         ourPerpetual = 0; // the current mover did not always check
8461                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8462                                         hisPerpetual = 0; // the opponent did not always check
8463                                 }
8464                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8465                                                                         ourPerpetual, hisPerpetual);
8466                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8467                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8468                                     details = "Xboard adjudication: perpetual checking";
8469                                 } else
8470                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8471                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8472                                 } else
8473                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8474                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8475                                         result = BlackWins;
8476                                         details = "Xboard adjudication: repetition";
8477                                     }
8478                                 } else // it must be XQ
8479                                 // Now check for perpetual chases
8480                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8481                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8482                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8483                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8484                                         static char resdet[MSG_SIZ];
8485                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8486                                         details = resdet;
8487                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8488                                     } else
8489                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8490                                         break; // Abort repetition-checking loop.
8491                                 }
8492                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8493                              }
8494                              if(engineOpponent) {
8495                                SendToProgram("force\n", engineOpponent); // suppress reply
8496                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8497                              }
8498                              GameEnds( result, details, GE_XBOARD );
8499                              return 1;
8500                         }
8501                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8502                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8503                     }
8504                 }
8505
8506                 /* Now we test for 50-move draws. Determine ply count */
8507                 count = forwardMostMove;
8508                 /* look for last irreversble move */
8509                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8510                     count--;
8511                 /* if we hit starting position, add initial plies */
8512                 if( count == backwardMostMove )
8513                     count -= initialRulePlies;
8514                 count = forwardMostMove - count;
8515                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8516                         // adjust reversible move counter for checks in Xiangqi
8517                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8518                         if(i < backwardMostMove) i = backwardMostMove;
8519                         while(i <= forwardMostMove) {
8520                                 lastCheck = inCheck; // check evasion does not count
8521                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8522                                 if(inCheck || lastCheck) count--; // check does not count
8523                                 i++;
8524                         }
8525                 }
8526                 if( count >= 100)
8527                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8528                          /* this is used to judge if draw claims are legal */
8529                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8530                          if(engineOpponent) {
8531                            SendToProgram("force\n", engineOpponent); // suppress reply
8532                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8533                          }
8534                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8535                          return 1;
8536                 }
8537
8538                 /* if draw offer is pending, treat it as a draw claim
8539                  * when draw condition present, to allow engines a way to
8540                  * claim draws before making their move to avoid a race
8541                  * condition occurring after their move
8542                  */
8543                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8544                          char *p = NULL;
8545                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8546                              p = "Draw claim: 50-move rule";
8547                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8548                              p = "Draw claim: 3-fold repetition";
8549                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8550                              p = "Draw claim: insufficient mating material";
8551                          if( p != NULL && canAdjudicate) {
8552                              if(engineOpponent) {
8553                                SendToProgram("force\n", engineOpponent); // suppress reply
8554                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8555                              }
8556                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8557                              return 1;
8558                          }
8559                 }
8560
8561                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8562                     if(engineOpponent) {
8563                       SendToProgram("force\n", engineOpponent); // suppress reply
8564                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8565                     }
8566                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8567                     return 1;
8568                 }
8569         return 0;
8570 }
8571
8572 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8573 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8574 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8575
8576 static int
8577 BitbaseProbe ()
8578 {
8579     int pieces[10], squares[10], cnt=0, r, f, res;
8580     static int loaded;
8581     static PPROBE_EGBB probeBB;
8582     if(!appData.testLegality) return 10;
8583     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8584     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8585     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8586     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8587         ChessSquare piece = boards[forwardMostMove][r][f];
8588         int black = (piece >= BlackPawn);
8589         int type = piece - black*BlackPawn;
8590         if(piece == EmptySquare) continue;
8591         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8592         if(type == WhiteKing) type = WhiteQueen + 1;
8593         type = egbbCode[type];
8594         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8595         pieces[cnt] = type + black*6;
8596         if(++cnt > 5) return 11;
8597     }
8598     pieces[cnt] = squares[cnt] = 0;
8599     // probe EGBB
8600     if(loaded == 2) return 13; // loading failed before
8601     if(loaded == 0) {
8602         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8603         HMODULE lib;
8604         PLOAD_EGBB loadBB;
8605         loaded = 2; // prepare for failure
8606         if(!path) return 13; // no egbb installed
8607         strncpy(buf, path + 8, MSG_SIZ);
8608         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8609         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8610         lib = LoadLibrary(buf);
8611         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8612         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8613         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8614         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8615         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8616         loaded = 1; // success!
8617     }
8618     res = probeBB(forwardMostMove & 1, pieces, squares);
8619     return res > 0 ? 1 : res < 0 ? -1 : 0;
8620 }
8621
8622 char *
8623 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8624 {   // [HGM] book: this routine intercepts moves to simulate book replies
8625     char *bookHit = NULL;
8626
8627     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8628         char buf[MSG_SIZ];
8629         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8630         SendToProgram(buf, cps);
8631     }
8632     //first determine if the incoming move brings opponent into his book
8633     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8634         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8635     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8636     if(bookHit != NULL && !cps->bookSuspend) {
8637         // make sure opponent is not going to reply after receiving move to book position
8638         SendToProgram("force\n", cps);
8639         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8640     }
8641     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8642     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8643     // now arrange restart after book miss
8644     if(bookHit) {
8645         // after a book hit we never send 'go', and the code after the call to this routine
8646         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8647         char buf[MSG_SIZ], *move = bookHit;
8648         if(cps->useSAN) {
8649             int fromX, fromY, toX, toY;
8650             char promoChar;
8651             ChessMove moveType;
8652             move = buf + 30;
8653             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8654                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8655                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8656                                     PosFlags(forwardMostMove),
8657                                     fromY, fromX, toY, toX, promoChar, move);
8658             } else {
8659                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8660                 bookHit = NULL;
8661             }
8662         }
8663         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8664         SendToProgram(buf, cps);
8665         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8666     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8667         SendToProgram("go\n", cps);
8668         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8669     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8670         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8671             SendToProgram("go\n", cps);
8672         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8673     }
8674     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8675 }
8676
8677 int
8678 LoadError (char *errmess, ChessProgramState *cps)
8679 {   // unloads engine and switches back to -ncp mode if it was first
8680     if(cps->initDone) return FALSE;
8681     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8682     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8683     cps->pr = NoProc;
8684     if(cps == &first) {
8685         appData.noChessProgram = TRUE;
8686         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8687         gameMode = BeginningOfGame; ModeHighlight();
8688         SetNCPMode();
8689     }
8690     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8691     DisplayMessage("", ""); // erase waiting message
8692     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8693     return TRUE;
8694 }
8695
8696 char *savedMessage;
8697 ChessProgramState *savedState;
8698 void
8699 DeferredBookMove (void)
8700 {
8701         if(savedState->lastPing != savedState->lastPong)
8702                     ScheduleDelayedEvent(DeferredBookMove, 10);
8703         else
8704         HandleMachineMove(savedMessage, savedState);
8705 }
8706
8707 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8708 static ChessProgramState *stalledEngine;
8709 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8710
8711 void
8712 HandleMachineMove (char *message, ChessProgramState *cps)
8713 {
8714     static char firstLeg[20], legs;
8715     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8716     char realname[MSG_SIZ];
8717     int fromX, fromY, toX, toY;
8718     ChessMove moveType;
8719     char promoChar, roar;
8720     char *p, *pv=buf1;
8721     int oldError;
8722     char *bookHit;
8723
8724     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8725         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8726         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8727             DisplayError(_("Invalid pairing from pairing engine"), 0);
8728             return;
8729         }
8730         pairingReceived = 1;
8731         NextMatchGame();
8732         return; // Skim the pairing messages here.
8733     }
8734
8735     oldError = cps->userError; cps->userError = 0;
8736
8737 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8738     /*
8739      * Kludge to ignore BEL characters
8740      */
8741     while (*message == '\007') message++;
8742
8743     /*
8744      * [HGM] engine debug message: ignore lines starting with '#' character
8745      */
8746     if(cps->debug && *message == '#') return;
8747
8748     /*
8749      * Look for book output
8750      */
8751     if (cps == &first && bookRequested) {
8752         if (message[0] == '\t' || message[0] == ' ') {
8753             /* Part of the book output is here; append it */
8754             strcat(bookOutput, message);
8755             strcat(bookOutput, "  \n");
8756             return;
8757         } else if (bookOutput[0] != NULLCHAR) {
8758             /* All of book output has arrived; display it */
8759             char *p = bookOutput;
8760             while (*p != NULLCHAR) {
8761                 if (*p == '\t') *p = ' ';
8762                 p++;
8763             }
8764             DisplayInformation(bookOutput);
8765             bookRequested = FALSE;
8766             /* Fall through to parse the current output */
8767         }
8768     }
8769
8770     /*
8771      * Look for machine move.
8772      */
8773     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8774         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8775     {
8776         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8777             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8778             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8779             stalledEngine = cps;
8780             if(appData.ponderNextMove) { // bring opponent out of ponder
8781                 if(gameMode == TwoMachinesPlay) {
8782                     if(cps->other->pause)
8783                         PauseEngine(cps->other);
8784                     else
8785                         SendToProgram("easy\n", cps->other);
8786                 }
8787             }
8788             StopClocks();
8789             return;
8790         }
8791
8792       if(cps->usePing) {
8793
8794         /* This method is only useful on engines that support ping */
8795         if(abortEngineThink) {
8796             if (appData.debugMode) {
8797                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8798             }
8799             SendToProgram("undo\n", cps);
8800             return;
8801         }
8802
8803         if (cps->lastPing != cps->lastPong) {
8804             /* Extra move from before last new; ignore */
8805             if (appData.debugMode) {
8806                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8807             }
8808           return;
8809         }
8810
8811       } else {
8812
8813         int machineWhite = FALSE;
8814
8815         switch (gameMode) {
8816           case BeginningOfGame:
8817             /* Extra move from before last reset; ignore */
8818             if (appData.debugMode) {
8819                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8820             }
8821             return;
8822
8823           case EndOfGame:
8824           case IcsIdle:
8825           default:
8826             /* Extra move after we tried to stop.  The mode test is
8827                not a reliable way of detecting this problem, but it's
8828                the best we can do on engines that don't support ping.
8829             */
8830             if (appData.debugMode) {
8831                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8832                         cps->which, gameMode);
8833             }
8834             SendToProgram("undo\n", cps);
8835             return;
8836
8837           case MachinePlaysWhite:
8838           case IcsPlayingWhite:
8839             machineWhite = TRUE;
8840             break;
8841
8842           case MachinePlaysBlack:
8843           case IcsPlayingBlack:
8844             machineWhite = FALSE;
8845             break;
8846
8847           case TwoMachinesPlay:
8848             machineWhite = (cps->twoMachinesColor[0] == 'w');
8849             break;
8850         }
8851         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8852             if (appData.debugMode) {
8853                 fprintf(debugFP,
8854                         "Ignoring move out of turn by %s, gameMode %d"
8855                         ", forwardMost %d\n",
8856                         cps->which, gameMode, forwardMostMove);
8857             }
8858             return;
8859         }
8860       }
8861
8862         if(cps->alphaRank) AlphaRank(machineMove, 4);
8863
8864         // [HGM] lion: (some very limited) support for Alien protocol
8865         killX = killY = kill2X = kill2Y = -1;
8866         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8867             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8868             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8869             return;
8870         }
8871         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8872             char *q = strchr(p+1, ',');            // second comma?
8873             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8874             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8875             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8876         }
8877         if(firstLeg[0]) { // there was a previous leg;
8878             // only support case where same piece makes two step
8879             char buf[20], *p = machineMove+1, *q = buf+1, f;
8880             safeStrCpy(buf, machineMove, 20);
8881             while(isdigit(*q)) q++; // find start of to-square
8882             safeStrCpy(machineMove, firstLeg, 20);
8883             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8884             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
8885             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)
8886             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8887             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8888             firstLeg[0] = NULLCHAR; legs = 0;
8889         }
8890
8891         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8892                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8893             /* Machine move could not be parsed; ignore it. */
8894           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8895                     machineMove, _(cps->which));
8896             DisplayMoveError(buf1);
8897             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8898                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8899             if (gameMode == TwoMachinesPlay) {
8900               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8901                        buf1, GE_XBOARD);
8902             }
8903             return;
8904         }
8905
8906         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8907         /* So we have to redo legality test with true e.p. status here,  */
8908         /* to make sure an illegal e.p. capture does not slip through,   */
8909         /* to cause a forfeit on a justified illegal-move complaint      */
8910         /* of the opponent.                                              */
8911         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8912            ChessMove moveType;
8913            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8914                              fromY, fromX, toY, toX, promoChar);
8915             if(moveType == IllegalMove) {
8916               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8917                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8918                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8919                            buf1, GE_XBOARD);
8920                 return;
8921            } else if(!appData.fischerCastling)
8922            /* [HGM] Kludge to handle engines that send FRC-style castling
8923               when they shouldn't (like TSCP-Gothic) */
8924            switch(moveType) {
8925              case WhiteASideCastleFR:
8926              case BlackASideCastleFR:
8927                toX+=2;
8928                currentMoveString[2]++;
8929                break;
8930              case WhiteHSideCastleFR:
8931              case BlackHSideCastleFR:
8932                toX--;
8933                currentMoveString[2]--;
8934                break;
8935              default: ; // nothing to do, but suppresses warning of pedantic compilers
8936            }
8937         }
8938         hintRequested = FALSE;
8939         lastHint[0] = NULLCHAR;
8940         bookRequested = FALSE;
8941         /* Program may be pondering now */
8942         cps->maybeThinking = TRUE;
8943         if (cps->sendTime == 2) cps->sendTime = 1;
8944         if (cps->offeredDraw) cps->offeredDraw--;
8945
8946         /* [AS] Save move info*/
8947         pvInfoList[ forwardMostMove ].score = programStats.score;
8948         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8949         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8950
8951         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8952
8953         /* Test suites abort the 'game' after one move */
8954         if(*appData.finger) {
8955            static FILE *f;
8956            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8957            if(!f) f = fopen(appData.finger, "w");
8958            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8959            else { DisplayFatalError("Bad output file", errno, 0); return; }
8960            free(fen);
8961            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8962         }
8963         if(appData.epd) {
8964            if(solvingTime >= 0) {
8965               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8966               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8967            } else {
8968               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8969               if(solvingTime == -2) second.matchWins++;
8970            }
8971            OutputKibitz(2, buf1);
8972            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8973         }
8974
8975         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8976         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8977             int count = 0;
8978
8979             while( count < adjudicateLossPlies ) {
8980                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8981
8982                 if( count & 1 ) {
8983                     score = -score; /* Flip score for winning side */
8984                 }
8985
8986                 if( score > appData.adjudicateLossThreshold ) {
8987                     break;
8988                 }
8989
8990                 count++;
8991             }
8992
8993             if( count >= adjudicateLossPlies ) {
8994                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8995
8996                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8997                     "Xboard adjudication",
8998                     GE_XBOARD );
8999
9000                 return;
9001             }
9002         }
9003
9004         if(Adjudicate(cps)) {
9005             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9006             return; // [HGM] adjudicate: for all automatic game ends
9007         }
9008
9009 #if ZIPPY
9010         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9011             first.initDone) {
9012           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9013                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9014                 SendToICS("draw ");
9015                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9016           }
9017           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9018           ics_user_moved = 1;
9019           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9020                 char buf[3*MSG_SIZ];
9021
9022                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9023                         programStats.score / 100.,
9024                         programStats.depth,
9025                         programStats.time / 100.,
9026                         (unsigned int)programStats.nodes,
9027                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9028                         programStats.movelist);
9029                 SendToICS(buf);
9030           }
9031         }
9032 #endif
9033
9034         /* [AS] Clear stats for next move */
9035         ClearProgramStats();
9036         thinkOutput[0] = NULLCHAR;
9037         hiddenThinkOutputState = 0;
9038
9039         bookHit = NULL;
9040         if (gameMode == TwoMachinesPlay) {
9041             /* [HGM] relaying draw offers moved to after reception of move */
9042             /* and interpreting offer as claim if it brings draw condition */
9043             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9044                 SendToProgram("draw\n", cps->other);
9045             }
9046             if (cps->other->sendTime) {
9047                 SendTimeRemaining(cps->other,
9048                                   cps->other->twoMachinesColor[0] == 'w');
9049             }
9050             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9051             if (firstMove && !bookHit) {
9052                 firstMove = FALSE;
9053                 if (cps->other->useColors) {
9054                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9055                 }
9056                 SendToProgram("go\n", cps->other);
9057             }
9058             cps->other->maybeThinking = TRUE;
9059         }
9060
9061         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9062
9063         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9064
9065         if (!pausing && appData.ringBellAfterMoves) {
9066             if(!roar) RingBell();
9067         }
9068
9069         /*
9070          * Reenable menu items that were disabled while
9071          * machine was thinking
9072          */
9073         if (gameMode != TwoMachinesPlay)
9074             SetUserThinkingEnables();
9075
9076         // [HGM] book: after book hit opponent has received move and is now in force mode
9077         // force the book reply into it, and then fake that it outputted this move by jumping
9078         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9079         if(bookHit) {
9080                 static char bookMove[MSG_SIZ]; // a bit generous?
9081
9082                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9083                 strcat(bookMove, bookHit);
9084                 message = bookMove;
9085                 cps = cps->other;
9086                 programStats.nodes = programStats.depth = programStats.time =
9087                 programStats.score = programStats.got_only_move = 0;
9088                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9089
9090                 if(cps->lastPing != cps->lastPong) {
9091                     savedMessage = message; // args for deferred call
9092                     savedState = cps;
9093                     ScheduleDelayedEvent(DeferredBookMove, 10);
9094                     return;
9095                 }
9096                 goto FakeBookMove;
9097         }
9098
9099         return;
9100     }
9101
9102     /* Set special modes for chess engines.  Later something general
9103      *  could be added here; for now there is just one kludge feature,
9104      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9105      *  when "xboard" is given as an interactive command.
9106      */
9107     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9108         cps->useSigint = FALSE;
9109         cps->useSigterm = FALSE;
9110     }
9111     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9112       ParseFeatures(message+8, cps);
9113       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9114     }
9115
9116     if (!strncmp(message, "setup ", 6) && 
9117         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9118           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9119                                         ) { // [HGM] allow first engine to define opening position
9120       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9121       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9122       *buf = NULLCHAR;
9123       if(sscanf(message, "setup (%s", buf) == 1) {
9124         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9125         ASSIGN(appData.pieceToCharTable, buf);
9126       }
9127       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9128       if(dummy >= 3) {
9129         while(message[s] && message[s++] != ' ');
9130         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9131            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9132             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9133             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9134           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9135           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9136           startedFromSetupPosition = FALSE;
9137         }
9138       }
9139       if(startedFromSetupPosition) return;
9140       ParseFEN(boards[0], &dummy, message+s, FALSE);
9141       DrawPosition(TRUE, boards[0]);
9142       CopyBoard(initialPosition, boards[0]);
9143       startedFromSetupPosition = TRUE;
9144       return;
9145     }
9146     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9147       ChessSquare piece = WhitePawn;
9148       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9149       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9150       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9151       piece += CharToPiece(ID & 255) - WhitePawn;
9152       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9153       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9154       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9155       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9156       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9157       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9158                                                && gameInfo.variant != VariantGreat
9159                                                && gameInfo.variant != VariantFairy    ) return;
9160       if(piece < EmptySquare) {
9161         pieceDefs = TRUE;
9162         ASSIGN(pieceDesc[piece], buf1);
9163         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9164       }
9165       return;
9166     }
9167     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9168       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9169       Sweep(0);
9170       return;
9171     }
9172     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9173      * want this, I was asked to put it in, and obliged.
9174      */
9175     if (!strncmp(message, "setboard ", 9)) {
9176         Board initial_position;
9177
9178         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9179
9180         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9181             DisplayError(_("Bad FEN received from engine"), 0);
9182             return ;
9183         } else {
9184            Reset(TRUE, FALSE);
9185            CopyBoard(boards[0], initial_position);
9186            initialRulePlies = FENrulePlies;
9187            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9188            else gameMode = MachinePlaysBlack;
9189            DrawPosition(FALSE, boards[currentMove]);
9190         }
9191         return;
9192     }
9193
9194     /*
9195      * Look for communication commands
9196      */
9197     if (!strncmp(message, "telluser ", 9)) {
9198         if(message[9] == '\\' && message[10] == '\\')
9199             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9200         PlayTellSound();
9201         DisplayNote(message + 9);
9202         return;
9203     }
9204     if (!strncmp(message, "tellusererror ", 14)) {
9205         cps->userError = 1;
9206         if(message[14] == '\\' && message[15] == '\\')
9207             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9208         PlayTellSound();
9209         DisplayError(message + 14, 0);
9210         return;
9211     }
9212     if (!strncmp(message, "tellopponent ", 13)) {
9213       if (appData.icsActive) {
9214         if (loggedOn) {
9215           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9216           SendToICS(buf1);
9217         }
9218       } else {
9219         DisplayNote(message + 13);
9220       }
9221       return;
9222     }
9223     if (!strncmp(message, "tellothers ", 11)) {
9224       if (appData.icsActive) {
9225         if (loggedOn) {
9226           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9227           SendToICS(buf1);
9228         }
9229       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9230       return;
9231     }
9232     if (!strncmp(message, "tellall ", 8)) {
9233       if (appData.icsActive) {
9234         if (loggedOn) {
9235           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9236           SendToICS(buf1);
9237         }
9238       } else {
9239         DisplayNote(message + 8);
9240       }
9241       return;
9242     }
9243     if (strncmp(message, "warning", 7) == 0) {
9244         /* Undocumented feature, use tellusererror in new code */
9245         DisplayError(message, 0);
9246         return;
9247     }
9248     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9249         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9250         strcat(realname, " query");
9251         AskQuestion(realname, buf2, buf1, cps->pr);
9252         return;
9253     }
9254     /* Commands from the engine directly to ICS.  We don't allow these to be
9255      *  sent until we are logged on. Crafty kibitzes have been known to
9256      *  interfere with the login process.
9257      */
9258     if (loggedOn) {
9259         if (!strncmp(message, "tellics ", 8)) {
9260             SendToICS(message + 8);
9261             SendToICS("\n");
9262             return;
9263         }
9264         if (!strncmp(message, "tellicsnoalias ", 15)) {
9265             SendToICS(ics_prefix);
9266             SendToICS(message + 15);
9267             SendToICS("\n");
9268             return;
9269         }
9270         /* The following are for backward compatibility only */
9271         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9272             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9273             SendToICS(ics_prefix);
9274             SendToICS(message);
9275             SendToICS("\n");
9276             return;
9277         }
9278     }
9279     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9280         if(initPing == cps->lastPong) {
9281             if(gameInfo.variant == VariantUnknown) {
9282                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9283                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9284                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9285             }
9286             initPing = -1;
9287         }
9288         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9289             abortEngineThink = FALSE;
9290             DisplayMessage("", "");
9291             ThawUI();
9292         }
9293         return;
9294     }
9295     if(!strncmp(message, "highlight ", 10)) {
9296         if(appData.testLegality && !*engineVariant && appData.markers) return;
9297         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9298         return;
9299     }
9300     if(!strncmp(message, "click ", 6)) {
9301         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9302         if(appData.testLegality || !appData.oneClick) return;
9303         sscanf(message+6, "%c%d%c", &f, &y, &c);
9304         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9305         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9306         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9307         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9308         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9309         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9310             LeftClick(Release, lastLeftX, lastLeftY);
9311         controlKey  = (c == ',');
9312         LeftClick(Press, x, y);
9313         LeftClick(Release, x, y);
9314         first.highlight = f;
9315         return;
9316     }
9317     /*
9318      * If the move is illegal, cancel it and redraw the board.
9319      * Also deal with other error cases.  Matching is rather loose
9320      * here to accommodate engines written before the spec.
9321      */
9322     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9323         strncmp(message, "Error", 5) == 0) {
9324         if (StrStr(message, "name") ||
9325             StrStr(message, "rating") || StrStr(message, "?") ||
9326             StrStr(message, "result") || StrStr(message, "board") ||
9327             StrStr(message, "bk") || StrStr(message, "computer") ||
9328             StrStr(message, "variant") || StrStr(message, "hint") ||
9329             StrStr(message, "random") || StrStr(message, "depth") ||
9330             StrStr(message, "accepted")) {
9331             return;
9332         }
9333         if (StrStr(message, "protover")) {
9334           /* Program is responding to input, so it's apparently done
9335              initializing, and this error message indicates it is
9336              protocol version 1.  So we don't need to wait any longer
9337              for it to initialize and send feature commands. */
9338           FeatureDone(cps, 1);
9339           cps->protocolVersion = 1;
9340           return;
9341         }
9342         cps->maybeThinking = FALSE;
9343
9344         if (StrStr(message, "draw")) {
9345             /* Program doesn't have "draw" command */
9346             cps->sendDrawOffers = 0;
9347             return;
9348         }
9349         if (cps->sendTime != 1 &&
9350             (StrStr(message, "time") || StrStr(message, "otim"))) {
9351           /* Program apparently doesn't have "time" or "otim" command */
9352           cps->sendTime = 0;
9353           return;
9354         }
9355         if (StrStr(message, "analyze")) {
9356             cps->analysisSupport = FALSE;
9357             cps->analyzing = FALSE;
9358 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9359             EditGameEvent(); // [HGM] try to preserve loaded game
9360             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9361             DisplayError(buf2, 0);
9362             return;
9363         }
9364         if (StrStr(message, "(no matching move)st")) {
9365           /* Special kludge for GNU Chess 4 only */
9366           cps->stKludge = TRUE;
9367           SendTimeControl(cps, movesPerSession, timeControl,
9368                           timeIncrement, appData.searchDepth,
9369                           searchTime);
9370           return;
9371         }
9372         if (StrStr(message, "(no matching move)sd")) {
9373           /* Special kludge for GNU Chess 4 only */
9374           cps->sdKludge = TRUE;
9375           SendTimeControl(cps, movesPerSession, timeControl,
9376                           timeIncrement, appData.searchDepth,
9377                           searchTime);
9378           return;
9379         }
9380         if (!StrStr(message, "llegal")) {
9381             return;
9382         }
9383         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9384             gameMode == IcsIdle) return;
9385         if (forwardMostMove <= backwardMostMove) return;
9386         if (pausing) PauseEvent();
9387       if(appData.forceIllegal) {
9388             // [HGM] illegal: machine refused move; force position after move into it
9389           SendToProgram("force\n", cps);
9390           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9391                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9392                 // when black is to move, while there might be nothing on a2 or black
9393                 // might already have the move. So send the board as if white has the move.
9394                 // But first we must change the stm of the engine, as it refused the last move
9395                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9396                 if(WhiteOnMove(forwardMostMove)) {
9397                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9398                     SendBoard(cps, forwardMostMove); // kludgeless board
9399                 } else {
9400                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9401                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9402                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9403                 }
9404           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9405             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9406                  gameMode == TwoMachinesPlay)
9407               SendToProgram("go\n", cps);
9408             return;
9409       } else
9410         if (gameMode == PlayFromGameFile) {
9411             /* Stop reading this game file */
9412             gameMode = EditGame;
9413             ModeHighlight();
9414         }
9415         /* [HGM] illegal-move claim should forfeit game when Xboard */
9416         /* only passes fully legal moves                            */
9417         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9418             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9419                                 "False illegal-move claim", GE_XBOARD );
9420             return; // do not take back move we tested as valid
9421         }
9422         currentMove = forwardMostMove-1;
9423         DisplayMove(currentMove-1); /* before DisplayMoveError */
9424         SwitchClocks(forwardMostMove-1); // [HGM] race
9425         DisplayBothClocks();
9426         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9427                 parseList[currentMove], _(cps->which));
9428         DisplayMoveError(buf1);
9429         DrawPosition(FALSE, boards[currentMove]);
9430
9431         SetUserThinkingEnables();
9432         return;
9433     }
9434     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9435         /* Program has a broken "time" command that
9436            outputs a string not ending in newline.
9437            Don't use it. */
9438         cps->sendTime = 0;
9439     }
9440     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9441         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9442             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9443     }
9444
9445     /*
9446      * If chess program startup fails, exit with an error message.
9447      * Attempts to recover here are futile. [HGM] Well, we try anyway
9448      */
9449     if ((StrStr(message, "unknown host") != NULL)
9450         || (StrStr(message, "No remote directory") != NULL)
9451         || (StrStr(message, "not found") != NULL)
9452         || (StrStr(message, "No such file") != NULL)
9453         || (StrStr(message, "can't alloc") != NULL)
9454         || (StrStr(message, "Permission denied") != NULL)) {
9455
9456         cps->maybeThinking = FALSE;
9457         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9458                 _(cps->which), cps->program, cps->host, message);
9459         RemoveInputSource(cps->isr);
9460         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9461             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9462             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9463         }
9464         return;
9465     }
9466
9467     /*
9468      * Look for hint output
9469      */
9470     if (sscanf(message, "Hint: %s", buf1) == 1) {
9471         if (cps == &first && hintRequested) {
9472             hintRequested = FALSE;
9473             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9474                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9475                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9476                                     PosFlags(forwardMostMove),
9477                                     fromY, fromX, toY, toX, promoChar, buf1);
9478                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9479                 DisplayInformation(buf2);
9480             } else {
9481                 /* Hint move could not be parsed!? */
9482               snprintf(buf2, sizeof(buf2),
9483                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9484                         buf1, _(cps->which));
9485                 DisplayError(buf2, 0);
9486             }
9487         } else {
9488           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9489         }
9490         return;
9491     }
9492
9493     /*
9494      * Ignore other messages if game is not in progress
9495      */
9496     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9497         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9498
9499     /*
9500      * look for win, lose, draw, or draw offer
9501      */
9502     if (strncmp(message, "1-0", 3) == 0) {
9503         char *p, *q, *r = "";
9504         p = strchr(message, '{');
9505         if (p) {
9506             q = strchr(p, '}');
9507             if (q) {
9508                 *q = NULLCHAR;
9509                 r = p + 1;
9510             }
9511         }
9512         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9513         return;
9514     } else if (strncmp(message, "0-1", 3) == 0) {
9515         char *p, *q, *r = "";
9516         p = strchr(message, '{');
9517         if (p) {
9518             q = strchr(p, '}');
9519             if (q) {
9520                 *q = NULLCHAR;
9521                 r = p + 1;
9522             }
9523         }
9524         /* Kludge for Arasan 4.1 bug */
9525         if (strcmp(r, "Black resigns") == 0) {
9526             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9527             return;
9528         }
9529         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9530         return;
9531     } else if (strncmp(message, "1/2", 3) == 0) {
9532         char *p, *q, *r = "";
9533         p = strchr(message, '{');
9534         if (p) {
9535             q = strchr(p, '}');
9536             if (q) {
9537                 *q = NULLCHAR;
9538                 r = p + 1;
9539             }
9540         }
9541
9542         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9543         return;
9544
9545     } else if (strncmp(message, "White resign", 12) == 0) {
9546         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9547         return;
9548     } else if (strncmp(message, "Black resign", 12) == 0) {
9549         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9550         return;
9551     } else if (strncmp(message, "White matches", 13) == 0 ||
9552                strncmp(message, "Black matches", 13) == 0   ) {
9553         /* [HGM] ignore GNUShogi noises */
9554         return;
9555     } else if (strncmp(message, "White", 5) == 0 &&
9556                message[5] != '(' &&
9557                StrStr(message, "Black") == NULL) {
9558         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9559         return;
9560     } else if (strncmp(message, "Black", 5) == 0 &&
9561                message[5] != '(') {
9562         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9563         return;
9564     } else if (strcmp(message, "resign") == 0 ||
9565                strcmp(message, "computer resigns") == 0) {
9566         switch (gameMode) {
9567           case MachinePlaysBlack:
9568           case IcsPlayingBlack:
9569             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9570             break;
9571           case MachinePlaysWhite:
9572           case IcsPlayingWhite:
9573             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9574             break;
9575           case TwoMachinesPlay:
9576             if (cps->twoMachinesColor[0] == 'w')
9577               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9578             else
9579               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9580             break;
9581           default:
9582             /* can't happen */
9583             break;
9584         }
9585         return;
9586     } else if (strncmp(message, "opponent mates", 14) == 0) {
9587         switch (gameMode) {
9588           case MachinePlaysBlack:
9589           case IcsPlayingBlack:
9590             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9591             break;
9592           case MachinePlaysWhite:
9593           case IcsPlayingWhite:
9594             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9595             break;
9596           case TwoMachinesPlay:
9597             if (cps->twoMachinesColor[0] == 'w')
9598               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9599             else
9600               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9601             break;
9602           default:
9603             /* can't happen */
9604             break;
9605         }
9606         return;
9607     } else if (strncmp(message, "computer mates", 14) == 0) {
9608         switch (gameMode) {
9609           case MachinePlaysBlack:
9610           case IcsPlayingBlack:
9611             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9612             break;
9613           case MachinePlaysWhite:
9614           case IcsPlayingWhite:
9615             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9616             break;
9617           case TwoMachinesPlay:
9618             if (cps->twoMachinesColor[0] == 'w')
9619               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9620             else
9621               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9622             break;
9623           default:
9624             /* can't happen */
9625             break;
9626         }
9627         return;
9628     } else if (strncmp(message, "checkmate", 9) == 0) {
9629         if (WhiteOnMove(forwardMostMove)) {
9630             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9631         } else {
9632             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9633         }
9634         return;
9635     } else if (strstr(message, "Draw") != NULL ||
9636                strstr(message, "game is a draw") != NULL) {
9637         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9638         return;
9639     } else if (strstr(message, "offer") != NULL &&
9640                strstr(message, "draw") != NULL) {
9641 #if ZIPPY
9642         if (appData.zippyPlay && first.initDone) {
9643             /* Relay offer to ICS */
9644             SendToICS(ics_prefix);
9645             SendToICS("draw\n");
9646         }
9647 #endif
9648         cps->offeredDraw = 2; /* valid until this engine moves twice */
9649         if (gameMode == TwoMachinesPlay) {
9650             if (cps->other->offeredDraw) {
9651                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9652             /* [HGM] in two-machine mode we delay relaying draw offer      */
9653             /* until after we also have move, to see if it is really claim */
9654             }
9655         } else if (gameMode == MachinePlaysWhite ||
9656                    gameMode == MachinePlaysBlack) {
9657           if (userOfferedDraw) {
9658             DisplayInformation(_("Machine accepts your draw offer"));
9659             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9660           } else {
9661             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9662           }
9663         }
9664     }
9665
9666
9667     /*
9668      * Look for thinking output
9669      */
9670     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9671           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9672                                 ) {
9673         int plylev, mvleft, mvtot, curscore, time;
9674         char mvname[MOVE_LEN];
9675         u64 nodes; // [DM]
9676         char plyext;
9677         int ignore = FALSE;
9678         int prefixHint = FALSE;
9679         mvname[0] = NULLCHAR;
9680
9681         switch (gameMode) {
9682           case MachinePlaysBlack:
9683           case IcsPlayingBlack:
9684             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9685             break;
9686           case MachinePlaysWhite:
9687           case IcsPlayingWhite:
9688             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9689             break;
9690           case AnalyzeMode:
9691           case AnalyzeFile:
9692             break;
9693           case IcsObserving: /* [DM] icsEngineAnalyze */
9694             if (!appData.icsEngineAnalyze) ignore = TRUE;
9695             break;
9696           case TwoMachinesPlay:
9697             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9698                 ignore = TRUE;
9699             }
9700             break;
9701           default:
9702             ignore = TRUE;
9703             break;
9704         }
9705
9706         if (!ignore) {
9707             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9708             int solved = 0;
9709             buf1[0] = NULLCHAR;
9710             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9711                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9712                 char score_buf[MSG_SIZ];
9713
9714                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9715                     nodes += u64Const(0x100000000);
9716
9717                 if (plyext != ' ' && plyext != '\t') {
9718                     time *= 100;
9719                 }
9720
9721                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9722                 if( cps->scoreIsAbsolute &&
9723                     ( gameMode == MachinePlaysBlack ||
9724                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9725                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9726                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9727                      !WhiteOnMove(currentMove)
9728                     ) )
9729                 {
9730                     curscore = -curscore;
9731                 }
9732
9733                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9734
9735                 if(*bestMove) { // rememer time best EPD move was first found
9736                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9737                     ChessMove mt; char *p = bestMove;
9738                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9739                     solved = 0;
9740                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9741                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9742                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9743                             solved = 1;
9744                             break;
9745                         }
9746                         while(*p && *p != ' ') p++;
9747                         while(*p == ' ') p++;
9748                     }
9749                     if(!solved) solvingTime = -1;
9750                 }
9751                 if(*avoidMove && !solved) {
9752                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9753                     ChessMove mt; char *p = avoidMove, solved = 1;
9754                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9755                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9756                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9757                             solved = 0; solvingTime = -2;
9758                             break;
9759                         }
9760                         while(*p && *p != ' ') p++;
9761                         while(*p == ' ') p++;
9762                     }
9763                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9764                 }
9765
9766                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9767                         char buf[MSG_SIZ];
9768                         FILE *f;
9769                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9770                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9771                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9772                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9773                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9774                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9775                                 fclose(f);
9776                         }
9777                         else
9778                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9779                           DisplayError(_("failed writing PV"), 0);
9780                 }
9781
9782                 tempStats.depth = plylev;
9783                 tempStats.nodes = nodes;
9784                 tempStats.time = time;
9785                 tempStats.score = curscore;
9786                 tempStats.got_only_move = 0;
9787
9788                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9789                         int ticklen;
9790
9791                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9792                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9793                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9794                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9795                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9796                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9797                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9798                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9799                 }
9800
9801                 /* Buffer overflow protection */
9802                 if (pv[0] != NULLCHAR) {
9803                     if (strlen(pv) >= sizeof(tempStats.movelist)
9804                         && appData.debugMode) {
9805                         fprintf(debugFP,
9806                                 "PV is too long; using the first %u bytes.\n",
9807                                 (unsigned) sizeof(tempStats.movelist) - 1);
9808                     }
9809
9810                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9811                 } else {
9812                     sprintf(tempStats.movelist, " no PV\n");
9813                 }
9814
9815                 if (tempStats.seen_stat) {
9816                     tempStats.ok_to_send = 1;
9817                 }
9818
9819                 if (strchr(tempStats.movelist, '(') != NULL) {
9820                     tempStats.line_is_book = 1;
9821                     tempStats.nr_moves = 0;
9822                     tempStats.moves_left = 0;
9823                 } else {
9824                     tempStats.line_is_book = 0;
9825                 }
9826
9827                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9828                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9829
9830                 SendProgramStatsToFrontend( cps, &tempStats );
9831
9832                 /*
9833                     [AS] Protect the thinkOutput buffer from overflow... this
9834                     is only useful if buf1 hasn't overflowed first!
9835                 */
9836                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9837                 if(curscore >= MATE_SCORE) 
9838                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9839                 else if(curscore <= -MATE_SCORE) 
9840                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9841                 else
9842                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9843                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9844                          plylev,
9845                          (gameMode == TwoMachinesPlay ?
9846                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9847                          score_buf,
9848                          prefixHint ? lastHint : "",
9849                          prefixHint ? " " : "" );
9850
9851                 if( buf1[0] != NULLCHAR ) {
9852                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9853
9854                     if( strlen(pv) > max_len ) {
9855                         if( appData.debugMode) {
9856                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9857                         }
9858                         pv[max_len+1] = '\0';
9859                     }
9860
9861                     strcat( thinkOutput, pv);
9862                 }
9863
9864                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9865                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9866                     DisplayMove(currentMove - 1);
9867                 }
9868                 return;
9869
9870             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9871                 /* crafty (9.25+) says "(only move) <move>"
9872                  * if there is only 1 legal move
9873                  */
9874                 sscanf(p, "(only move) %s", buf1);
9875                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9876                 sprintf(programStats.movelist, "%s (only move)", buf1);
9877                 programStats.depth = 1;
9878                 programStats.nr_moves = 1;
9879                 programStats.moves_left = 1;
9880                 programStats.nodes = 1;
9881                 programStats.time = 1;
9882                 programStats.got_only_move = 1;
9883
9884                 /* Not really, but we also use this member to
9885                    mean "line isn't going to change" (Crafty
9886                    isn't searching, so stats won't change) */
9887                 programStats.line_is_book = 1;
9888
9889                 SendProgramStatsToFrontend( cps, &programStats );
9890
9891                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9892                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9893                     DisplayMove(currentMove - 1);
9894                 }
9895                 return;
9896             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9897                               &time, &nodes, &plylev, &mvleft,
9898                               &mvtot, mvname) >= 5) {
9899                 /* The stat01: line is from Crafty (9.29+) in response
9900                    to the "." command */
9901                 programStats.seen_stat = 1;
9902                 cps->maybeThinking = TRUE;
9903
9904                 if (programStats.got_only_move || !appData.periodicUpdates)
9905                   return;
9906
9907                 programStats.depth = plylev;
9908                 programStats.time = time;
9909                 programStats.nodes = nodes;
9910                 programStats.moves_left = mvleft;
9911                 programStats.nr_moves = mvtot;
9912                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9913                 programStats.ok_to_send = 1;
9914                 programStats.movelist[0] = '\0';
9915
9916                 SendProgramStatsToFrontend( cps, &programStats );
9917
9918                 return;
9919
9920             } else if (strncmp(message,"++",2) == 0) {
9921                 /* Crafty 9.29+ outputs this */
9922                 programStats.got_fail = 2;
9923                 return;
9924
9925             } else if (strncmp(message,"--",2) == 0) {
9926                 /* Crafty 9.29+ outputs this */
9927                 programStats.got_fail = 1;
9928                 return;
9929
9930             } else if (thinkOutput[0] != NULLCHAR &&
9931                        strncmp(message, "    ", 4) == 0) {
9932                 unsigned message_len;
9933
9934                 p = message;
9935                 while (*p && *p == ' ') p++;
9936
9937                 message_len = strlen( p );
9938
9939                 /* [AS] Avoid buffer overflow */
9940                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9941                     strcat(thinkOutput, " ");
9942                     strcat(thinkOutput, p);
9943                 }
9944
9945                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9946                     strcat(programStats.movelist, " ");
9947                     strcat(programStats.movelist, p);
9948                 }
9949
9950                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9951                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9952                     DisplayMove(currentMove - 1);
9953                 }
9954                 return;
9955             }
9956         }
9957         else {
9958             buf1[0] = NULLCHAR;
9959
9960             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9961                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9962             {
9963                 ChessProgramStats cpstats;
9964
9965                 if (plyext != ' ' && plyext != '\t') {
9966                     time *= 100;
9967                 }
9968
9969                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9970                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9971                     curscore = -curscore;
9972                 }
9973
9974                 cpstats.depth = plylev;
9975                 cpstats.nodes = nodes;
9976                 cpstats.time = time;
9977                 cpstats.score = curscore;
9978                 cpstats.got_only_move = 0;
9979                 cpstats.movelist[0] = '\0';
9980
9981                 if (buf1[0] != NULLCHAR) {
9982                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9983                 }
9984
9985                 cpstats.ok_to_send = 0;
9986                 cpstats.line_is_book = 0;
9987                 cpstats.nr_moves = 0;
9988                 cpstats.moves_left = 0;
9989
9990                 SendProgramStatsToFrontend( cps, &cpstats );
9991             }
9992         }
9993     }
9994 }
9995
9996
9997 /* Parse a game score from the character string "game", and
9998    record it as the history of the current game.  The game
9999    score is NOT assumed to start from the standard position.
10000    The display is not updated in any way.
10001    */
10002 void
10003 ParseGameHistory (char *game)
10004 {
10005     ChessMove moveType;
10006     int fromX, fromY, toX, toY, boardIndex;
10007     char promoChar;
10008     char *p, *q;
10009     char buf[MSG_SIZ];
10010
10011     if (appData.debugMode)
10012       fprintf(debugFP, "Parsing game history: %s\n", game);
10013
10014     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10015     gameInfo.site = StrSave(appData.icsHost);
10016     gameInfo.date = PGNDate();
10017     gameInfo.round = StrSave("-");
10018
10019     /* Parse out names of players */
10020     while (*game == ' ') game++;
10021     p = buf;
10022     while (*game != ' ') *p++ = *game++;
10023     *p = NULLCHAR;
10024     gameInfo.white = StrSave(buf);
10025     while (*game == ' ') game++;
10026     p = buf;
10027     while (*game != ' ' && *game != '\n') *p++ = *game++;
10028     *p = NULLCHAR;
10029     gameInfo.black = StrSave(buf);
10030
10031     /* Parse moves */
10032     boardIndex = blackPlaysFirst ? 1 : 0;
10033     yynewstr(game);
10034     for (;;) {
10035         yyboardindex = boardIndex;
10036         moveType = (ChessMove) Myylex();
10037         switch (moveType) {
10038           case IllegalMove:             /* maybe suicide chess, etc. */
10039   if (appData.debugMode) {
10040     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10041     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10042     setbuf(debugFP, NULL);
10043   }
10044           case WhitePromotion:
10045           case BlackPromotion:
10046           case WhiteNonPromotion:
10047           case BlackNonPromotion:
10048           case NormalMove:
10049           case FirstLeg:
10050           case WhiteCapturesEnPassant:
10051           case BlackCapturesEnPassant:
10052           case WhiteKingSideCastle:
10053           case WhiteQueenSideCastle:
10054           case BlackKingSideCastle:
10055           case BlackQueenSideCastle:
10056           case WhiteKingSideCastleWild:
10057           case WhiteQueenSideCastleWild:
10058           case BlackKingSideCastleWild:
10059           case BlackQueenSideCastleWild:
10060           /* PUSH Fabien */
10061           case WhiteHSideCastleFR:
10062           case WhiteASideCastleFR:
10063           case BlackHSideCastleFR:
10064           case BlackASideCastleFR:
10065           /* POP Fabien */
10066             fromX = currentMoveString[0] - AAA;
10067             fromY = currentMoveString[1] - ONE;
10068             toX = currentMoveString[2] - AAA;
10069             toY = currentMoveString[3] - ONE;
10070             promoChar = currentMoveString[4];
10071             break;
10072           case WhiteDrop:
10073           case BlackDrop:
10074             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10075             fromX = moveType == WhiteDrop ?
10076               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10077             (int) CharToPiece(ToLower(currentMoveString[0]));
10078             fromY = DROP_RANK;
10079             toX = currentMoveString[2] - AAA;
10080             toY = currentMoveString[3] - ONE;
10081             promoChar = NULLCHAR;
10082             break;
10083           case AmbiguousMove:
10084             /* bug? */
10085             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10086   if (appData.debugMode) {
10087     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10088     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10089     setbuf(debugFP, NULL);
10090   }
10091             DisplayError(buf, 0);
10092             return;
10093           case ImpossibleMove:
10094             /* bug? */
10095             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10096   if (appData.debugMode) {
10097     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10098     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10099     setbuf(debugFP, NULL);
10100   }
10101             DisplayError(buf, 0);
10102             return;
10103           case EndOfFile:
10104             if (boardIndex < backwardMostMove) {
10105                 /* Oops, gap.  How did that happen? */
10106                 DisplayError(_("Gap in move list"), 0);
10107                 return;
10108             }
10109             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10110             if (boardIndex > forwardMostMove) {
10111                 forwardMostMove = boardIndex;
10112             }
10113             return;
10114           case ElapsedTime:
10115             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10116                 strcat(parseList[boardIndex-1], " ");
10117                 strcat(parseList[boardIndex-1], yy_text);
10118             }
10119             continue;
10120           case Comment:
10121           case PGNTag:
10122           case NAG:
10123           default:
10124             /* ignore */
10125             continue;
10126           case WhiteWins:
10127           case BlackWins:
10128           case GameIsDrawn:
10129           case GameUnfinished:
10130             if (gameMode == IcsExamining) {
10131                 if (boardIndex < backwardMostMove) {
10132                     /* Oops, gap.  How did that happen? */
10133                     return;
10134                 }
10135                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10136                 return;
10137             }
10138             gameInfo.result = moveType;
10139             p = strchr(yy_text, '{');
10140             if (p == NULL) p = strchr(yy_text, '(');
10141             if (p == NULL) {
10142                 p = yy_text;
10143                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10144             } else {
10145                 q = strchr(p, *p == '{' ? '}' : ')');
10146                 if (q != NULL) *q = NULLCHAR;
10147                 p++;
10148             }
10149             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10150             gameInfo.resultDetails = StrSave(p);
10151             continue;
10152         }
10153         if (boardIndex >= forwardMostMove &&
10154             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10155             backwardMostMove = blackPlaysFirst ? 1 : 0;
10156             return;
10157         }
10158         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10159                                  fromY, fromX, toY, toX, promoChar,
10160                                  parseList[boardIndex]);
10161         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10162         /* currentMoveString is set as a side-effect of yylex */
10163         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10164         strcat(moveList[boardIndex], "\n");
10165         boardIndex++;
10166         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10167         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10168           case MT_NONE:
10169           case MT_STALEMATE:
10170           default:
10171             break;
10172           case MT_CHECK:
10173             if(!IS_SHOGI(gameInfo.variant))
10174                 strcat(parseList[boardIndex - 1], "+");
10175             break;
10176           case MT_CHECKMATE:
10177           case MT_STAINMATE:
10178             strcat(parseList[boardIndex - 1], "#");
10179             break;
10180         }
10181     }
10182 }
10183
10184
10185 /* Apply a move to the given board  */
10186 void
10187 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10188 {
10189   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10190   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10191
10192     /* [HGM] compute & store e.p. status and castling rights for new position */
10193     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10194
10195       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10196       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10197       board[EP_STATUS] = EP_NONE;
10198       board[EP_FILE] = board[EP_RANK] = 100;
10199
10200   if (fromY == DROP_RANK) {
10201         /* must be first */
10202         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10203             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10204             return;
10205         }
10206         piece = board[toY][toX] = (ChessSquare) fromX;
10207   } else {
10208 //      ChessSquare victim;
10209       int i;
10210
10211       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10212 //           victim = board[killY][killX],
10213            killed = board[killY][killX],
10214            board[killY][killX] = EmptySquare,
10215            board[EP_STATUS] = EP_CAPTURE;
10216            if( kill2X >= 0 && kill2Y >= 0)
10217              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10218       }
10219
10220       if( board[toY][toX] != EmptySquare ) {
10221            board[EP_STATUS] = EP_CAPTURE;
10222            if( (fromX != toX || fromY != toY) && // not igui!
10223                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10224                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10225                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10226            }
10227       }
10228
10229       pawn = board[fromY][fromX];
10230       if( pawn == WhiteLance || pawn == BlackLance ) {
10231            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10232                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10233                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10234            }
10235       }
10236       if( pawn == WhitePawn ) {
10237            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10238                board[EP_STATUS] = EP_PAWN_MOVE;
10239            if( toY-fromY>=2) {
10240                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10241                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10242                         gameInfo.variant != VariantBerolina || toX < fromX)
10243                       board[EP_STATUS] = toX | berolina;
10244                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10245                         gameInfo.variant != VariantBerolina || toX > fromX)
10246                       board[EP_STATUS] = toX;
10247            }
10248       } else
10249       if( pawn == BlackPawn ) {
10250            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10251                board[EP_STATUS] = EP_PAWN_MOVE;
10252            if( toY-fromY<= -2) {
10253                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10254                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10255                         gameInfo.variant != VariantBerolina || toX < fromX)
10256                       board[EP_STATUS] = toX | berolina;
10257                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10258                         gameInfo.variant != VariantBerolina || toX > fromX)
10259                       board[EP_STATUS] = toX;
10260            }
10261        }
10262
10263        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10264        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10265        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10266        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10267
10268        for(i=0; i<nrCastlingRights; i++) {
10269            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10270               board[CASTLING][i] == toX   && castlingRank[i] == toY
10271              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10272        }
10273
10274        if(gameInfo.variant == VariantSChess) { // update virginity
10275            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10276            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10277            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10278            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10279        }
10280
10281      if (fromX == toX && fromY == toY && killX < 0) return;
10282
10283      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10284      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10285      if(gameInfo.variant == VariantKnightmate)
10286          king += (int) WhiteUnicorn - (int) WhiteKing;
10287
10288     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10289        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10290         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10291         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10292         board[EP_STATUS] = EP_NONE; // capture was fake!
10293     } else
10294     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10295         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10296         board[toY][toX] = piece;
10297         board[EP_STATUS] = EP_NONE; // capture was fake!
10298     } else
10299     /* Code added by Tord: */
10300     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10301     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10302         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10303       board[EP_STATUS] = EP_NONE; // capture was fake!
10304       board[fromY][fromX] = EmptySquare;
10305       board[toY][toX] = EmptySquare;
10306       if((toX > fromX) != (piece == WhiteRook)) {
10307         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10308       } else {
10309         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10310       }
10311     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10312                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10313       board[EP_STATUS] = EP_NONE;
10314       board[fromY][fromX] = EmptySquare;
10315       board[toY][toX] = EmptySquare;
10316       if((toX > fromX) != (piece == BlackRook)) {
10317         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10318       } else {
10319         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10320       }
10321     /* End of code added by Tord */
10322
10323     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10324         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10325         board[toY][toX] = piece;
10326     } else if (board[fromY][fromX] == king
10327         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10328         && toY == fromY && toX > fromX+1) {
10329         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10330                                                                                              ; // castle with nearest piece
10331         board[fromY][toX-1] = board[fromY][rookX];
10332         board[fromY][rookX] = EmptySquare;
10333         board[fromY][fromX] = EmptySquare;
10334         board[toY][toX] = king;
10335     } else if (board[fromY][fromX] == king
10336         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10337                && toY == fromY && toX < fromX-1) {
10338         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10339                                                                                   ; // castle with nearest piece
10340         board[fromY][toX+1] = board[fromY][rookX];
10341         board[fromY][rookX] = EmptySquare;
10342         board[fromY][fromX] = EmptySquare;
10343         board[toY][toX] = king;
10344     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10345                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10346                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10347                ) {
10348         /* white pawn promotion */
10349         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10350         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10351             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10352         board[fromY][fromX] = EmptySquare;
10353     } else if ((fromY >= BOARD_HEIGHT>>1)
10354                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10355                && (toX != fromX)
10356                && gameInfo.variant != VariantXiangqi
10357                && gameInfo.variant != VariantBerolina
10358                && (pawn == WhitePawn)
10359                && (board[toY][toX] == EmptySquare)) {
10360         board[fromY][fromX] = EmptySquare;
10361         board[toY][toX] = piece;
10362         if(toY == epRank - 128 + 1)
10363             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10364         else
10365             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10366     } else if ((fromY == BOARD_HEIGHT-4)
10367                && (toX == fromX)
10368                && gameInfo.variant == VariantBerolina
10369                && (board[fromY][fromX] == WhitePawn)
10370                && (board[toY][toX] == EmptySquare)) {
10371         board[fromY][fromX] = EmptySquare;
10372         board[toY][toX] = WhitePawn;
10373         if(oldEP & EP_BEROLIN_A) {
10374                 captured = board[fromY][fromX-1];
10375                 board[fromY][fromX-1] = EmptySquare;
10376         }else{  captured = board[fromY][fromX+1];
10377                 board[fromY][fromX+1] = EmptySquare;
10378         }
10379     } else if (board[fromY][fromX] == king
10380         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10381                && toY == fromY && toX > fromX+1) {
10382         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10383                                                                                              ;
10384         board[fromY][toX-1] = board[fromY][rookX];
10385         board[fromY][rookX] = EmptySquare;
10386         board[fromY][fromX] = EmptySquare;
10387         board[toY][toX] = king;
10388     } else if (board[fromY][fromX] == king
10389         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10390                && toY == fromY && toX < fromX-1) {
10391         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10392                                                                                 ;
10393         board[fromY][toX+1] = board[fromY][rookX];
10394         board[fromY][rookX] = EmptySquare;
10395         board[fromY][fromX] = EmptySquare;
10396         board[toY][toX] = king;
10397     } else if (fromY == 7 && fromX == 3
10398                && board[fromY][fromX] == BlackKing
10399                && toY == 7 && toX == 5) {
10400         board[fromY][fromX] = EmptySquare;
10401         board[toY][toX] = BlackKing;
10402         board[fromY][7] = EmptySquare;
10403         board[toY][4] = BlackRook;
10404     } else if (fromY == 7 && fromX == 3
10405                && board[fromY][fromX] == BlackKing
10406                && toY == 7 && toX == 1) {
10407         board[fromY][fromX] = EmptySquare;
10408         board[toY][toX] = BlackKing;
10409         board[fromY][0] = EmptySquare;
10410         board[toY][2] = BlackRook;
10411     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10412                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10413                && toY < promoRank && promoChar
10414                ) {
10415         /* black pawn promotion */
10416         board[toY][toX] = CharToPiece(ToLower(promoChar));
10417         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10418             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10419         board[fromY][fromX] = EmptySquare;
10420     } else if ((fromY < BOARD_HEIGHT>>1)
10421                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10422                && (toX != fromX)
10423                && gameInfo.variant != VariantXiangqi
10424                && gameInfo.variant != VariantBerolina
10425                && (pawn == BlackPawn)
10426                && (board[toY][toX] == EmptySquare)) {
10427         board[fromY][fromX] = EmptySquare;
10428         board[toY][toX] = piece;
10429         if(toY == epRank - 128 - 1)
10430             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10431         else
10432             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10433     } else if ((fromY == 3)
10434                && (toX == fromX)
10435                && gameInfo.variant == VariantBerolina
10436                && (board[fromY][fromX] == BlackPawn)
10437                && (board[toY][toX] == EmptySquare)) {
10438         board[fromY][fromX] = EmptySquare;
10439         board[toY][toX] = BlackPawn;
10440         if(oldEP & EP_BEROLIN_A) {
10441                 captured = board[fromY][fromX-1];
10442                 board[fromY][fromX-1] = EmptySquare;
10443         }else{  captured = board[fromY][fromX+1];
10444                 board[fromY][fromX+1] = EmptySquare;
10445         }
10446     } else {
10447         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10448         board[fromY][fromX] = EmptySquare;
10449         board[toY][toX] = piece;
10450     }
10451   }
10452
10453     if (gameInfo.holdingsWidth != 0) {
10454
10455       /* !!A lot more code needs to be written to support holdings  */
10456       /* [HGM] OK, so I have written it. Holdings are stored in the */
10457       /* penultimate board files, so they are automaticlly stored   */
10458       /* in the game history.                                       */
10459       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10460                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10461         /* Delete from holdings, by decreasing count */
10462         /* and erasing image if necessary            */
10463         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10464         if(p < (int) BlackPawn) { /* white drop */
10465              p -= (int)WhitePawn;
10466                  p = PieceToNumber((ChessSquare)p);
10467              if(p >= gameInfo.holdingsSize) p = 0;
10468              if(--board[p][BOARD_WIDTH-2] <= 0)
10469                   board[p][BOARD_WIDTH-1] = EmptySquare;
10470              if((int)board[p][BOARD_WIDTH-2] < 0)
10471                         board[p][BOARD_WIDTH-2] = 0;
10472         } else {                  /* black drop */
10473              p -= (int)BlackPawn;
10474                  p = PieceToNumber((ChessSquare)p);
10475              if(p >= gameInfo.holdingsSize) p = 0;
10476              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10477                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10478              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10479                         board[BOARD_HEIGHT-1-p][1] = 0;
10480         }
10481       }
10482       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10483           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10484         /* [HGM] holdings: Add to holdings, if holdings exist */
10485         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10486                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10487                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10488         }
10489         p = (int) captured;
10490         if (p >= (int) BlackPawn) {
10491           p -= (int)BlackPawn;
10492           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10493                   /* Restore shogi-promoted piece to its original  first */
10494                   captured = (ChessSquare) (DEMOTED(captured));
10495                   p = DEMOTED(p);
10496           }
10497           p = PieceToNumber((ChessSquare)p);
10498           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10499           board[p][BOARD_WIDTH-2]++;
10500           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10501         } else {
10502           p -= (int)WhitePawn;
10503           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10504                   captured = (ChessSquare) (DEMOTED(captured));
10505                   p = DEMOTED(p);
10506           }
10507           p = PieceToNumber((ChessSquare)p);
10508           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10509           board[BOARD_HEIGHT-1-p][1]++;
10510           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10511         }
10512       }
10513     } else if (gameInfo.variant == VariantAtomic) {
10514       if (captured != EmptySquare) {
10515         int y, x;
10516         for (y = toY-1; y <= toY+1; y++) {
10517           for (x = toX-1; x <= toX+1; x++) {
10518             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10519                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10520               board[y][x] = EmptySquare;
10521             }
10522           }
10523         }
10524         board[toY][toX] = EmptySquare;
10525       }
10526     }
10527
10528     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10529         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10530     } else
10531     if(promoChar == '+') {
10532         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10533         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10534         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10535           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10536     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10537         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10538         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10539            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10540         board[toY][toX] = newPiece;
10541     }
10542     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10543                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10544         // [HGM] superchess: take promotion piece out of holdings
10545         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10546         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10547             if(!--board[k][BOARD_WIDTH-2])
10548                 board[k][BOARD_WIDTH-1] = EmptySquare;
10549         } else {
10550             if(!--board[BOARD_HEIGHT-1-k][1])
10551                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10552         }
10553     }
10554 }
10555
10556 /* Updates forwardMostMove */
10557 void
10558 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10559 {
10560     int x = toX, y = toY;
10561     char *s = parseList[forwardMostMove];
10562     ChessSquare p = boards[forwardMostMove][toY][toX];
10563 //    forwardMostMove++; // [HGM] bare: moved downstream
10564
10565     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10566     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10567     (void) CoordsToAlgebraic(boards[forwardMostMove],
10568                              PosFlags(forwardMostMove),
10569                              fromY, fromX, y, x, (killX < 0)*promoChar,
10570                              s);
10571     if(kill2X >= 0 && kill2Y >= 0)
10572         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10573     if(killX >= 0 && killY >= 0)
10574         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10575                                            toX + AAA, toY + ONE - '0', promoChar);
10576
10577     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10578         int timeLeft; static int lastLoadFlag=0; int king, piece;
10579         piece = boards[forwardMostMove][fromY][fromX];
10580         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10581         if(gameInfo.variant == VariantKnightmate)
10582             king += (int) WhiteUnicorn - (int) WhiteKing;
10583         if(forwardMostMove == 0) {
10584             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10585                 fprintf(serverMoves, "%s;", UserName());
10586             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10587                 fprintf(serverMoves, "%s;", second.tidy);
10588             fprintf(serverMoves, "%s;", first.tidy);
10589             if(gameMode == MachinePlaysWhite)
10590                 fprintf(serverMoves, "%s;", UserName());
10591             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10592                 fprintf(serverMoves, "%s;", second.tidy);
10593         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10594         lastLoadFlag = loadFlag;
10595         // print base move
10596         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10597         // print castling suffix
10598         if( toY == fromY && piece == king ) {
10599             if(toX-fromX > 1)
10600                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10601             if(fromX-toX >1)
10602                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10603         }
10604         // e.p. suffix
10605         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10606              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10607              boards[forwardMostMove][toY][toX] == EmptySquare
10608              && fromX != toX && fromY != toY)
10609                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10610         // promotion suffix
10611         if(promoChar != NULLCHAR) {
10612             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10613                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10614                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10615             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10616         }
10617         if(!loadFlag) {
10618                 char buf[MOVE_LEN*2], *p; int len;
10619             fprintf(serverMoves, "/%d/%d",
10620                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10621             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10622             else                      timeLeft = blackTimeRemaining/1000;
10623             fprintf(serverMoves, "/%d", timeLeft);
10624                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10625                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10626                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10627                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10628             fprintf(serverMoves, "/%s", buf);
10629         }
10630         fflush(serverMoves);
10631     }
10632
10633     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10634         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10635       return;
10636     }
10637     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10638     if (commentList[forwardMostMove+1] != NULL) {
10639         free(commentList[forwardMostMove+1]);
10640         commentList[forwardMostMove+1] = NULL;
10641     }
10642     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10643     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10644     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10645     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10646     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10647     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10648     adjustedClock = FALSE;
10649     gameInfo.result = GameUnfinished;
10650     if (gameInfo.resultDetails != NULL) {
10651         free(gameInfo.resultDetails);
10652         gameInfo.resultDetails = NULL;
10653     }
10654     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10655                               moveList[forwardMostMove - 1]);
10656     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10657       case MT_NONE:
10658       case MT_STALEMATE:
10659       default:
10660         break;
10661       case MT_CHECK:
10662         if(!IS_SHOGI(gameInfo.variant))
10663             strcat(parseList[forwardMostMove - 1], "+");
10664         break;
10665       case MT_CHECKMATE:
10666       case MT_STAINMATE:
10667         strcat(parseList[forwardMostMove - 1], "#");
10668         break;
10669     }
10670 }
10671
10672 /* Updates currentMove if not pausing */
10673 void
10674 ShowMove (int fromX, int fromY, int toX, int toY)
10675 {
10676     int instant = (gameMode == PlayFromGameFile) ?
10677         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10678     if(appData.noGUI) return;
10679     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10680         if (!instant) {
10681             if (forwardMostMove == currentMove + 1) {
10682                 AnimateMove(boards[forwardMostMove - 1],
10683                             fromX, fromY, toX, toY);
10684             }
10685         }
10686         currentMove = forwardMostMove;
10687     }
10688
10689     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10690
10691     if (instant) return;
10692
10693     DisplayMove(currentMove - 1);
10694     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10695             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10696                 SetHighlights(fromX, fromY, toX, toY);
10697             }
10698     }
10699     DrawPosition(FALSE, boards[currentMove]);
10700     DisplayBothClocks();
10701     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10702 }
10703
10704 void
10705 SendEgtPath (ChessProgramState *cps)
10706 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10707         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10708
10709         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10710
10711         while(*p) {
10712             char c, *q = name+1, *r, *s;
10713
10714             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10715             while(*p && *p != ',') *q++ = *p++;
10716             *q++ = ':'; *q = 0;
10717             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10718                 strcmp(name, ",nalimov:") == 0 ) {
10719                 // take nalimov path from the menu-changeable option first, if it is defined
10720               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10721                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10722             } else
10723             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10724                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10725                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10726                 s = r = StrStr(s, ":") + 1; // beginning of path info
10727                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10728                 c = *r; *r = 0;             // temporarily null-terminate path info
10729                     *--q = 0;               // strip of trailig ':' from name
10730                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10731                 *r = c;
10732                 SendToProgram(buf,cps);     // send egtbpath command for this format
10733             }
10734             if(*p == ',') p++; // read away comma to position for next format name
10735         }
10736 }
10737
10738 static int
10739 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10740 {
10741       int width = 8, height = 8, holdings = 0;             // most common sizes
10742       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10743       // correct the deviations default for each variant
10744       if( v == VariantXiangqi ) width = 9,  height = 10;
10745       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10746       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10747       if( v == VariantCapablanca || v == VariantCapaRandom ||
10748           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10749                                 width = 10;
10750       if( v == VariantCourier ) width = 12;
10751       if( v == VariantSuper )                            holdings = 8;
10752       if( v == VariantGreat )   width = 10,              holdings = 8;
10753       if( v == VariantSChess )                           holdings = 7;
10754       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10755       if( v == VariantChuChess) width = 10, height = 10;
10756       if( v == VariantChu )     width = 12, height = 12;
10757       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10758              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10759              holdingsSize >= 0 && holdingsSize != holdings;
10760 }
10761
10762 char variantError[MSG_SIZ];
10763
10764 char *
10765 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10766 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10767       char *p, *variant = VariantName(v);
10768       static char b[MSG_SIZ];
10769       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10770            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10771                                                holdingsSize, variant); // cook up sized variant name
10772            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10773            if(StrStr(list, b) == NULL) {
10774                // specific sized variant not known, check if general sizing allowed
10775                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10776                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10777                             boardWidth, boardHeight, holdingsSize, engine);
10778                    return NULL;
10779                }
10780                /* [HGM] here we really should compare with the maximum supported board size */
10781            }
10782       } else snprintf(b, MSG_SIZ,"%s", variant);
10783       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10784       p = StrStr(list, b);
10785       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10786       if(p == NULL) {
10787           // occurs not at all in list, or only as sub-string
10788           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10789           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10790               int l = strlen(variantError);
10791               char *q;
10792               while(p != list && p[-1] != ',') p--;
10793               q = strchr(p, ',');
10794               if(q) *q = NULLCHAR;
10795               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10796               if(q) *q= ',';
10797           }
10798           return NULL;
10799       }
10800       return b;
10801 }
10802
10803 void
10804 InitChessProgram (ChessProgramState *cps, int setup)
10805 /* setup needed to setup FRC opening position */
10806 {
10807     char buf[MSG_SIZ], *b;
10808     if (appData.noChessProgram) return;
10809     hintRequested = FALSE;
10810     bookRequested = FALSE;
10811
10812     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10813     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10814     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10815     if(cps->memSize) { /* [HGM] memory */
10816       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10817         SendToProgram(buf, cps);
10818     }
10819     SendEgtPath(cps); /* [HGM] EGT */
10820     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10821       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10822         SendToProgram(buf, cps);
10823     }
10824
10825     setboardSpoiledMachineBlack = FALSE;
10826     SendToProgram(cps->initString, cps);
10827     if (gameInfo.variant != VariantNormal &&
10828         gameInfo.variant != VariantLoadable
10829         /* [HGM] also send variant if board size non-standard */
10830         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10831
10832       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10833                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10834
10835       if (b == NULL) {
10836         VariantClass v;
10837         char c, *q = cps->variants, *p = strchr(q, ',');
10838         if(p) *p = NULLCHAR;
10839         v = StringToVariant(q);
10840         DisplayError(variantError, 0);
10841         if(v != VariantUnknown && cps == &first) {
10842             int w, h, s;
10843             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10844                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10845             ASSIGN(appData.variant, q);
10846             Reset(TRUE, FALSE);
10847         }
10848         if(p) *p = ',';
10849         return;
10850       }
10851
10852       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10853       SendToProgram(buf, cps);
10854     }
10855     currentlyInitializedVariant = gameInfo.variant;
10856
10857     /* [HGM] send opening position in FRC to first engine */
10858     if(setup) {
10859           SendToProgram("force\n", cps);
10860           SendBoard(cps, 0);
10861           /* engine is now in force mode! Set flag to wake it up after first move. */
10862           setboardSpoiledMachineBlack = 1;
10863     }
10864
10865     if (cps->sendICS) {
10866       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10867       SendToProgram(buf, cps);
10868     }
10869     cps->maybeThinking = FALSE;
10870     cps->offeredDraw = 0;
10871     if (!appData.icsActive) {
10872         SendTimeControl(cps, movesPerSession, timeControl,
10873                         timeIncrement, appData.searchDepth,
10874                         searchTime);
10875     }
10876     if (appData.showThinking
10877         // [HGM] thinking: four options require thinking output to be sent
10878         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10879                                 ) {
10880         SendToProgram("post\n", cps);
10881     }
10882     SendToProgram("hard\n", cps);
10883     if (!appData.ponderNextMove) {
10884         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10885            it without being sure what state we are in first.  "hard"
10886            is not a toggle, so that one is OK.
10887          */
10888         SendToProgram("easy\n", cps);
10889     }
10890     if (cps->usePing) {
10891       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10892       SendToProgram(buf, cps);
10893     }
10894     cps->initDone = TRUE;
10895     ClearEngineOutputPane(cps == &second);
10896 }
10897
10898
10899 void
10900 ResendOptions (ChessProgramState *cps)
10901 { // send the stored value of the options
10902   int i;
10903   char buf[MSG_SIZ];
10904   Option *opt = cps->option;
10905   for(i=0; i<cps->nrOptions; i++, opt++) {
10906       switch(opt->type) {
10907         case Spin:
10908         case Slider:
10909         case CheckBox:
10910             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10911           break;
10912         case ComboBox:
10913           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10914           break;
10915         default:
10916             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10917           break;
10918         case Button:
10919         case SaveButton:
10920           continue;
10921       }
10922       SendToProgram(buf, cps);
10923   }
10924 }
10925
10926 void
10927 StartChessProgram (ChessProgramState *cps)
10928 {
10929     char buf[MSG_SIZ];
10930     int err;
10931
10932     if (appData.noChessProgram) return;
10933     cps->initDone = FALSE;
10934
10935     if (strcmp(cps->host, "localhost") == 0) {
10936         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10937     } else if (*appData.remoteShell == NULLCHAR) {
10938         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10939     } else {
10940         if (*appData.remoteUser == NULLCHAR) {
10941           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10942                     cps->program);
10943         } else {
10944           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10945                     cps->host, appData.remoteUser, cps->program);
10946         }
10947         err = StartChildProcess(buf, "", &cps->pr);
10948     }
10949
10950     if (err != 0) {
10951       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10952         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10953         if(cps != &first) return;
10954         appData.noChessProgram = TRUE;
10955         ThawUI();
10956         SetNCPMode();
10957 //      DisplayFatalError(buf, err, 1);
10958 //      cps->pr = NoProc;
10959 //      cps->isr = NULL;
10960         return;
10961     }
10962
10963     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10964     if (cps->protocolVersion > 1) {
10965       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10966       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10967         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10968         cps->comboCnt = 0;  //                and values of combo boxes
10969       }
10970       SendToProgram(buf, cps);
10971       if(cps->reload) ResendOptions(cps);
10972     } else {
10973       SendToProgram("xboard\n", cps);
10974     }
10975 }
10976
10977 void
10978 TwoMachinesEventIfReady P((void))
10979 {
10980   static int curMess = 0;
10981   if (first.lastPing != first.lastPong) {
10982     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10983     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10984     return;
10985   }
10986   if (second.lastPing != second.lastPong) {
10987     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10988     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10989     return;
10990   }
10991   DisplayMessage("", ""); curMess = 0;
10992   TwoMachinesEvent();
10993 }
10994
10995 char *
10996 MakeName (char *template)
10997 {
10998     time_t clock;
10999     struct tm *tm;
11000     static char buf[MSG_SIZ];
11001     char *p = buf;
11002     int i;
11003
11004     clock = time((time_t *)NULL);
11005     tm = localtime(&clock);
11006
11007     while(*p++ = *template++) if(p[-1] == '%') {
11008         switch(*template++) {
11009           case 0:   *p = 0; return buf;
11010           case 'Y': i = tm->tm_year+1900; break;
11011           case 'y': i = tm->tm_year-100; break;
11012           case 'M': i = tm->tm_mon+1; break;
11013           case 'd': i = tm->tm_mday; break;
11014           case 'h': i = tm->tm_hour; break;
11015           case 'm': i = tm->tm_min; break;
11016           case 's': i = tm->tm_sec; break;
11017           default:  i = 0;
11018         }
11019         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11020     }
11021     return buf;
11022 }
11023
11024 int
11025 CountPlayers (char *p)
11026 {
11027     int n = 0;
11028     while(p = strchr(p, '\n')) p++, n++; // count participants
11029     return n;
11030 }
11031
11032 FILE *
11033 WriteTourneyFile (char *results, FILE *f)
11034 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11035     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11036     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11037         // create a file with tournament description
11038         fprintf(f, "-participants {%s}\n", appData.participants);
11039         fprintf(f, "-seedBase %d\n", appData.seedBase);
11040         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11041         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11042         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11043         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11044         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11045         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11046         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11047         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11048         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11049         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11050         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11051         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11052         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11053         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11054         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11055         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11056         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11057         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11058         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11059         fprintf(f, "-smpCores %d\n", appData.smpCores);
11060         if(searchTime > 0)
11061                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11062         else {
11063                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11064                 fprintf(f, "-tc %s\n", appData.timeControl);
11065                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11066         }
11067         fprintf(f, "-results \"%s\"\n", results);
11068     }
11069     return f;
11070 }
11071
11072 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11073
11074 void
11075 Substitute (char *participants, int expunge)
11076 {
11077     int i, changed, changes=0, nPlayers=0;
11078     char *p, *q, *r, buf[MSG_SIZ];
11079     if(participants == NULL) return;
11080     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11081     r = p = participants; q = appData.participants;
11082     while(*p && *p == *q) {
11083         if(*p == '\n') r = p+1, nPlayers++;
11084         p++; q++;
11085     }
11086     if(*p) { // difference
11087         while(*p && *p++ != '\n')
11088                                  ;
11089         while(*q && *q++ != '\n')
11090                                  ;
11091       changed = nPlayers;
11092         changes = 1 + (strcmp(p, q) != 0);
11093     }
11094     if(changes == 1) { // a single engine mnemonic was changed
11095         q = r; while(*q) nPlayers += (*q++ == '\n');
11096         p = buf; while(*r && (*p = *r++) != '\n') p++;
11097         *p = NULLCHAR;
11098         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11099         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11100         if(mnemonic[i]) { // The substitute is valid
11101             FILE *f;
11102             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11103                 flock(fileno(f), LOCK_EX);
11104                 ParseArgsFromFile(f);
11105                 fseek(f, 0, SEEK_SET);
11106                 FREE(appData.participants); appData.participants = participants;
11107                 if(expunge) { // erase results of replaced engine
11108                     int len = strlen(appData.results), w, b, dummy;
11109                     for(i=0; i<len; i++) {
11110                         Pairing(i, nPlayers, &w, &b, &dummy);
11111                         if((w == changed || b == changed) && appData.results[i] == '*') {
11112                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11113                             fclose(f);
11114                             return;
11115                         }
11116                     }
11117                     for(i=0; i<len; i++) {
11118                         Pairing(i, nPlayers, &w, &b, &dummy);
11119                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11120                     }
11121                 }
11122                 WriteTourneyFile(appData.results, f);
11123                 fclose(f); // release lock
11124                 return;
11125             }
11126         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11127     }
11128     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11129     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11130     free(participants);
11131     return;
11132 }
11133
11134 int
11135 CheckPlayers (char *participants)
11136 {
11137         int i;
11138         char buf[MSG_SIZ], *p;
11139         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11140         while(p = strchr(participants, '\n')) {
11141             *p = NULLCHAR;
11142             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11143             if(!mnemonic[i]) {
11144                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11145                 *p = '\n';
11146                 DisplayError(buf, 0);
11147                 return 1;
11148             }
11149             *p = '\n';
11150             participants = p + 1;
11151         }
11152         return 0;
11153 }
11154
11155 int
11156 CreateTourney (char *name)
11157 {
11158         FILE *f;
11159         if(matchMode && strcmp(name, appData.tourneyFile)) {
11160              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11161         }
11162         if(name[0] == NULLCHAR) {
11163             if(appData.participants[0])
11164                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11165             return 0;
11166         }
11167         f = fopen(name, "r");
11168         if(f) { // file exists
11169             ASSIGN(appData.tourneyFile, name);
11170             ParseArgsFromFile(f); // parse it
11171         } else {
11172             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11173             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11174                 DisplayError(_("Not enough participants"), 0);
11175                 return 0;
11176             }
11177             if(CheckPlayers(appData.participants)) return 0;
11178             ASSIGN(appData.tourneyFile, name);
11179             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11180             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11181         }
11182         fclose(f);
11183         appData.noChessProgram = FALSE;
11184         appData.clockMode = TRUE;
11185         SetGNUMode();
11186         return 1;
11187 }
11188
11189 int
11190 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11191 {
11192     char buf[MSG_SIZ], *p, *q;
11193     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11194     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11195     skip = !all && group[0]; // if group requested, we start in skip mode
11196     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11197         p = names; q = buf; header = 0;
11198         while(*p && *p != '\n') *q++ = *p++;
11199         *q = 0;
11200         if(*p == '\n') p++;
11201         if(buf[0] == '#') {
11202             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11203             depth++; // we must be entering a new group
11204             if(all) continue; // suppress printing group headers when complete list requested
11205             header = 1;
11206             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11207         }
11208         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11209         if(engineList[i]) free(engineList[i]);
11210         engineList[i] = strdup(buf);
11211         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11212         if(engineMnemonic[i]) free(engineMnemonic[i]);
11213         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11214             strcat(buf, " (");
11215             sscanf(q + 8, "%s", buf + strlen(buf));
11216             strcat(buf, ")");
11217         }
11218         engineMnemonic[i] = strdup(buf);
11219         i++;
11220     }
11221     engineList[i] = engineMnemonic[i] = NULL;
11222     return i;
11223 }
11224
11225 // following implemented as macro to avoid type limitations
11226 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11227
11228 void
11229 SwapEngines (int n)
11230 {   // swap settings for first engine and other engine (so far only some selected options)
11231     int h;
11232     char *p;
11233     if(n == 0) return;
11234     SWAP(directory, p)
11235     SWAP(chessProgram, p)
11236     SWAP(isUCI, h)
11237     SWAP(hasOwnBookUCI, h)
11238     SWAP(protocolVersion, h)
11239     SWAP(reuse, h)
11240     SWAP(scoreIsAbsolute, h)
11241     SWAP(timeOdds, h)
11242     SWAP(logo, p)
11243     SWAP(pgnName, p)
11244     SWAP(pvSAN, h)
11245     SWAP(engOptions, p)
11246     SWAP(engInitString, p)
11247     SWAP(computerString, p)
11248     SWAP(features, p)
11249     SWAP(fenOverride, p)
11250     SWAP(NPS, h)
11251     SWAP(accumulateTC, h)
11252     SWAP(drawDepth, h)
11253     SWAP(host, p)
11254     SWAP(pseudo, h)
11255 }
11256
11257 int
11258 GetEngineLine (char *s, int n)
11259 {
11260     int i;
11261     char buf[MSG_SIZ];
11262     extern char *icsNames;
11263     if(!s || !*s) return 0;
11264     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11265     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11266     if(!mnemonic[i]) return 0;
11267     if(n == 11) return 1; // just testing if there was a match
11268     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11269     if(n == 1) SwapEngines(n);
11270     ParseArgsFromString(buf);
11271     if(n == 1) SwapEngines(n);
11272     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11273         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11274         ParseArgsFromString(buf);
11275     }
11276     return 1;
11277 }
11278
11279 int
11280 SetPlayer (int player, char *p)
11281 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11282     int i;
11283     char buf[MSG_SIZ], *engineName;
11284     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11285     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11286     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11287     if(mnemonic[i]) {
11288         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11289         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11290         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11291         ParseArgsFromString(buf);
11292     } else { // no engine with this nickname is installed!
11293         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11294         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11295         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11296         ModeHighlight();
11297         DisplayError(buf, 0);
11298         return 0;
11299     }
11300     free(engineName);
11301     return i;
11302 }
11303
11304 char *recentEngines;
11305
11306 void
11307 RecentEngineEvent (int nr)
11308 {
11309     int n;
11310 //    SwapEngines(1); // bump first to second
11311 //    ReplaceEngine(&second, 1); // and load it there
11312     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11313     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11314     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11315         ReplaceEngine(&first, 0);
11316         FloatToFront(&appData.recentEngineList, command[n]);
11317     }
11318 }
11319
11320 int
11321 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11322 {   // determine players from game number
11323     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11324
11325     if(appData.tourneyType == 0) {
11326         roundsPerCycle = (nPlayers - 1) | 1;
11327         pairingsPerRound = nPlayers / 2;
11328     } else if(appData.tourneyType > 0) {
11329         roundsPerCycle = nPlayers - appData.tourneyType;
11330         pairingsPerRound = appData.tourneyType;
11331     }
11332     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11333     gamesPerCycle = gamesPerRound * roundsPerCycle;
11334     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11335     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11336     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11337     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11338     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11339     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11340
11341     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11342     if(appData.roundSync) *syncInterval = gamesPerRound;
11343
11344     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11345
11346     if(appData.tourneyType == 0) {
11347         if(curPairing == (nPlayers-1)/2 ) {
11348             *whitePlayer = curRound;
11349             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11350         } else {
11351             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11352             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11353             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11354             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11355         }
11356     } else if(appData.tourneyType > 1) {
11357         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11358         *whitePlayer = curRound + appData.tourneyType;
11359     } else if(appData.tourneyType > 0) {
11360         *whitePlayer = curPairing;
11361         *blackPlayer = curRound + appData.tourneyType;
11362     }
11363
11364     // take care of white/black alternation per round.
11365     // For cycles and games this is already taken care of by default, derived from matchGame!
11366     return curRound & 1;
11367 }
11368
11369 int
11370 NextTourneyGame (int nr, int *swapColors)
11371 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11372     char *p, *q;
11373     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11374     FILE *tf;
11375     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11376     tf = fopen(appData.tourneyFile, "r");
11377     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11378     ParseArgsFromFile(tf); fclose(tf);
11379     InitTimeControls(); // TC might be altered from tourney file
11380
11381     nPlayers = CountPlayers(appData.participants); // count participants
11382     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11383     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11384
11385     if(syncInterval) {
11386         p = q = appData.results;
11387         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11388         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11389             DisplayMessage(_("Waiting for other game(s)"),"");
11390             waitingForGame = TRUE;
11391             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11392             return 0;
11393         }
11394         waitingForGame = FALSE;
11395     }
11396
11397     if(appData.tourneyType < 0) {
11398         if(nr>=0 && !pairingReceived) {
11399             char buf[1<<16];
11400             if(pairing.pr == NoProc) {
11401                 if(!appData.pairingEngine[0]) {
11402                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11403                     return 0;
11404                 }
11405                 StartChessProgram(&pairing); // starts the pairing engine
11406             }
11407             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11408             SendToProgram(buf, &pairing);
11409             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11410             SendToProgram(buf, &pairing);
11411             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11412         }
11413         pairingReceived = 0;                              // ... so we continue here
11414         *swapColors = 0;
11415         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11416         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11417         matchGame = 1; roundNr = nr / syncInterval + 1;
11418     }
11419
11420     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11421
11422     // redefine engines, engine dir, etc.
11423     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11424     if(first.pr == NoProc) {
11425       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11426       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11427     }
11428     if(second.pr == NoProc) {
11429       SwapEngines(1);
11430       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11431       SwapEngines(1);         // and make that valid for second engine by swapping
11432       InitEngine(&second, 1);
11433     }
11434     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11435     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11436     return OK;
11437 }
11438
11439 void
11440 NextMatchGame ()
11441 {   // performs game initialization that does not invoke engines, and then tries to start the game
11442     int res, firstWhite, swapColors = 0;
11443     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11444     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
11445         char buf[MSG_SIZ];
11446         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11447         if(strcmp(buf, currentDebugFile)) { // name has changed
11448             FILE *f = fopen(buf, "w");
11449             if(f) { // if opening the new file failed, just keep using the old one
11450                 ASSIGN(currentDebugFile, buf);
11451                 fclose(debugFP);
11452                 debugFP = f;
11453             }
11454             if(appData.serverFileName) {
11455                 if(serverFP) fclose(serverFP);
11456                 serverFP = fopen(appData.serverFileName, "w");
11457                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11458                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11459             }
11460         }
11461     }
11462     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11463     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11464     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11465     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11466     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11467     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11468     Reset(FALSE, first.pr != NoProc);
11469     res = LoadGameOrPosition(matchGame); // setup game
11470     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11471     if(!res) return; // abort when bad game/pos file
11472     if(appData.epd) {// in EPD mode we make sure first engine is to move
11473         firstWhite = !(forwardMostMove & 1);
11474         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11475         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11476     }
11477     TwoMachinesEvent();
11478 }
11479
11480 void
11481 UserAdjudicationEvent (int result)
11482 {
11483     ChessMove gameResult = GameIsDrawn;
11484
11485     if( result > 0 ) {
11486         gameResult = WhiteWins;
11487     }
11488     else if( result < 0 ) {
11489         gameResult = BlackWins;
11490     }
11491
11492     if( gameMode == TwoMachinesPlay ) {
11493         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11494     }
11495 }
11496
11497
11498 // [HGM] save: calculate checksum of game to make games easily identifiable
11499 int
11500 StringCheckSum (char *s)
11501 {
11502         int i = 0;
11503         if(s==NULL) return 0;
11504         while(*s) i = i*259 + *s++;
11505         return i;
11506 }
11507
11508 int
11509 GameCheckSum ()
11510 {
11511         int i, sum=0;
11512         for(i=backwardMostMove; i<forwardMostMove; i++) {
11513                 sum += pvInfoList[i].depth;
11514                 sum += StringCheckSum(parseList[i]);
11515                 sum += StringCheckSum(commentList[i]);
11516                 sum *= 261;
11517         }
11518         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11519         return sum + StringCheckSum(commentList[i]);
11520 } // end of save patch
11521
11522 void
11523 GameEnds (ChessMove result, char *resultDetails, int whosays)
11524 {
11525     GameMode nextGameMode;
11526     int isIcsGame;
11527     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11528
11529     if(endingGame) return; /* [HGM] crash: forbid recursion */
11530     endingGame = 1;
11531     if(twoBoards) { // [HGM] dual: switch back to one board
11532         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11533         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11534     }
11535     if (appData.debugMode) {
11536       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11537               result, resultDetails ? resultDetails : "(null)", whosays);
11538     }
11539
11540     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11541
11542     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11543
11544     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11545         /* If we are playing on ICS, the server decides when the
11546            game is over, but the engine can offer to draw, claim
11547            a draw, or resign.
11548          */
11549 #if ZIPPY
11550         if (appData.zippyPlay && first.initDone) {
11551             if (result == GameIsDrawn) {
11552                 /* In case draw still needs to be claimed */
11553                 SendToICS(ics_prefix);
11554                 SendToICS("draw\n");
11555             } else if (StrCaseStr(resultDetails, "resign")) {
11556                 SendToICS(ics_prefix);
11557                 SendToICS("resign\n");
11558             }
11559         }
11560 #endif
11561         endingGame = 0; /* [HGM] crash */
11562         return;
11563     }
11564
11565     /* If we're loading the game from a file, stop */
11566     if (whosays == GE_FILE) {
11567       (void) StopLoadGameTimer();
11568       gameFileFP = NULL;
11569     }
11570
11571     /* Cancel draw offers */
11572     first.offeredDraw = second.offeredDraw = 0;
11573
11574     /* If this is an ICS game, only ICS can really say it's done;
11575        if not, anyone can. */
11576     isIcsGame = (gameMode == IcsPlayingWhite ||
11577                  gameMode == IcsPlayingBlack ||
11578                  gameMode == IcsObserving    ||
11579                  gameMode == IcsExamining);
11580
11581     if (!isIcsGame || whosays == GE_ICS) {
11582         /* OK -- not an ICS game, or ICS said it was done */
11583         StopClocks();
11584         if (!isIcsGame && !appData.noChessProgram)
11585           SetUserThinkingEnables();
11586
11587         /* [HGM] if a machine claims the game end we verify this claim */
11588         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11589             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11590                 char claimer;
11591                 ChessMove trueResult = (ChessMove) -1;
11592
11593                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11594                                             first.twoMachinesColor[0] :
11595                                             second.twoMachinesColor[0] ;
11596
11597                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11598                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11599                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11600                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11601                 } else
11602                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11603                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11604                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11605                 } else
11606                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11607                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11608                 }
11609
11610                 // now verify win claims, but not in drop games, as we don't understand those yet
11611                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11612                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11613                     (result == WhiteWins && claimer == 'w' ||
11614                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11615                       if (appData.debugMode) {
11616                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11617                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11618                       }
11619                       if(result != trueResult) {
11620                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11621                               result = claimer == 'w' ? BlackWins : WhiteWins;
11622                               resultDetails = buf;
11623                       }
11624                 } else
11625                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11626                     && (forwardMostMove <= backwardMostMove ||
11627                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11628                         (claimer=='b')==(forwardMostMove&1))
11629                                                                                   ) {
11630                       /* [HGM] verify: draws that were not flagged are false claims */
11631                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11632                       result = claimer == 'w' ? BlackWins : WhiteWins;
11633                       resultDetails = buf;
11634                 }
11635                 /* (Claiming a loss is accepted no questions asked!) */
11636             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11637                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11638                 result = GameUnfinished;
11639                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11640             }
11641             /* [HGM] bare: don't allow bare King to win */
11642             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11643                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11644                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11645                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11646                && result != GameIsDrawn)
11647             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11648                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11649                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11650                         if(p >= 0 && p <= (int)WhiteKing) k++;
11651                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11652                 }
11653                 if (appData.debugMode) {
11654                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11655                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11656                 }
11657                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11658                         result = GameIsDrawn;
11659                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11660                         resultDetails = buf;
11661                 }
11662             }
11663         }
11664
11665
11666         if(serverMoves != NULL && !loadFlag) { char c = '=';
11667             if(result==WhiteWins) c = '+';
11668             if(result==BlackWins) c = '-';
11669             if(resultDetails != NULL)
11670                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11671         }
11672         if (resultDetails != NULL) {
11673             gameInfo.result = result;
11674             gameInfo.resultDetails = StrSave(resultDetails);
11675
11676             /* display last move only if game was not loaded from file */
11677             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11678                 DisplayMove(currentMove - 1);
11679
11680             if (forwardMostMove != 0) {
11681                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11682                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11683                                                                 ) {
11684                     if (*appData.saveGameFile != NULLCHAR) {
11685                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11686                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11687                         else
11688                         SaveGameToFile(appData.saveGameFile, TRUE);
11689                     } else if (appData.autoSaveGames) {
11690                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11691                     }
11692                     if (*appData.savePositionFile != NULLCHAR) {
11693                         SavePositionToFile(appData.savePositionFile);
11694                     }
11695                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11696                 }
11697             }
11698
11699             /* Tell program how game ended in case it is learning */
11700             /* [HGM] Moved this to after saving the PGN, just in case */
11701             /* engine died and we got here through time loss. In that */
11702             /* case we will get a fatal error writing the pipe, which */
11703             /* would otherwise lose us the PGN.                       */
11704             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11705             /* output during GameEnds should never be fatal anymore   */
11706             if (gameMode == MachinePlaysWhite ||
11707                 gameMode == MachinePlaysBlack ||
11708                 gameMode == TwoMachinesPlay ||
11709                 gameMode == IcsPlayingWhite ||
11710                 gameMode == IcsPlayingBlack ||
11711                 gameMode == BeginningOfGame) {
11712                 char buf[MSG_SIZ];
11713                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11714                         resultDetails);
11715                 if (first.pr != NoProc) {
11716                     SendToProgram(buf, &first);
11717                 }
11718                 if (second.pr != NoProc &&
11719                     gameMode == TwoMachinesPlay) {
11720                     SendToProgram(buf, &second);
11721                 }
11722             }
11723         }
11724
11725         if (appData.icsActive) {
11726             if (appData.quietPlay &&
11727                 (gameMode == IcsPlayingWhite ||
11728                  gameMode == IcsPlayingBlack)) {
11729                 SendToICS(ics_prefix);
11730                 SendToICS("set shout 1\n");
11731             }
11732             nextGameMode = IcsIdle;
11733             ics_user_moved = FALSE;
11734             /* clean up premove.  It's ugly when the game has ended and the
11735              * premove highlights are still on the board.
11736              */
11737             if (gotPremove) {
11738               gotPremove = FALSE;
11739               ClearPremoveHighlights();
11740               DrawPosition(FALSE, boards[currentMove]);
11741             }
11742             if (whosays == GE_ICS) {
11743                 switch (result) {
11744                 case WhiteWins:
11745                     if (gameMode == IcsPlayingWhite)
11746                         PlayIcsWinSound();
11747                     else if(gameMode == IcsPlayingBlack)
11748                         PlayIcsLossSound();
11749                     break;
11750                 case BlackWins:
11751                     if (gameMode == IcsPlayingBlack)
11752                         PlayIcsWinSound();
11753                     else if(gameMode == IcsPlayingWhite)
11754                         PlayIcsLossSound();
11755                     break;
11756                 case GameIsDrawn:
11757                     PlayIcsDrawSound();
11758                     break;
11759                 default:
11760                     PlayIcsUnfinishedSound();
11761                 }
11762             }
11763             if(appData.quitNext) { ExitEvent(0); return; }
11764         } else if (gameMode == EditGame ||
11765                    gameMode == PlayFromGameFile ||
11766                    gameMode == AnalyzeMode ||
11767                    gameMode == AnalyzeFile) {
11768             nextGameMode = gameMode;
11769         } else {
11770             nextGameMode = EndOfGame;
11771         }
11772         pausing = FALSE;
11773         ModeHighlight();
11774     } else {
11775         nextGameMode = gameMode;
11776     }
11777
11778     if (appData.noChessProgram) {
11779         gameMode = nextGameMode;
11780         ModeHighlight();
11781         endingGame = 0; /* [HGM] crash */
11782         return;
11783     }
11784
11785     if (first.reuse) {
11786         /* Put first chess program into idle state */
11787         if (first.pr != NoProc &&
11788             (gameMode == MachinePlaysWhite ||
11789              gameMode == MachinePlaysBlack ||
11790              gameMode == TwoMachinesPlay ||
11791              gameMode == IcsPlayingWhite ||
11792              gameMode == IcsPlayingBlack ||
11793              gameMode == BeginningOfGame)) {
11794             SendToProgram("force\n", &first);
11795             if (first.usePing) {
11796               char buf[MSG_SIZ];
11797               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11798               SendToProgram(buf, &first);
11799             }
11800         }
11801     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11802         /* Kill off first chess program */
11803         if (first.isr != NULL)
11804           RemoveInputSource(first.isr);
11805         first.isr = NULL;
11806
11807         if (first.pr != NoProc) {
11808             ExitAnalyzeMode();
11809             DoSleep( appData.delayBeforeQuit );
11810             SendToProgram("quit\n", &first);
11811             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11812             first.reload = TRUE;
11813         }
11814         first.pr = NoProc;
11815     }
11816     if (second.reuse) {
11817         /* Put second chess program into idle state */
11818         if (second.pr != NoProc &&
11819             gameMode == TwoMachinesPlay) {
11820             SendToProgram("force\n", &second);
11821             if (second.usePing) {
11822               char buf[MSG_SIZ];
11823               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11824               SendToProgram(buf, &second);
11825             }
11826         }
11827     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11828         /* Kill off second chess program */
11829         if (second.isr != NULL)
11830           RemoveInputSource(second.isr);
11831         second.isr = NULL;
11832
11833         if (second.pr != NoProc) {
11834             DoSleep( appData.delayBeforeQuit );
11835             SendToProgram("quit\n", &second);
11836             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11837             second.reload = TRUE;
11838         }
11839         second.pr = NoProc;
11840     }
11841
11842     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11843         char resChar = '=';
11844         switch (result) {
11845         case WhiteWins:
11846           resChar = '+';
11847           if (first.twoMachinesColor[0] == 'w') {
11848             first.matchWins++;
11849           } else {
11850             second.matchWins++;
11851           }
11852           break;
11853         case BlackWins:
11854           resChar = '-';
11855           if (first.twoMachinesColor[0] == 'b') {
11856             first.matchWins++;
11857           } else {
11858             second.matchWins++;
11859           }
11860           break;
11861         case GameUnfinished:
11862           resChar = ' ';
11863         default:
11864           break;
11865         }
11866
11867         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11868         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11869             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11870             ReserveGame(nextGame, resChar); // sets nextGame
11871             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11872             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11873         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11874
11875         if (nextGame <= appData.matchGames && !abortMatch) {
11876             gameMode = nextGameMode;
11877             matchGame = nextGame; // this will be overruled in tourney mode!
11878             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11879             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11880             endingGame = 0; /* [HGM] crash */
11881             return;
11882         } else {
11883             gameMode = nextGameMode;
11884             if(appData.epd) {
11885                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11886                 OutputKibitz(2, buf);
11887                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11888                 OutputKibitz(2, buf);
11889                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11890                 if(second.matchWins) OutputKibitz(2, buf);
11891                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11892                 OutputKibitz(2, buf);
11893             }
11894             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11895                      first.tidy, second.tidy,
11896                      first.matchWins, second.matchWins,
11897                      appData.matchGames - (first.matchWins + second.matchWins));
11898             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11899             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11900             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11901             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11902                 first.twoMachinesColor = "black\n";
11903                 second.twoMachinesColor = "white\n";
11904             } else {
11905                 first.twoMachinesColor = "white\n";
11906                 second.twoMachinesColor = "black\n";
11907             }
11908         }
11909     }
11910     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11911         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11912       ExitAnalyzeMode();
11913     gameMode = nextGameMode;
11914     ModeHighlight();
11915     endingGame = 0;  /* [HGM] crash */
11916     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11917         if(matchMode == TRUE) { // match through command line: exit with or without popup
11918             if(ranking) {
11919                 ToNrEvent(forwardMostMove);
11920                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11921                 else ExitEvent(0);
11922             } else DisplayFatalError(buf, 0, 0);
11923         } else { // match through menu; just stop, with or without popup
11924             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11925             ModeHighlight();
11926             if(ranking){
11927                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11928             } else DisplayNote(buf);
11929       }
11930       if(ranking) free(ranking);
11931     }
11932 }
11933
11934 /* Assumes program was just initialized (initString sent).
11935    Leaves program in force mode. */
11936 void
11937 FeedMovesToProgram (ChessProgramState *cps, int upto)
11938 {
11939     int i;
11940
11941     if (appData.debugMode)
11942       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11943               startedFromSetupPosition ? "position and " : "",
11944               backwardMostMove, upto, cps->which);
11945     if(currentlyInitializedVariant != gameInfo.variant) {
11946       char buf[MSG_SIZ];
11947         // [HGM] variantswitch: make engine aware of new variant
11948         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11949                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11950                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11951         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11952         SendToProgram(buf, cps);
11953         currentlyInitializedVariant = gameInfo.variant;
11954     }
11955     SendToProgram("force\n", cps);
11956     if (startedFromSetupPosition) {
11957         SendBoard(cps, backwardMostMove);
11958     if (appData.debugMode) {
11959         fprintf(debugFP, "feedMoves\n");
11960     }
11961     }
11962     for (i = backwardMostMove; i < upto; i++) {
11963         SendMoveToProgram(i, cps);
11964     }
11965 }
11966
11967
11968 int
11969 ResurrectChessProgram ()
11970 {
11971      /* The chess program may have exited.
11972         If so, restart it and feed it all the moves made so far. */
11973     static int doInit = 0;
11974
11975     if (appData.noChessProgram) return 1;
11976
11977     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11978         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11979         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11980         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11981     } else {
11982         if (first.pr != NoProc) return 1;
11983         StartChessProgram(&first);
11984     }
11985     InitChessProgram(&first, FALSE);
11986     FeedMovesToProgram(&first, currentMove);
11987
11988     if (!first.sendTime) {
11989         /* can't tell gnuchess what its clock should read,
11990            so we bow to its notion. */
11991         ResetClocks();
11992         timeRemaining[0][currentMove] = whiteTimeRemaining;
11993         timeRemaining[1][currentMove] = blackTimeRemaining;
11994     }
11995
11996     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11997                 appData.icsEngineAnalyze) && first.analysisSupport) {
11998       SendToProgram("analyze\n", &first);
11999       first.analyzing = TRUE;
12000     }
12001     return 1;
12002 }
12003
12004 /*
12005  * Button procedures
12006  */
12007 void
12008 Reset (int redraw, int init)
12009 {
12010     int i;
12011
12012     if (appData.debugMode) {
12013         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12014                 redraw, init, gameMode);
12015     }
12016     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12017     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12018     CleanupTail(); // [HGM] vari: delete any stored variations
12019     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12020     pausing = pauseExamInvalid = FALSE;
12021     startedFromSetupPosition = blackPlaysFirst = FALSE;
12022     firstMove = TRUE;
12023     whiteFlag = blackFlag = FALSE;
12024     userOfferedDraw = FALSE;
12025     hintRequested = bookRequested = FALSE;
12026     first.maybeThinking = FALSE;
12027     second.maybeThinking = FALSE;
12028     first.bookSuspend = FALSE; // [HGM] book
12029     second.bookSuspend = FALSE;
12030     thinkOutput[0] = NULLCHAR;
12031     lastHint[0] = NULLCHAR;
12032     ClearGameInfo(&gameInfo);
12033     gameInfo.variant = StringToVariant(appData.variant);
12034     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12035         gameInfo.variant = VariantUnknown;
12036         strncpy(engineVariant, appData.variant, MSG_SIZ);
12037     }
12038     ics_user_moved = ics_clock_paused = FALSE;
12039     ics_getting_history = H_FALSE;
12040     ics_gamenum = -1;
12041     white_holding[0] = black_holding[0] = NULLCHAR;
12042     ClearProgramStats();
12043     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12044
12045     ResetFrontEnd();
12046     ClearHighlights();
12047     flipView = appData.flipView;
12048     ClearPremoveHighlights();
12049     gotPremove = FALSE;
12050     alarmSounded = FALSE;
12051     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12052
12053     GameEnds(EndOfFile, NULL, GE_PLAYER);
12054     if(appData.serverMovesName != NULL) {
12055         /* [HGM] prepare to make moves file for broadcasting */
12056         clock_t t = clock();
12057         if(serverMoves != NULL) fclose(serverMoves);
12058         serverMoves = fopen(appData.serverMovesName, "r");
12059         if(serverMoves != NULL) {
12060             fclose(serverMoves);
12061             /* delay 15 sec before overwriting, so all clients can see end */
12062             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12063         }
12064         serverMoves = fopen(appData.serverMovesName, "w");
12065     }
12066
12067     ExitAnalyzeMode();
12068     gameMode = BeginningOfGame;
12069     ModeHighlight();
12070     if(appData.icsActive) gameInfo.variant = VariantNormal;
12071     currentMove = forwardMostMove = backwardMostMove = 0;
12072     MarkTargetSquares(1);
12073     InitPosition(redraw);
12074     for (i = 0; i < MAX_MOVES; i++) {
12075         if (commentList[i] != NULL) {
12076             free(commentList[i]);
12077             commentList[i] = NULL;
12078         }
12079     }
12080     ResetClocks();
12081     timeRemaining[0][0] = whiteTimeRemaining;
12082     timeRemaining[1][0] = blackTimeRemaining;
12083
12084     if (first.pr == NoProc) {
12085         StartChessProgram(&first);
12086     }
12087     if (init) {
12088             InitChessProgram(&first, startedFromSetupPosition);
12089     }
12090     DisplayTitle("");
12091     DisplayMessage("", "");
12092     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12093     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12094     ClearMap();        // [HGM] exclude: invalidate map
12095 }
12096
12097 void
12098 AutoPlayGameLoop ()
12099 {
12100     for (;;) {
12101         if (!AutoPlayOneMove())
12102           return;
12103         if (matchMode || appData.timeDelay == 0)
12104           continue;
12105         if (appData.timeDelay < 0)
12106           return;
12107         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12108         break;
12109     }
12110 }
12111
12112 void
12113 AnalyzeNextGame()
12114 {
12115     ReloadGame(1); // next game
12116 }
12117
12118 int
12119 AutoPlayOneMove ()
12120 {
12121     int fromX, fromY, toX, toY;
12122
12123     if (appData.debugMode) {
12124       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12125     }
12126
12127     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12128       return FALSE;
12129
12130     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12131       pvInfoList[currentMove].depth = programStats.depth;
12132       pvInfoList[currentMove].score = programStats.score;
12133       pvInfoList[currentMove].time  = 0;
12134       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12135       else { // append analysis of final position as comment
12136         char buf[MSG_SIZ];
12137         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12138         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12139       }
12140       programStats.depth = 0;
12141     }
12142
12143     if (currentMove >= forwardMostMove) {
12144       if(gameMode == AnalyzeFile) {
12145           if(appData.loadGameIndex == -1) {
12146             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12147           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12148           } else {
12149           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12150         }
12151       }
12152 //      gameMode = EndOfGame;
12153 //      ModeHighlight();
12154
12155       /* [AS] Clear current move marker at the end of a game */
12156       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12157
12158       return FALSE;
12159     }
12160
12161     toX = moveList[currentMove][2] - AAA;
12162     toY = moveList[currentMove][3] - ONE;
12163
12164     if (moveList[currentMove][1] == '@') {
12165         if (appData.highlightLastMove) {
12166             SetHighlights(-1, -1, toX, toY);
12167         }
12168     } else {
12169         fromX = moveList[currentMove][0] - AAA;
12170         fromY = moveList[currentMove][1] - ONE;
12171
12172         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12173
12174         if(moveList[currentMove][4] == ';') { // multi-leg
12175             killX = moveList[currentMove][5] - AAA;
12176             killY = moveList[currentMove][6] - ONE;
12177         }
12178         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12179         killX = killY = -1;
12180
12181         if (appData.highlightLastMove) {
12182             SetHighlights(fromX, fromY, toX, toY);
12183         }
12184     }
12185     DisplayMove(currentMove);
12186     SendMoveToProgram(currentMove++, &first);
12187     DisplayBothClocks();
12188     DrawPosition(FALSE, boards[currentMove]);
12189     // [HGM] PV info: always display, routine tests if empty
12190     DisplayComment(currentMove - 1, commentList[currentMove]);
12191     return TRUE;
12192 }
12193
12194
12195 int
12196 LoadGameOneMove (ChessMove readAhead)
12197 {
12198     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12199     char promoChar = NULLCHAR;
12200     ChessMove moveType;
12201     char move[MSG_SIZ];
12202     char *p, *q;
12203
12204     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12205         gameMode != AnalyzeMode && gameMode != Training) {
12206         gameFileFP = NULL;
12207         return FALSE;
12208     }
12209
12210     yyboardindex = forwardMostMove;
12211     if (readAhead != EndOfFile) {
12212       moveType = readAhead;
12213     } else {
12214       if (gameFileFP == NULL)
12215           return FALSE;
12216       moveType = (ChessMove) Myylex();
12217     }
12218
12219     done = FALSE;
12220     switch (moveType) {
12221       case Comment:
12222         if (appData.debugMode)
12223           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12224         p = yy_text;
12225
12226         /* append the comment but don't display it */
12227         AppendComment(currentMove, p, FALSE);
12228         return TRUE;
12229
12230       case WhiteCapturesEnPassant:
12231       case BlackCapturesEnPassant:
12232       case WhitePromotion:
12233       case BlackPromotion:
12234       case WhiteNonPromotion:
12235       case BlackNonPromotion:
12236       case NormalMove:
12237       case FirstLeg:
12238       case WhiteKingSideCastle:
12239       case WhiteQueenSideCastle:
12240       case BlackKingSideCastle:
12241       case BlackQueenSideCastle:
12242       case WhiteKingSideCastleWild:
12243       case WhiteQueenSideCastleWild:
12244       case BlackKingSideCastleWild:
12245       case BlackQueenSideCastleWild:
12246       /* PUSH Fabien */
12247       case WhiteHSideCastleFR:
12248       case WhiteASideCastleFR:
12249       case BlackHSideCastleFR:
12250       case BlackASideCastleFR:
12251       /* POP Fabien */
12252         if (appData.debugMode)
12253           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12254         fromX = currentMoveString[0] - AAA;
12255         fromY = currentMoveString[1] - ONE;
12256         toX = currentMoveString[2] - AAA;
12257         toY = currentMoveString[3] - ONE;
12258         promoChar = currentMoveString[4];
12259         if(promoChar == ';') promoChar = currentMoveString[7];
12260         break;
12261
12262       case WhiteDrop:
12263       case BlackDrop:
12264         if (appData.debugMode)
12265           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12266         fromX = moveType == WhiteDrop ?
12267           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12268         (int) CharToPiece(ToLower(currentMoveString[0]));
12269         fromY = DROP_RANK;
12270         toX = currentMoveString[2] - AAA;
12271         toY = currentMoveString[3] - ONE;
12272         break;
12273
12274       case WhiteWins:
12275       case BlackWins:
12276       case GameIsDrawn:
12277       case GameUnfinished:
12278         if (appData.debugMode)
12279           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12280         p = strchr(yy_text, '{');
12281         if (p == NULL) p = strchr(yy_text, '(');
12282         if (p == NULL) {
12283             p = yy_text;
12284             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12285         } else {
12286             q = strchr(p, *p == '{' ? '}' : ')');
12287             if (q != NULL) *q = NULLCHAR;
12288             p++;
12289         }
12290         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12291         GameEnds(moveType, p, GE_FILE);
12292         done = TRUE;
12293         if (cmailMsgLoaded) {
12294             ClearHighlights();
12295             flipView = WhiteOnMove(currentMove);
12296             if (moveType == GameUnfinished) flipView = !flipView;
12297             if (appData.debugMode)
12298               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12299         }
12300         break;
12301
12302       case EndOfFile:
12303         if (appData.debugMode)
12304           fprintf(debugFP, "Parser hit end of file\n");
12305         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12306           case MT_NONE:
12307           case MT_CHECK:
12308             break;
12309           case MT_CHECKMATE:
12310           case MT_STAINMATE:
12311             if (WhiteOnMove(currentMove)) {
12312                 GameEnds(BlackWins, "Black mates", GE_FILE);
12313             } else {
12314                 GameEnds(WhiteWins, "White mates", GE_FILE);
12315             }
12316             break;
12317           case MT_STALEMATE:
12318             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12319             break;
12320         }
12321         done = TRUE;
12322         break;
12323
12324       case MoveNumberOne:
12325         if (lastLoadGameStart == GNUChessGame) {
12326             /* GNUChessGames have numbers, but they aren't move numbers */
12327             if (appData.debugMode)
12328               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12329                       yy_text, (int) moveType);
12330             return LoadGameOneMove(EndOfFile); /* tail recursion */
12331         }
12332         /* else fall thru */
12333
12334       case XBoardGame:
12335       case GNUChessGame:
12336       case PGNTag:
12337         /* Reached start of next game in file */
12338         if (appData.debugMode)
12339           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12340         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12341           case MT_NONE:
12342           case MT_CHECK:
12343             break;
12344           case MT_CHECKMATE:
12345           case MT_STAINMATE:
12346             if (WhiteOnMove(currentMove)) {
12347                 GameEnds(BlackWins, "Black mates", GE_FILE);
12348             } else {
12349                 GameEnds(WhiteWins, "White mates", GE_FILE);
12350             }
12351             break;
12352           case MT_STALEMATE:
12353             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12354             break;
12355         }
12356         done = TRUE;
12357         break;
12358
12359       case PositionDiagram:     /* should not happen; ignore */
12360       case ElapsedTime:         /* ignore */
12361       case NAG:                 /* ignore */
12362         if (appData.debugMode)
12363           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12364                   yy_text, (int) moveType);
12365         return LoadGameOneMove(EndOfFile); /* tail recursion */
12366
12367       case IllegalMove:
12368         if (appData.testLegality) {
12369             if (appData.debugMode)
12370               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12371             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12372                     (forwardMostMove / 2) + 1,
12373                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12374             DisplayError(move, 0);
12375             done = TRUE;
12376         } else {
12377             if (appData.debugMode)
12378               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12379                       yy_text, currentMoveString);
12380             if(currentMoveString[1] == '@') {
12381                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12382                 fromY = DROP_RANK;
12383             } else {
12384                 fromX = currentMoveString[0] - AAA;
12385                 fromY = currentMoveString[1] - ONE;
12386             }
12387             toX = currentMoveString[2] - AAA;
12388             toY = currentMoveString[3] - ONE;
12389             promoChar = currentMoveString[4];
12390         }
12391         break;
12392
12393       case AmbiguousMove:
12394         if (appData.debugMode)
12395           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12396         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12397                 (forwardMostMove / 2) + 1,
12398                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12399         DisplayError(move, 0);
12400         done = TRUE;
12401         break;
12402
12403       default:
12404       case ImpossibleMove:
12405         if (appData.debugMode)
12406           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12407         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12408                 (forwardMostMove / 2) + 1,
12409                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12410         DisplayError(move, 0);
12411         done = TRUE;
12412         break;
12413     }
12414
12415     if (done) {
12416         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12417             DrawPosition(FALSE, boards[currentMove]);
12418             DisplayBothClocks();
12419             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12420               DisplayComment(currentMove - 1, commentList[currentMove]);
12421         }
12422         (void) StopLoadGameTimer();
12423         gameFileFP = NULL;
12424         cmailOldMove = forwardMostMove;
12425         return FALSE;
12426     } else {
12427         /* currentMoveString is set as a side-effect of yylex */
12428
12429         thinkOutput[0] = NULLCHAR;
12430         MakeMove(fromX, fromY, toX, toY, promoChar);
12431         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12432         currentMove = forwardMostMove;
12433         return TRUE;
12434     }
12435 }
12436
12437 /* Load the nth game from the given file */
12438 int
12439 LoadGameFromFile (char *filename, int n, char *title, int useList)
12440 {
12441     FILE *f;
12442     char buf[MSG_SIZ];
12443
12444     if (strcmp(filename, "-") == 0) {
12445         f = stdin;
12446         title = "stdin";
12447     } else {
12448         f = fopen(filename, "rb");
12449         if (f == NULL) {
12450           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12451             DisplayError(buf, errno);
12452             return FALSE;
12453         }
12454     }
12455     if (fseek(f, 0, 0) == -1) {
12456         /* f is not seekable; probably a pipe */
12457         useList = FALSE;
12458     }
12459     if (useList && n == 0) {
12460         int error = GameListBuild(f);
12461         if (error) {
12462             DisplayError(_("Cannot build game list"), error);
12463         } else if (!ListEmpty(&gameList) &&
12464                    ((ListGame *) gameList.tailPred)->number > 1) {
12465             GameListPopUp(f, title);
12466             return TRUE;
12467         }
12468         GameListDestroy();
12469         n = 1;
12470     }
12471     if (n == 0) n = 1;
12472     return LoadGame(f, n, title, FALSE);
12473 }
12474
12475
12476 void
12477 MakeRegisteredMove ()
12478 {
12479     int fromX, fromY, toX, toY;
12480     char promoChar;
12481     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12482         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12483           case CMAIL_MOVE:
12484           case CMAIL_DRAW:
12485             if (appData.debugMode)
12486               fprintf(debugFP, "Restoring %s for game %d\n",
12487                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12488
12489             thinkOutput[0] = NULLCHAR;
12490             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12491             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12492             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12493             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12494             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12495             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12496             MakeMove(fromX, fromY, toX, toY, promoChar);
12497             ShowMove(fromX, fromY, toX, toY);
12498
12499             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12500               case MT_NONE:
12501               case MT_CHECK:
12502                 break;
12503
12504               case MT_CHECKMATE:
12505               case MT_STAINMATE:
12506                 if (WhiteOnMove(currentMove)) {
12507                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12508                 } else {
12509                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12510                 }
12511                 break;
12512
12513               case MT_STALEMATE:
12514                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12515                 break;
12516             }
12517
12518             break;
12519
12520           case CMAIL_RESIGN:
12521             if (WhiteOnMove(currentMove)) {
12522                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12523             } else {
12524                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12525             }
12526             break;
12527
12528           case CMAIL_ACCEPT:
12529             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12530             break;
12531
12532           default:
12533             break;
12534         }
12535     }
12536
12537     return;
12538 }
12539
12540 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12541 int
12542 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12543 {
12544     int retVal;
12545
12546     if (gameNumber > nCmailGames) {
12547         DisplayError(_("No more games in this message"), 0);
12548         return FALSE;
12549     }
12550     if (f == lastLoadGameFP) {
12551         int offset = gameNumber - lastLoadGameNumber;
12552         if (offset == 0) {
12553             cmailMsg[0] = NULLCHAR;
12554             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12555                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12556                 nCmailMovesRegistered--;
12557             }
12558             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12559             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12560                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12561             }
12562         } else {
12563             if (! RegisterMove()) return FALSE;
12564         }
12565     }
12566
12567     retVal = LoadGame(f, gameNumber, title, useList);
12568
12569     /* Make move registered during previous look at this game, if any */
12570     MakeRegisteredMove();
12571
12572     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12573         commentList[currentMove]
12574           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12575         DisplayComment(currentMove - 1, commentList[currentMove]);
12576     }
12577
12578     return retVal;
12579 }
12580
12581 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12582 int
12583 ReloadGame (int offset)
12584 {
12585     int gameNumber = lastLoadGameNumber + offset;
12586     if (lastLoadGameFP == NULL) {
12587         DisplayError(_("No game has been loaded yet"), 0);
12588         return FALSE;
12589     }
12590     if (gameNumber <= 0) {
12591         DisplayError(_("Can't back up any further"), 0);
12592         return FALSE;
12593     }
12594     if (cmailMsgLoaded) {
12595         return CmailLoadGame(lastLoadGameFP, gameNumber,
12596                              lastLoadGameTitle, lastLoadGameUseList);
12597     } else {
12598         return LoadGame(lastLoadGameFP, gameNumber,
12599                         lastLoadGameTitle, lastLoadGameUseList);
12600     }
12601 }
12602
12603 int keys[EmptySquare+1];
12604
12605 int
12606 PositionMatches (Board b1, Board b2)
12607 {
12608     int r, f, sum=0;
12609     switch(appData.searchMode) {
12610         case 1: return CompareWithRights(b1, b2);
12611         case 2:
12612             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12613                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12614             }
12615             return TRUE;
12616         case 3:
12617             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12618               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12619                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12620             }
12621             return sum==0;
12622         case 4:
12623             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12624                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12625             }
12626             return sum==0;
12627     }
12628     return TRUE;
12629 }
12630
12631 #define Q_PROMO  4
12632 #define Q_EP     3
12633 #define Q_BCASTL 2
12634 #define Q_WCASTL 1
12635
12636 int pieceList[256], quickBoard[256];
12637 ChessSquare pieceType[256] = { EmptySquare };
12638 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12639 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12640 int soughtTotal, turn;
12641 Boolean epOK, flipSearch;
12642
12643 typedef struct {
12644     unsigned char piece, to;
12645 } Move;
12646
12647 #define DSIZE (250000)
12648
12649 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12650 Move *moveDatabase = initialSpace;
12651 unsigned int movePtr, dataSize = DSIZE;
12652
12653 int
12654 MakePieceList (Board board, int *counts)
12655 {
12656     int r, f, n=Q_PROMO, total=0;
12657     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12658     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12659         int sq = f + (r<<4);
12660         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12661             quickBoard[sq] = ++n;
12662             pieceList[n] = sq;
12663             pieceType[n] = board[r][f];
12664             counts[board[r][f]]++;
12665             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12666             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12667             total++;
12668         }
12669     }
12670     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12671     return total;
12672 }
12673
12674 void
12675 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12676 {
12677     int sq = fromX + (fromY<<4);
12678     int piece = quickBoard[sq], rook;
12679     quickBoard[sq] = 0;
12680     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12681     if(piece == pieceList[1] && fromY == toY) {
12682       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12683         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12684         moveDatabase[movePtr++].piece = Q_WCASTL;
12685         quickBoard[sq] = piece;
12686         piece = quickBoard[from]; quickBoard[from] = 0;
12687         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12688       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12689         quickBoard[sq] = 0; // remove Rook
12690         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12691         moveDatabase[movePtr++].piece = Q_WCASTL;
12692         quickBoard[sq] = pieceList[1]; // put King
12693         piece = rook;
12694         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12695       }
12696     } else
12697     if(piece == pieceList[2] && fromY == toY) {
12698       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12699         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12700         moveDatabase[movePtr++].piece = Q_BCASTL;
12701         quickBoard[sq] = piece;
12702         piece = quickBoard[from]; quickBoard[from] = 0;
12703         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12704       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12705         quickBoard[sq] = 0; // remove Rook
12706         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12707         moveDatabase[movePtr++].piece = Q_BCASTL;
12708         quickBoard[sq] = pieceList[2]; // put King
12709         piece = rook;
12710         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12711       }
12712     } else
12713     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12714         quickBoard[(fromY<<4)+toX] = 0;
12715         moveDatabase[movePtr].piece = Q_EP;
12716         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12717         moveDatabase[movePtr].to = sq;
12718     } else
12719     if(promoPiece != pieceType[piece]) {
12720         moveDatabase[movePtr++].piece = Q_PROMO;
12721         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12722     }
12723     moveDatabase[movePtr].piece = piece;
12724     quickBoard[sq] = piece;
12725     movePtr++;
12726 }
12727
12728 int
12729 PackGame (Board board)
12730 {
12731     Move *newSpace = NULL;
12732     moveDatabase[movePtr].piece = 0; // terminate previous game
12733     if(movePtr > dataSize) {
12734         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12735         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12736         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12737         if(newSpace) {
12738             int i;
12739             Move *p = moveDatabase, *q = newSpace;
12740             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12741             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12742             moveDatabase = newSpace;
12743         } else { // calloc failed, we must be out of memory. Too bad...
12744             dataSize = 0; // prevent calloc events for all subsequent games
12745             return 0;     // and signal this one isn't cached
12746         }
12747     }
12748     movePtr++;
12749     MakePieceList(board, counts);
12750     return movePtr;
12751 }
12752
12753 int
12754 QuickCompare (Board board, int *minCounts, int *maxCounts)
12755 {   // compare according to search mode
12756     int r, f;
12757     switch(appData.searchMode)
12758     {
12759       case 1: // exact position match
12760         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12761         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12762             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12763         }
12764         break;
12765       case 2: // can have extra material on empty squares
12766         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12767             if(board[r][f] == EmptySquare) continue;
12768             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12769         }
12770         break;
12771       case 3: // material with exact Pawn structure
12772         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12773             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12774             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12775         } // fall through to material comparison
12776       case 4: // exact material
12777         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12778         break;
12779       case 6: // material range with given imbalance
12780         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12781         // fall through to range comparison
12782       case 5: // material range
12783         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12784     }
12785     return TRUE;
12786 }
12787
12788 int
12789 QuickScan (Board board, Move *move)
12790 {   // reconstruct game,and compare all positions in it
12791     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12792     do {
12793         int piece = move->piece;
12794         int to = move->to, from = pieceList[piece];
12795         if(found < 0) { // if already found just scan to game end for final piece count
12796           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12797            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12798            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12799                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12800             ) {
12801             static int lastCounts[EmptySquare+1];
12802             int i;
12803             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12804             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12805           } else stretch = 0;
12806           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12807           if(found >= 0 && !appData.minPieces) return found;
12808         }
12809         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12810           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12811           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12812             piece = (++move)->piece;
12813             from = pieceList[piece];
12814             counts[pieceType[piece]]--;
12815             pieceType[piece] = (ChessSquare) move->to;
12816             counts[move->to]++;
12817           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12818             counts[pieceType[quickBoard[to]]]--;
12819             quickBoard[to] = 0; total--;
12820             move++;
12821             continue;
12822           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12823             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12824             from  = pieceList[piece]; // so this must be King
12825             quickBoard[from] = 0;
12826             pieceList[piece] = to;
12827             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12828             quickBoard[from] = 0; // rook
12829             quickBoard[to] = piece;
12830             to = move->to; piece = move->piece;
12831             goto aftercastle;
12832           }
12833         }
12834         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12835         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12836         quickBoard[from] = 0;
12837       aftercastle:
12838         quickBoard[to] = piece;
12839         pieceList[piece] = to;
12840         cnt++; turn ^= 3;
12841         move++;
12842     } while(1);
12843 }
12844
12845 void
12846 InitSearch ()
12847 {
12848     int r, f;
12849     flipSearch = FALSE;
12850     CopyBoard(soughtBoard, boards[currentMove]);
12851     soughtTotal = MakePieceList(soughtBoard, maxSought);
12852     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12853     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12854     CopyBoard(reverseBoard, boards[currentMove]);
12855     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12856         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12857         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12858         reverseBoard[r][f] = piece;
12859     }
12860     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12861     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12862     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12863                  || (boards[currentMove][CASTLING][2] == NoRights ||
12864                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12865                  && (boards[currentMove][CASTLING][5] == NoRights ||
12866                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12867       ) {
12868         flipSearch = TRUE;
12869         CopyBoard(flipBoard, soughtBoard);
12870         CopyBoard(rotateBoard, reverseBoard);
12871         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12872             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12873             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12874         }
12875     }
12876     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12877     if(appData.searchMode >= 5) {
12878         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12879         MakePieceList(soughtBoard, minSought);
12880         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12881     }
12882     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12883         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12884 }
12885
12886 GameInfo dummyInfo;
12887 static int creatingBook;
12888
12889 int
12890 GameContainsPosition (FILE *f, ListGame *lg)
12891 {
12892     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12893     int fromX, fromY, toX, toY;
12894     char promoChar;
12895     static int initDone=FALSE;
12896
12897     // weed out games based on numerical tag comparison
12898     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12899     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12900     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12901     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12902     if(!initDone) {
12903         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12904         initDone = TRUE;
12905     }
12906     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12907     else CopyBoard(boards[scratch], initialPosition); // default start position
12908     if(lg->moves) {
12909         turn = btm + 1;
12910         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12911         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12912     }
12913     if(btm) plyNr++;
12914     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12915     fseek(f, lg->offset, 0);
12916     yynewfile(f);
12917     while(1) {
12918         yyboardindex = scratch;
12919         quickFlag = plyNr+1;
12920         next = Myylex();
12921         quickFlag = 0;
12922         switch(next) {
12923             case PGNTag:
12924                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12925             default:
12926                 continue;
12927
12928             case XBoardGame:
12929             case GNUChessGame:
12930                 if(plyNr) return -1; // after we have seen moves, this is for new game
12931               continue;
12932
12933             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12934             case ImpossibleMove:
12935             case WhiteWins: // game ends here with these four
12936             case BlackWins:
12937             case GameIsDrawn:
12938             case GameUnfinished:
12939                 return -1;
12940
12941             case IllegalMove:
12942                 if(appData.testLegality) return -1;
12943             case WhiteCapturesEnPassant:
12944             case BlackCapturesEnPassant:
12945             case WhitePromotion:
12946             case BlackPromotion:
12947             case WhiteNonPromotion:
12948             case BlackNonPromotion:
12949             case NormalMove:
12950             case FirstLeg:
12951             case WhiteKingSideCastle:
12952             case WhiteQueenSideCastle:
12953             case BlackKingSideCastle:
12954             case BlackQueenSideCastle:
12955             case WhiteKingSideCastleWild:
12956             case WhiteQueenSideCastleWild:
12957             case BlackKingSideCastleWild:
12958             case BlackQueenSideCastleWild:
12959             case WhiteHSideCastleFR:
12960             case WhiteASideCastleFR:
12961             case BlackHSideCastleFR:
12962             case BlackASideCastleFR:
12963                 fromX = currentMoveString[0] - AAA;
12964                 fromY = currentMoveString[1] - ONE;
12965                 toX = currentMoveString[2] - AAA;
12966                 toY = currentMoveString[3] - ONE;
12967                 promoChar = currentMoveString[4];
12968                 break;
12969             case WhiteDrop:
12970             case BlackDrop:
12971                 fromX = next == WhiteDrop ?
12972                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12973                   (int) CharToPiece(ToLower(currentMoveString[0]));
12974                 fromY = DROP_RANK;
12975                 toX = currentMoveString[2] - AAA;
12976                 toY = currentMoveString[3] - ONE;
12977                 promoChar = 0;
12978                 break;
12979         }
12980         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12981         plyNr++;
12982         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12983         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12984         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12985         if(appData.findMirror) {
12986             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12987             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12988         }
12989     }
12990 }
12991
12992 /* Load the nth game from open file f */
12993 int
12994 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12995 {
12996     ChessMove cm;
12997     char buf[MSG_SIZ];
12998     int gn = gameNumber;
12999     ListGame *lg = NULL;
13000     int numPGNTags = 0, i;
13001     int err, pos = -1;
13002     GameMode oldGameMode;
13003     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13004     char oldName[MSG_SIZ];
13005
13006     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13007
13008     if (appData.debugMode)
13009         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13010
13011     if (gameMode == Training )
13012         SetTrainingModeOff();
13013
13014     oldGameMode = gameMode;
13015     if (gameMode != BeginningOfGame) {
13016       Reset(FALSE, TRUE);
13017     }
13018     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13019
13020     gameFileFP = f;
13021     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13022         fclose(lastLoadGameFP);
13023     }
13024
13025     if (useList) {
13026         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13027
13028         if (lg) {
13029             fseek(f, lg->offset, 0);
13030             GameListHighlight(gameNumber);
13031             pos = lg->position;
13032             gn = 1;
13033         }
13034         else {
13035             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13036               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13037             else
13038             DisplayError(_("Game number out of range"), 0);
13039             return FALSE;
13040         }
13041     } else {
13042         GameListDestroy();
13043         if (fseek(f, 0, 0) == -1) {
13044             if (f == lastLoadGameFP ?
13045                 gameNumber == lastLoadGameNumber + 1 :
13046                 gameNumber == 1) {
13047                 gn = 1;
13048             } else {
13049                 DisplayError(_("Can't seek on game file"), 0);
13050                 return FALSE;
13051             }
13052         }
13053     }
13054     lastLoadGameFP = f;
13055     lastLoadGameNumber = gameNumber;
13056     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13057     lastLoadGameUseList = useList;
13058
13059     yynewfile(f);
13060
13061     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13062       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13063                 lg->gameInfo.black);
13064             DisplayTitle(buf);
13065     } else if (*title != NULLCHAR) {
13066         if (gameNumber > 1) {
13067           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13068             DisplayTitle(buf);
13069         } else {
13070             DisplayTitle(title);
13071         }
13072     }
13073
13074     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13075         gameMode = PlayFromGameFile;
13076         ModeHighlight();
13077     }
13078
13079     currentMove = forwardMostMove = backwardMostMove = 0;
13080     CopyBoard(boards[0], initialPosition);
13081     StopClocks();
13082
13083     /*
13084      * Skip the first gn-1 games in the file.
13085      * Also skip over anything that precedes an identifiable
13086      * start of game marker, to avoid being confused by
13087      * garbage at the start of the file.  Currently
13088      * recognized start of game markers are the move number "1",
13089      * the pattern "gnuchess .* game", the pattern
13090      * "^[#;%] [^ ]* game file", and a PGN tag block.
13091      * A game that starts with one of the latter two patterns
13092      * will also have a move number 1, possibly
13093      * following a position diagram.
13094      * 5-4-02: Let's try being more lenient and allowing a game to
13095      * start with an unnumbered move.  Does that break anything?
13096      */
13097     cm = lastLoadGameStart = EndOfFile;
13098     while (gn > 0) {
13099         yyboardindex = forwardMostMove;
13100         cm = (ChessMove) Myylex();
13101         switch (cm) {
13102           case EndOfFile:
13103             if (cmailMsgLoaded) {
13104                 nCmailGames = CMAIL_MAX_GAMES - gn;
13105             } else {
13106                 Reset(TRUE, TRUE);
13107                 DisplayError(_("Game not found in file"), 0);
13108             }
13109             return FALSE;
13110
13111           case GNUChessGame:
13112           case XBoardGame:
13113             gn--;
13114             lastLoadGameStart = cm;
13115             break;
13116
13117           case MoveNumberOne:
13118             switch (lastLoadGameStart) {
13119               case GNUChessGame:
13120               case XBoardGame:
13121               case PGNTag:
13122                 break;
13123               case MoveNumberOne:
13124               case EndOfFile:
13125                 gn--;           /* count this game */
13126                 lastLoadGameStart = cm;
13127                 break;
13128               default:
13129                 /* impossible */
13130                 break;
13131             }
13132             break;
13133
13134           case PGNTag:
13135             switch (lastLoadGameStart) {
13136               case GNUChessGame:
13137               case PGNTag:
13138               case MoveNumberOne:
13139               case EndOfFile:
13140                 gn--;           /* count this game */
13141                 lastLoadGameStart = cm;
13142                 break;
13143               case XBoardGame:
13144                 lastLoadGameStart = cm; /* game counted already */
13145                 break;
13146               default:
13147                 /* impossible */
13148                 break;
13149             }
13150             if (gn > 0) {
13151                 do {
13152                     yyboardindex = forwardMostMove;
13153                     cm = (ChessMove) Myylex();
13154                 } while (cm == PGNTag || cm == Comment);
13155             }
13156             break;
13157
13158           case WhiteWins:
13159           case BlackWins:
13160           case GameIsDrawn:
13161             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13162                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13163                     != CMAIL_OLD_RESULT) {
13164                     nCmailResults ++ ;
13165                     cmailResult[  CMAIL_MAX_GAMES
13166                                 - gn - 1] = CMAIL_OLD_RESULT;
13167                 }
13168             }
13169             break;
13170
13171           case NormalMove:
13172           case FirstLeg:
13173             /* Only a NormalMove can be at the start of a game
13174              * without a position diagram. */
13175             if (lastLoadGameStart == EndOfFile ) {
13176               gn--;
13177               lastLoadGameStart = MoveNumberOne;
13178             }
13179             break;
13180
13181           default:
13182             break;
13183         }
13184     }
13185
13186     if (appData.debugMode)
13187       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13188
13189     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13190
13191     if (cm == XBoardGame) {
13192         /* Skip any header junk before position diagram and/or move 1 */
13193         for (;;) {
13194             yyboardindex = forwardMostMove;
13195             cm = (ChessMove) Myylex();
13196
13197             if (cm == EndOfFile ||
13198                 cm == GNUChessGame || cm == XBoardGame) {
13199                 /* Empty game; pretend end-of-file and handle later */
13200                 cm = EndOfFile;
13201                 break;
13202             }
13203
13204             if (cm == MoveNumberOne || cm == PositionDiagram ||
13205                 cm == PGNTag || cm == Comment)
13206               break;
13207         }
13208     } else if (cm == GNUChessGame) {
13209         if (gameInfo.event != NULL) {
13210             free(gameInfo.event);
13211         }
13212         gameInfo.event = StrSave(yy_text);
13213     }
13214
13215     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13216     while (cm == PGNTag) {
13217         if (appData.debugMode)
13218           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13219         err = ParsePGNTag(yy_text, &gameInfo);
13220         if (!err) numPGNTags++;
13221
13222         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13223         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13224             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13225             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13226             InitPosition(TRUE);
13227             oldVariant = gameInfo.variant;
13228             if (appData.debugMode)
13229               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13230         }
13231
13232
13233         if (gameInfo.fen != NULL) {
13234           Board initial_position;
13235           startedFromSetupPosition = TRUE;
13236           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13237             Reset(TRUE, TRUE);
13238             DisplayError(_("Bad FEN position in file"), 0);
13239             return FALSE;
13240           }
13241           CopyBoard(boards[0], initial_position);
13242           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13243             CopyBoard(initialPosition, initial_position);
13244           if (blackPlaysFirst) {
13245             currentMove = forwardMostMove = backwardMostMove = 1;
13246             CopyBoard(boards[1], initial_position);
13247             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13248             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13249             timeRemaining[0][1] = whiteTimeRemaining;
13250             timeRemaining[1][1] = blackTimeRemaining;
13251             if (commentList[0] != NULL) {
13252               commentList[1] = commentList[0];
13253               commentList[0] = NULL;
13254             }
13255           } else {
13256             currentMove = forwardMostMove = backwardMostMove = 0;
13257           }
13258           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13259           {   int i;
13260               initialRulePlies = FENrulePlies;
13261               for( i=0; i< nrCastlingRights; i++ )
13262                   initialRights[i] = initial_position[CASTLING][i];
13263           }
13264           yyboardindex = forwardMostMove;
13265           free(gameInfo.fen);
13266           gameInfo.fen = NULL;
13267         }
13268
13269         yyboardindex = forwardMostMove;
13270         cm = (ChessMove) Myylex();
13271
13272         /* Handle comments interspersed among the tags */
13273         while (cm == Comment) {
13274             char *p;
13275             if (appData.debugMode)
13276               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13277             p = yy_text;
13278             AppendComment(currentMove, p, FALSE);
13279             yyboardindex = forwardMostMove;
13280             cm = (ChessMove) Myylex();
13281         }
13282     }
13283
13284     /* don't rely on existence of Event tag since if game was
13285      * pasted from clipboard the Event tag may not exist
13286      */
13287     if (numPGNTags > 0){
13288         char *tags;
13289         if (gameInfo.variant == VariantNormal) {
13290           VariantClass v = StringToVariant(gameInfo.event);
13291           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13292           if(v < VariantShogi) gameInfo.variant = v;
13293         }
13294         if (!matchMode) {
13295           if( appData.autoDisplayTags ) {
13296             tags = PGNTags(&gameInfo);
13297             TagsPopUp(tags, CmailMsg());
13298             free(tags);
13299           }
13300         }
13301     } else {
13302         /* Make something up, but don't display it now */
13303         SetGameInfo();
13304         TagsPopDown();
13305     }
13306
13307     if (cm == PositionDiagram) {
13308         int i, j;
13309         char *p;
13310         Board initial_position;
13311
13312         if (appData.debugMode)
13313           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13314
13315         if (!startedFromSetupPosition) {
13316             p = yy_text;
13317             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13318               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13319                 switch (*p) {
13320                   case '{':
13321                   case '[':
13322                   case '-':
13323                   case ' ':
13324                   case '\t':
13325                   case '\n':
13326                   case '\r':
13327                     break;
13328                   default:
13329                     initial_position[i][j++] = CharToPiece(*p);
13330                     break;
13331                 }
13332             while (*p == ' ' || *p == '\t' ||
13333                    *p == '\n' || *p == '\r') p++;
13334
13335             if (strncmp(p, "black", strlen("black"))==0)
13336               blackPlaysFirst = TRUE;
13337             else
13338               blackPlaysFirst = FALSE;
13339             startedFromSetupPosition = TRUE;
13340
13341             CopyBoard(boards[0], initial_position);
13342             if (blackPlaysFirst) {
13343                 currentMove = forwardMostMove = backwardMostMove = 1;
13344                 CopyBoard(boards[1], initial_position);
13345                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13346                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13347                 timeRemaining[0][1] = whiteTimeRemaining;
13348                 timeRemaining[1][1] = blackTimeRemaining;
13349                 if (commentList[0] != NULL) {
13350                     commentList[1] = commentList[0];
13351                     commentList[0] = NULL;
13352                 }
13353             } else {
13354                 currentMove = forwardMostMove = backwardMostMove = 0;
13355             }
13356         }
13357         yyboardindex = forwardMostMove;
13358         cm = (ChessMove) Myylex();
13359     }
13360
13361   if(!creatingBook) {
13362     if (first.pr == NoProc) {
13363         StartChessProgram(&first);
13364     }
13365     InitChessProgram(&first, FALSE);
13366     if(gameInfo.variant == VariantUnknown && *oldName) {
13367         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13368         gameInfo.variant = v;
13369     }
13370     SendToProgram("force\n", &first);
13371     if (startedFromSetupPosition) {
13372         SendBoard(&first, forwardMostMove);
13373     if (appData.debugMode) {
13374         fprintf(debugFP, "Load Game\n");
13375     }
13376         DisplayBothClocks();
13377     }
13378   }
13379
13380     /* [HGM] server: flag to write setup moves in broadcast file as one */
13381     loadFlag = appData.suppressLoadMoves;
13382
13383     while (cm == Comment) {
13384         char *p;
13385         if (appData.debugMode)
13386           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13387         p = yy_text;
13388         AppendComment(currentMove, p, FALSE);
13389         yyboardindex = forwardMostMove;
13390         cm = (ChessMove) Myylex();
13391     }
13392
13393     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13394         cm == WhiteWins || cm == BlackWins ||
13395         cm == GameIsDrawn || cm == GameUnfinished) {
13396         DisplayMessage("", _("No moves in game"));
13397         if (cmailMsgLoaded) {
13398             if (appData.debugMode)
13399               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13400             ClearHighlights();
13401             flipView = FALSE;
13402         }
13403         DrawPosition(FALSE, boards[currentMove]);
13404         DisplayBothClocks();
13405         gameMode = EditGame;
13406         ModeHighlight();
13407         gameFileFP = NULL;
13408         cmailOldMove = 0;
13409         return TRUE;
13410     }
13411
13412     // [HGM] PV info: routine tests if comment empty
13413     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13414         DisplayComment(currentMove - 1, commentList[currentMove]);
13415     }
13416     if (!matchMode && appData.timeDelay != 0)
13417       DrawPosition(FALSE, boards[currentMove]);
13418
13419     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13420       programStats.ok_to_send = 1;
13421     }
13422
13423     /* if the first token after the PGN tags is a move
13424      * and not move number 1, retrieve it from the parser
13425      */
13426     if (cm != MoveNumberOne)
13427         LoadGameOneMove(cm);
13428
13429     /* load the remaining moves from the file */
13430     while (LoadGameOneMove(EndOfFile)) {
13431       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13432       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13433     }
13434
13435     /* rewind to the start of the game */
13436     currentMove = backwardMostMove;
13437
13438     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13439
13440     if (oldGameMode == AnalyzeFile) {
13441       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13442       AnalyzeFileEvent();
13443     } else
13444     if (oldGameMode == AnalyzeMode) {
13445       AnalyzeFileEvent();
13446     }
13447
13448     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13449         long int w, b; // [HGM] adjourn: restore saved clock times
13450         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13451         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13452             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13453             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13454         }
13455     }
13456
13457     if(creatingBook) return TRUE;
13458     if (!matchMode && pos > 0) {
13459         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13460     } else
13461     if (matchMode || appData.timeDelay == 0) {
13462       ToEndEvent();
13463     } else if (appData.timeDelay > 0) {
13464       AutoPlayGameLoop();
13465     }
13466
13467     if (appData.debugMode)
13468         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13469
13470     loadFlag = 0; /* [HGM] true game starts */
13471     return TRUE;
13472 }
13473
13474 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13475 int
13476 ReloadPosition (int offset)
13477 {
13478     int positionNumber = lastLoadPositionNumber + offset;
13479     if (lastLoadPositionFP == NULL) {
13480         DisplayError(_("No position has been loaded yet"), 0);
13481         return FALSE;
13482     }
13483     if (positionNumber <= 0) {
13484         DisplayError(_("Can't back up any further"), 0);
13485         return FALSE;
13486     }
13487     return LoadPosition(lastLoadPositionFP, positionNumber,
13488                         lastLoadPositionTitle);
13489 }
13490
13491 /* Load the nth position from the given file */
13492 int
13493 LoadPositionFromFile (char *filename, int n, char *title)
13494 {
13495     FILE *f;
13496     char buf[MSG_SIZ];
13497
13498     if (strcmp(filename, "-") == 0) {
13499         return LoadPosition(stdin, n, "stdin");
13500     } else {
13501         f = fopen(filename, "rb");
13502         if (f == NULL) {
13503             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13504             DisplayError(buf, errno);
13505             return FALSE;
13506         } else {
13507             return LoadPosition(f, n, title);
13508         }
13509     }
13510 }
13511
13512 /* Load the nth position from the given open file, and close it */
13513 int
13514 LoadPosition (FILE *f, int positionNumber, char *title)
13515 {
13516     char *p, line[MSG_SIZ];
13517     Board initial_position;
13518     int i, j, fenMode, pn;
13519
13520     if (gameMode == Training )
13521         SetTrainingModeOff();
13522
13523     if (gameMode != BeginningOfGame) {
13524         Reset(FALSE, TRUE);
13525     }
13526     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13527         fclose(lastLoadPositionFP);
13528     }
13529     if (positionNumber == 0) positionNumber = 1;
13530     lastLoadPositionFP = f;
13531     lastLoadPositionNumber = positionNumber;
13532     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13533     if (first.pr == NoProc && !appData.noChessProgram) {
13534       StartChessProgram(&first);
13535       InitChessProgram(&first, FALSE);
13536     }
13537     pn = positionNumber;
13538     if (positionNumber < 0) {
13539         /* Negative position number means to seek to that byte offset */
13540         if (fseek(f, -positionNumber, 0) == -1) {
13541             DisplayError(_("Can't seek on position file"), 0);
13542             return FALSE;
13543         };
13544         pn = 1;
13545     } else {
13546         if (fseek(f, 0, 0) == -1) {
13547             if (f == lastLoadPositionFP ?
13548                 positionNumber == lastLoadPositionNumber + 1 :
13549                 positionNumber == 1) {
13550                 pn = 1;
13551             } else {
13552                 DisplayError(_("Can't seek on position file"), 0);
13553                 return FALSE;
13554             }
13555         }
13556     }
13557     /* See if this file is FEN or old-style xboard */
13558     if (fgets(line, MSG_SIZ, f) == NULL) {
13559         DisplayError(_("Position not found in file"), 0);
13560         return FALSE;
13561     }
13562     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13563     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13564
13565     if (pn >= 2) {
13566         if (fenMode || line[0] == '#') pn--;
13567         while (pn > 0) {
13568             /* skip positions before number pn */
13569             if (fgets(line, MSG_SIZ, f) == NULL) {
13570                 Reset(TRUE, TRUE);
13571                 DisplayError(_("Position not found in file"), 0);
13572                 return FALSE;
13573             }
13574             if (fenMode || line[0] == '#') pn--;
13575         }
13576     }
13577
13578     if (fenMode) {
13579         char *p;
13580         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13581             DisplayError(_("Bad FEN position in file"), 0);
13582             return FALSE;
13583         }
13584         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13585             sscanf(p+4, "%[^;]", bestMove);
13586         } else *bestMove = NULLCHAR;
13587         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13588             sscanf(p+4, "%[^;]", avoidMove);
13589         } else *avoidMove = NULLCHAR;
13590     } else {
13591         (void) fgets(line, MSG_SIZ, f);
13592         (void) fgets(line, MSG_SIZ, f);
13593
13594         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13595             (void) fgets(line, MSG_SIZ, f);
13596             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13597                 if (*p == ' ')
13598                   continue;
13599                 initial_position[i][j++] = CharToPiece(*p);
13600             }
13601         }
13602
13603         blackPlaysFirst = FALSE;
13604         if (!feof(f)) {
13605             (void) fgets(line, MSG_SIZ, f);
13606             if (strncmp(line, "black", strlen("black"))==0)
13607               blackPlaysFirst = TRUE;
13608         }
13609     }
13610     startedFromSetupPosition = TRUE;
13611
13612     CopyBoard(boards[0], initial_position);
13613     if (blackPlaysFirst) {
13614         currentMove = forwardMostMove = backwardMostMove = 1;
13615         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13616         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13617         CopyBoard(boards[1], initial_position);
13618         DisplayMessage("", _("Black to play"));
13619     } else {
13620         currentMove = forwardMostMove = backwardMostMove = 0;
13621         DisplayMessage("", _("White to play"));
13622     }
13623     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13624     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13625         SendToProgram("force\n", &first);
13626         SendBoard(&first, forwardMostMove);
13627     }
13628     if (appData.debugMode) {
13629 int i, j;
13630   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13631   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13632         fprintf(debugFP, "Load Position\n");
13633     }
13634
13635     if (positionNumber > 1) {
13636       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13637         DisplayTitle(line);
13638     } else {
13639         DisplayTitle(title);
13640     }
13641     gameMode = EditGame;
13642     ModeHighlight();
13643     ResetClocks();
13644     timeRemaining[0][1] = whiteTimeRemaining;
13645     timeRemaining[1][1] = blackTimeRemaining;
13646     DrawPosition(FALSE, boards[currentMove]);
13647
13648     return TRUE;
13649 }
13650
13651
13652 void
13653 CopyPlayerNameIntoFileName (char **dest, char *src)
13654 {
13655     while (*src != NULLCHAR && *src != ',') {
13656         if (*src == ' ') {
13657             *(*dest)++ = '_';
13658             src++;
13659         } else {
13660             *(*dest)++ = *src++;
13661         }
13662     }
13663 }
13664
13665 char *
13666 DefaultFileName (char *ext)
13667 {
13668     static char def[MSG_SIZ];
13669     char *p;
13670
13671     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13672         p = def;
13673         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13674         *p++ = '-';
13675         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13676         *p++ = '.';
13677         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13678     } else {
13679         def[0] = NULLCHAR;
13680     }
13681     return def;
13682 }
13683
13684 /* Save the current game to the given file */
13685 int
13686 SaveGameToFile (char *filename, int append)
13687 {
13688     FILE *f;
13689     char buf[MSG_SIZ];
13690     int result, i, t,tot=0;
13691
13692     if (strcmp(filename, "-") == 0) {
13693         return SaveGame(stdout, 0, NULL);
13694     } else {
13695         for(i=0; i<10; i++) { // upto 10 tries
13696              f = fopen(filename, append ? "a" : "w");
13697              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13698              if(f || errno != 13) break;
13699              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13700              tot += t;
13701         }
13702         if (f == NULL) {
13703             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13704             DisplayError(buf, errno);
13705             return FALSE;
13706         } else {
13707             safeStrCpy(buf, lastMsg, MSG_SIZ);
13708             DisplayMessage(_("Waiting for access to save file"), "");
13709             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13710             DisplayMessage(_("Saving game"), "");
13711             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13712             result = SaveGame(f, 0, NULL);
13713             DisplayMessage(buf, "");
13714             return result;
13715         }
13716     }
13717 }
13718
13719 char *
13720 SavePart (char *str)
13721 {
13722     static char buf[MSG_SIZ];
13723     char *p;
13724
13725     p = strchr(str, ' ');
13726     if (p == NULL) return str;
13727     strncpy(buf, str, p - str);
13728     buf[p - str] = NULLCHAR;
13729     return buf;
13730 }
13731
13732 #define PGN_MAX_LINE 75
13733
13734 #define PGN_SIDE_WHITE  0
13735 #define PGN_SIDE_BLACK  1
13736
13737 static int
13738 FindFirstMoveOutOfBook (int side)
13739 {
13740     int result = -1;
13741
13742     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13743         int index = backwardMostMove;
13744         int has_book_hit = 0;
13745
13746         if( (index % 2) != side ) {
13747             index++;
13748         }
13749
13750         while( index < forwardMostMove ) {
13751             /* Check to see if engine is in book */
13752             int depth = pvInfoList[index].depth;
13753             int score = pvInfoList[index].score;
13754             int in_book = 0;
13755
13756             if( depth <= 2 ) {
13757                 in_book = 1;
13758             }
13759             else if( score == 0 && depth == 63 ) {
13760                 in_book = 1; /* Zappa */
13761             }
13762             else if( score == 2 && depth == 99 ) {
13763                 in_book = 1; /* Abrok */
13764             }
13765
13766             has_book_hit += in_book;
13767
13768             if( ! in_book ) {
13769                 result = index;
13770
13771                 break;
13772             }
13773
13774             index += 2;
13775         }
13776     }
13777
13778     return result;
13779 }
13780
13781 void
13782 GetOutOfBookInfo (char * buf)
13783 {
13784     int oob[2];
13785     int i;
13786     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13787
13788     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13789     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13790
13791     *buf = '\0';
13792
13793     if( oob[0] >= 0 || oob[1] >= 0 ) {
13794         for( i=0; i<2; i++ ) {
13795             int idx = oob[i];
13796
13797             if( idx >= 0 ) {
13798                 if( i > 0 && oob[0] >= 0 ) {
13799                     strcat( buf, "   " );
13800                 }
13801
13802                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13803                 sprintf( buf+strlen(buf), "%s%.2f",
13804                     pvInfoList[idx].score >= 0 ? "+" : "",
13805                     pvInfoList[idx].score / 100.0 );
13806             }
13807         }
13808     }
13809 }
13810
13811 /* Save game in PGN style */
13812 static void
13813 SaveGamePGN2 (FILE *f)
13814 {
13815     int i, offset, linelen, newblock;
13816 //    char *movetext;
13817     char numtext[32];
13818     int movelen, numlen, blank;
13819     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13820
13821     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13822
13823     PrintPGNTags(f, &gameInfo);
13824
13825     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13826
13827     if (backwardMostMove > 0 || startedFromSetupPosition) {
13828         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13829         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13830         fprintf(f, "\n{--------------\n");
13831         PrintPosition(f, backwardMostMove);
13832         fprintf(f, "--------------}\n");
13833         free(fen);
13834     }
13835     else {
13836         /* [AS] Out of book annotation */
13837         if( appData.saveOutOfBookInfo ) {
13838             char buf[64];
13839
13840             GetOutOfBookInfo( buf );
13841
13842             if( buf[0] != '\0' ) {
13843                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13844             }
13845         }
13846
13847         fprintf(f, "\n");
13848     }
13849
13850     i = backwardMostMove;
13851     linelen = 0;
13852     newblock = TRUE;
13853
13854     while (i < forwardMostMove) {
13855         /* Print comments preceding this move */
13856         if (commentList[i] != NULL) {
13857             if (linelen > 0) fprintf(f, "\n");
13858             fprintf(f, "%s", commentList[i]);
13859             linelen = 0;
13860             newblock = TRUE;
13861         }
13862
13863         /* Format move number */
13864         if ((i % 2) == 0)
13865           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13866         else
13867           if (newblock)
13868             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13869           else
13870             numtext[0] = NULLCHAR;
13871
13872         numlen = strlen(numtext);
13873         newblock = FALSE;
13874
13875         /* Print move number */
13876         blank = linelen > 0 && numlen > 0;
13877         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13878             fprintf(f, "\n");
13879             linelen = 0;
13880             blank = 0;
13881         }
13882         if (blank) {
13883             fprintf(f, " ");
13884             linelen++;
13885         }
13886         fprintf(f, "%s", numtext);
13887         linelen += numlen;
13888
13889         /* Get move */
13890         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13891         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13892
13893         /* Print move */
13894         blank = linelen > 0 && movelen > 0;
13895         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13896             fprintf(f, "\n");
13897             linelen = 0;
13898             blank = 0;
13899         }
13900         if (blank) {
13901             fprintf(f, " ");
13902             linelen++;
13903         }
13904         fprintf(f, "%s", move_buffer);
13905         linelen += movelen;
13906
13907         /* [AS] Add PV info if present */
13908         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13909             /* [HGM] add time */
13910             char buf[MSG_SIZ]; int seconds;
13911
13912             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13913
13914             if( seconds <= 0)
13915               buf[0] = 0;
13916             else
13917               if( seconds < 30 )
13918                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13919               else
13920                 {
13921                   seconds = (seconds + 4)/10; // round to full seconds
13922                   if( seconds < 60 )
13923                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13924                   else
13925                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13926                 }
13927
13928             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13929                       pvInfoList[i].score >= 0 ? "+" : "",
13930                       pvInfoList[i].score / 100.0,
13931                       pvInfoList[i].depth,
13932                       buf );
13933
13934             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13935
13936             /* Print score/depth */
13937             blank = linelen > 0 && movelen > 0;
13938             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13939                 fprintf(f, "\n");
13940                 linelen = 0;
13941                 blank = 0;
13942             }
13943             if (blank) {
13944                 fprintf(f, " ");
13945                 linelen++;
13946             }
13947             fprintf(f, "%s", move_buffer);
13948             linelen += movelen;
13949         }
13950
13951         i++;
13952     }
13953
13954     /* Start a new line */
13955     if (linelen > 0) fprintf(f, "\n");
13956
13957     /* Print comments after last move */
13958     if (commentList[i] != NULL) {
13959         fprintf(f, "%s\n", commentList[i]);
13960     }
13961
13962     /* Print result */
13963     if (gameInfo.resultDetails != NULL &&
13964         gameInfo.resultDetails[0] != NULLCHAR) {
13965         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13966         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13967            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13968             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13969         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13970     } else {
13971         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13972     }
13973 }
13974
13975 /* Save game in PGN style and close the file */
13976 int
13977 SaveGamePGN (FILE *f)
13978 {
13979     SaveGamePGN2(f);
13980     fclose(f);
13981     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13982     return TRUE;
13983 }
13984
13985 /* Save game in old style and close the file */
13986 int
13987 SaveGameOldStyle (FILE *f)
13988 {
13989     int i, offset;
13990     time_t tm;
13991
13992     tm = time((time_t *) NULL);
13993
13994     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13995     PrintOpponents(f);
13996
13997     if (backwardMostMove > 0 || startedFromSetupPosition) {
13998         fprintf(f, "\n[--------------\n");
13999         PrintPosition(f, backwardMostMove);
14000         fprintf(f, "--------------]\n");
14001     } else {
14002         fprintf(f, "\n");
14003     }
14004
14005     i = backwardMostMove;
14006     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14007
14008     while (i < forwardMostMove) {
14009         if (commentList[i] != NULL) {
14010             fprintf(f, "[%s]\n", commentList[i]);
14011         }
14012
14013         if ((i % 2) == 1) {
14014             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14015             i++;
14016         } else {
14017             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14018             i++;
14019             if (commentList[i] != NULL) {
14020                 fprintf(f, "\n");
14021                 continue;
14022             }
14023             if (i >= forwardMostMove) {
14024                 fprintf(f, "\n");
14025                 break;
14026             }
14027             fprintf(f, "%s\n", parseList[i]);
14028             i++;
14029         }
14030     }
14031
14032     if (commentList[i] != NULL) {
14033         fprintf(f, "[%s]\n", commentList[i]);
14034     }
14035
14036     /* This isn't really the old style, but it's close enough */
14037     if (gameInfo.resultDetails != NULL &&
14038         gameInfo.resultDetails[0] != NULLCHAR) {
14039         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14040                 gameInfo.resultDetails);
14041     } else {
14042         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14043     }
14044
14045     fclose(f);
14046     return TRUE;
14047 }
14048
14049 /* Save the current game to open file f and close the file */
14050 int
14051 SaveGame (FILE *f, int dummy, char *dummy2)
14052 {
14053     if (gameMode == EditPosition) EditPositionDone(TRUE);
14054     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14055     if (appData.oldSaveStyle)
14056       return SaveGameOldStyle(f);
14057     else
14058       return SaveGamePGN(f);
14059 }
14060
14061 /* Save the current position to the given file */
14062 int
14063 SavePositionToFile (char *filename)
14064 {
14065     FILE *f;
14066     char buf[MSG_SIZ];
14067
14068     if (strcmp(filename, "-") == 0) {
14069         return SavePosition(stdout, 0, NULL);
14070     } else {
14071         f = fopen(filename, "a");
14072         if (f == NULL) {
14073             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14074             DisplayError(buf, errno);
14075             return FALSE;
14076         } else {
14077             safeStrCpy(buf, lastMsg, MSG_SIZ);
14078             DisplayMessage(_("Waiting for access to save file"), "");
14079             flock(fileno(f), LOCK_EX); // [HGM] lock
14080             DisplayMessage(_("Saving position"), "");
14081             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14082             SavePosition(f, 0, NULL);
14083             DisplayMessage(buf, "");
14084             return TRUE;
14085         }
14086     }
14087 }
14088
14089 /* Save the current position to the given open file and close the file */
14090 int
14091 SavePosition (FILE *f, int dummy, char *dummy2)
14092 {
14093     time_t tm;
14094     char *fen;
14095
14096     if (gameMode == EditPosition) EditPositionDone(TRUE);
14097     if (appData.oldSaveStyle) {
14098         tm = time((time_t *) NULL);
14099
14100         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14101         PrintOpponents(f);
14102         fprintf(f, "[--------------\n");
14103         PrintPosition(f, currentMove);
14104         fprintf(f, "--------------]\n");
14105     } else {
14106         fen = PositionToFEN(currentMove, NULL, 1);
14107         fprintf(f, "%s\n", fen);
14108         free(fen);
14109     }
14110     fclose(f);
14111     return TRUE;
14112 }
14113
14114 void
14115 ReloadCmailMsgEvent (int unregister)
14116 {
14117 #if !WIN32
14118     static char *inFilename = NULL;
14119     static char *outFilename;
14120     int i;
14121     struct stat inbuf, outbuf;
14122     int status;
14123
14124     /* Any registered moves are unregistered if unregister is set, */
14125     /* i.e. invoked by the signal handler */
14126     if (unregister) {
14127         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14128             cmailMoveRegistered[i] = FALSE;
14129             if (cmailCommentList[i] != NULL) {
14130                 free(cmailCommentList[i]);
14131                 cmailCommentList[i] = NULL;
14132             }
14133         }
14134         nCmailMovesRegistered = 0;
14135     }
14136
14137     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14138         cmailResult[i] = CMAIL_NOT_RESULT;
14139     }
14140     nCmailResults = 0;
14141
14142     if (inFilename == NULL) {
14143         /* Because the filenames are static they only get malloced once  */
14144         /* and they never get freed                                      */
14145         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14146         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14147
14148         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14149         sprintf(outFilename, "%s.out", appData.cmailGameName);
14150     }
14151
14152     status = stat(outFilename, &outbuf);
14153     if (status < 0) {
14154         cmailMailedMove = FALSE;
14155     } else {
14156         status = stat(inFilename, &inbuf);
14157         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14158     }
14159
14160     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14161        counts the games, notes how each one terminated, etc.
14162
14163        It would be nice to remove this kludge and instead gather all
14164        the information while building the game list.  (And to keep it
14165        in the game list nodes instead of having a bunch of fixed-size
14166        parallel arrays.)  Note this will require getting each game's
14167        termination from the PGN tags, as the game list builder does
14168        not process the game moves.  --mann
14169        */
14170     cmailMsgLoaded = TRUE;
14171     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14172
14173     /* Load first game in the file or popup game menu */
14174     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14175
14176 #endif /* !WIN32 */
14177     return;
14178 }
14179
14180 int
14181 RegisterMove ()
14182 {
14183     FILE *f;
14184     char string[MSG_SIZ];
14185
14186     if (   cmailMailedMove
14187         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14188         return TRUE;            /* Allow free viewing  */
14189     }
14190
14191     /* Unregister move to ensure that we don't leave RegisterMove        */
14192     /* with the move registered when the conditions for registering no   */
14193     /* longer hold                                                       */
14194     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14195         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14196         nCmailMovesRegistered --;
14197
14198         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14199           {
14200               free(cmailCommentList[lastLoadGameNumber - 1]);
14201               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14202           }
14203     }
14204
14205     if (cmailOldMove == -1) {
14206         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14207         return FALSE;
14208     }
14209
14210     if (currentMove > cmailOldMove + 1) {
14211         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14212         return FALSE;
14213     }
14214
14215     if (currentMove < cmailOldMove) {
14216         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14217         return FALSE;
14218     }
14219
14220     if (forwardMostMove > currentMove) {
14221         /* Silently truncate extra moves */
14222         TruncateGame();
14223     }
14224
14225     if (   (currentMove == cmailOldMove + 1)
14226         || (   (currentMove == cmailOldMove)
14227             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14228                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14229         if (gameInfo.result != GameUnfinished) {
14230             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14231         }
14232
14233         if (commentList[currentMove] != NULL) {
14234             cmailCommentList[lastLoadGameNumber - 1]
14235               = StrSave(commentList[currentMove]);
14236         }
14237         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14238
14239         if (appData.debugMode)
14240           fprintf(debugFP, "Saving %s for game %d\n",
14241                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14242
14243         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14244
14245         f = fopen(string, "w");
14246         if (appData.oldSaveStyle) {
14247             SaveGameOldStyle(f); /* also closes the file */
14248
14249             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14250             f = fopen(string, "w");
14251             SavePosition(f, 0, NULL); /* also closes the file */
14252         } else {
14253             fprintf(f, "{--------------\n");
14254             PrintPosition(f, currentMove);
14255             fprintf(f, "--------------}\n\n");
14256
14257             SaveGame(f, 0, NULL); /* also closes the file*/
14258         }
14259
14260         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14261         nCmailMovesRegistered ++;
14262     } else if (nCmailGames == 1) {
14263         DisplayError(_("You have not made a move yet"), 0);
14264         return FALSE;
14265     }
14266
14267     return TRUE;
14268 }
14269
14270 void
14271 MailMoveEvent ()
14272 {
14273 #if !WIN32
14274     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14275     FILE *commandOutput;
14276     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14277     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14278     int nBuffers;
14279     int i;
14280     int archived;
14281     char *arcDir;
14282
14283     if (! cmailMsgLoaded) {
14284         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14285         return;
14286     }
14287
14288     if (nCmailGames == nCmailResults) {
14289         DisplayError(_("No unfinished games"), 0);
14290         return;
14291     }
14292
14293 #if CMAIL_PROHIBIT_REMAIL
14294     if (cmailMailedMove) {
14295       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);
14296         DisplayError(msg, 0);
14297         return;
14298     }
14299 #endif
14300
14301     if (! (cmailMailedMove || RegisterMove())) return;
14302
14303     if (   cmailMailedMove
14304         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14305       snprintf(string, MSG_SIZ, partCommandString,
14306                appData.debugMode ? " -v" : "", appData.cmailGameName);
14307         commandOutput = popen(string, "r");
14308
14309         if (commandOutput == NULL) {
14310             DisplayError(_("Failed to invoke cmail"), 0);
14311         } else {
14312             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14313                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14314             }
14315             if (nBuffers > 1) {
14316                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14317                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14318                 nBytes = MSG_SIZ - 1;
14319             } else {
14320                 (void) memcpy(msg, buffer, nBytes);
14321             }
14322             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14323
14324             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14325                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14326
14327                 archived = TRUE;
14328                 for (i = 0; i < nCmailGames; i ++) {
14329                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14330                         archived = FALSE;
14331                     }
14332                 }
14333                 if (   archived
14334                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14335                         != NULL)) {
14336                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14337                            arcDir,
14338                            appData.cmailGameName,
14339                            gameInfo.date);
14340                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14341                     cmailMsgLoaded = FALSE;
14342                 }
14343             }
14344
14345             DisplayInformation(msg);
14346             pclose(commandOutput);
14347         }
14348     } else {
14349         if ((*cmailMsg) != '\0') {
14350             DisplayInformation(cmailMsg);
14351         }
14352     }
14353
14354     return;
14355 #endif /* !WIN32 */
14356 }
14357
14358 char *
14359 CmailMsg ()
14360 {
14361 #if WIN32
14362     return NULL;
14363 #else
14364     int  prependComma = 0;
14365     char number[5];
14366     char string[MSG_SIZ];       /* Space for game-list */
14367     int  i;
14368
14369     if (!cmailMsgLoaded) return "";
14370
14371     if (cmailMailedMove) {
14372       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14373     } else {
14374         /* Create a list of games left */
14375       snprintf(string, MSG_SIZ, "[");
14376         for (i = 0; i < nCmailGames; i ++) {
14377             if (! (   cmailMoveRegistered[i]
14378                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14379                 if (prependComma) {
14380                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14381                 } else {
14382                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14383                     prependComma = 1;
14384                 }
14385
14386                 strcat(string, number);
14387             }
14388         }
14389         strcat(string, "]");
14390
14391         if (nCmailMovesRegistered + nCmailResults == 0) {
14392             switch (nCmailGames) {
14393               case 1:
14394                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14395                 break;
14396
14397               case 2:
14398                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14399                 break;
14400
14401               default:
14402                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14403                          nCmailGames);
14404                 break;
14405             }
14406         } else {
14407             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14408               case 1:
14409                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14410                          string);
14411                 break;
14412
14413               case 0:
14414                 if (nCmailResults == nCmailGames) {
14415                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14416                 } else {
14417                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14418                 }
14419                 break;
14420
14421               default:
14422                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14423                          string);
14424             }
14425         }
14426     }
14427     return cmailMsg;
14428 #endif /* WIN32 */
14429 }
14430
14431 void
14432 ResetGameEvent ()
14433 {
14434     if (gameMode == Training)
14435       SetTrainingModeOff();
14436
14437     Reset(TRUE, TRUE);
14438     cmailMsgLoaded = FALSE;
14439     if (appData.icsActive) {
14440       SendToICS(ics_prefix);
14441       SendToICS("refresh\n");
14442     }
14443 }
14444
14445 void
14446 ExitEvent (int status)
14447 {
14448     exiting++;
14449     if (exiting > 2) {
14450       /* Give up on clean exit */
14451       exit(status);
14452     }
14453     if (exiting > 1) {
14454       /* Keep trying for clean exit */
14455       return;
14456     }
14457
14458     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14459     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14460
14461     if (telnetISR != NULL) {
14462       RemoveInputSource(telnetISR);
14463     }
14464     if (icsPR != NoProc) {
14465       DestroyChildProcess(icsPR, TRUE);
14466     }
14467
14468     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14469     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14470
14471     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14472     /* make sure this other one finishes before killing it!                  */
14473     if(endingGame) { int count = 0;
14474         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14475         while(endingGame && count++ < 10) DoSleep(1);
14476         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14477     }
14478
14479     /* Kill off chess programs */
14480     if (first.pr != NoProc) {
14481         ExitAnalyzeMode();
14482
14483         DoSleep( appData.delayBeforeQuit );
14484         SendToProgram("quit\n", &first);
14485         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14486     }
14487     if (second.pr != NoProc) {
14488         DoSleep( appData.delayBeforeQuit );
14489         SendToProgram("quit\n", &second);
14490         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14491     }
14492     if (first.isr != NULL) {
14493         RemoveInputSource(first.isr);
14494     }
14495     if (second.isr != NULL) {
14496         RemoveInputSource(second.isr);
14497     }
14498
14499     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14500     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14501
14502     ShutDownFrontEnd();
14503     exit(status);
14504 }
14505
14506 void
14507 PauseEngine (ChessProgramState *cps)
14508 {
14509     SendToProgram("pause\n", cps);
14510     cps->pause = 2;
14511 }
14512
14513 void
14514 UnPauseEngine (ChessProgramState *cps)
14515 {
14516     SendToProgram("resume\n", cps);
14517     cps->pause = 1;
14518 }
14519
14520 void
14521 PauseEvent ()
14522 {
14523     if (appData.debugMode)
14524         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14525     if (pausing) {
14526         pausing = FALSE;
14527         ModeHighlight();
14528         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14529             StartClocks();
14530             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14531                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14532                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14533             }
14534             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14535             HandleMachineMove(stashedInputMove, stalledEngine);
14536             stalledEngine = NULL;
14537             return;
14538         }
14539         if (gameMode == MachinePlaysWhite ||
14540             gameMode == TwoMachinesPlay   ||
14541             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14542             if(first.pause)  UnPauseEngine(&first);
14543             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14544             if(second.pause) UnPauseEngine(&second);
14545             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14546             StartClocks();
14547         } else {
14548             DisplayBothClocks();
14549         }
14550         if (gameMode == PlayFromGameFile) {
14551             if (appData.timeDelay >= 0)
14552                 AutoPlayGameLoop();
14553         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14554             Reset(FALSE, TRUE);
14555             SendToICS(ics_prefix);
14556             SendToICS("refresh\n");
14557         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14558             ForwardInner(forwardMostMove);
14559         }
14560         pauseExamInvalid = FALSE;
14561     } else {
14562         switch (gameMode) {
14563           default:
14564             return;
14565           case IcsExamining:
14566             pauseExamForwardMostMove = forwardMostMove;
14567             pauseExamInvalid = FALSE;
14568             /* fall through */
14569           case IcsObserving:
14570           case IcsPlayingWhite:
14571           case IcsPlayingBlack:
14572             pausing = TRUE;
14573             ModeHighlight();
14574             return;
14575           case PlayFromGameFile:
14576             (void) StopLoadGameTimer();
14577             pausing = TRUE;
14578             ModeHighlight();
14579             break;
14580           case BeginningOfGame:
14581             if (appData.icsActive) return;
14582             /* else fall through */
14583           case MachinePlaysWhite:
14584           case MachinePlaysBlack:
14585           case TwoMachinesPlay:
14586             if (forwardMostMove == 0)
14587               return;           /* don't pause if no one has moved */
14588             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14589                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14590                 if(onMove->pause) {           // thinking engine can be paused
14591                     PauseEngine(onMove);      // do it
14592                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14593                         PauseEngine(onMove->other);
14594                     else
14595                         SendToProgram("easy\n", onMove->other);
14596                     StopClocks();
14597                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14598             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14599                 if(first.pause) {
14600                     PauseEngine(&first);
14601                     StopClocks();
14602                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14603             } else { // human on move, pause pondering by either method
14604                 if(first.pause)
14605                     PauseEngine(&first);
14606                 else if(appData.ponderNextMove)
14607                     SendToProgram("easy\n", &first);
14608                 StopClocks();
14609             }
14610             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14611           case AnalyzeMode:
14612             pausing = TRUE;
14613             ModeHighlight();
14614             break;
14615         }
14616     }
14617 }
14618
14619 void
14620 EditCommentEvent ()
14621 {
14622     char title[MSG_SIZ];
14623
14624     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14625       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14626     } else {
14627       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14628                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14629                parseList[currentMove - 1]);
14630     }
14631
14632     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14633 }
14634
14635
14636 void
14637 EditTagsEvent ()
14638 {
14639     char *tags = PGNTags(&gameInfo);
14640     bookUp = FALSE;
14641     EditTagsPopUp(tags, NULL);
14642     free(tags);
14643 }
14644
14645 void
14646 ToggleSecond ()
14647 {
14648   if(second.analyzing) {
14649     SendToProgram("exit\n", &second);
14650     second.analyzing = FALSE;
14651   } else {
14652     if (second.pr == NoProc) StartChessProgram(&second);
14653     InitChessProgram(&second, FALSE);
14654     FeedMovesToProgram(&second, currentMove);
14655
14656     SendToProgram("analyze\n", &second);
14657     second.analyzing = TRUE;
14658   }
14659 }
14660
14661 /* Toggle ShowThinking */
14662 void
14663 ToggleShowThinking()
14664 {
14665   appData.showThinking = !appData.showThinking;
14666   ShowThinkingEvent();
14667 }
14668
14669 int
14670 AnalyzeModeEvent ()
14671 {
14672     char buf[MSG_SIZ];
14673
14674     if (!first.analysisSupport) {
14675       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14676       DisplayError(buf, 0);
14677       return 0;
14678     }
14679     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14680     if (appData.icsActive) {
14681         if (gameMode != IcsObserving) {
14682           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14683             DisplayError(buf, 0);
14684             /* secure check */
14685             if (appData.icsEngineAnalyze) {
14686                 if (appData.debugMode)
14687                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14688                 ExitAnalyzeMode();
14689                 ModeHighlight();
14690             }
14691             return 0;
14692         }
14693         /* if enable, user wants to disable icsEngineAnalyze */
14694         if (appData.icsEngineAnalyze) {
14695                 ExitAnalyzeMode();
14696                 ModeHighlight();
14697                 return 0;
14698         }
14699         appData.icsEngineAnalyze = TRUE;
14700         if (appData.debugMode)
14701             fprintf(debugFP, "ICS engine analyze starting... \n");
14702     }
14703
14704     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14705     if (appData.noChessProgram || gameMode == AnalyzeMode)
14706       return 0;
14707
14708     if (gameMode != AnalyzeFile) {
14709         if (!appData.icsEngineAnalyze) {
14710                EditGameEvent();
14711                if (gameMode != EditGame) return 0;
14712         }
14713         if (!appData.showThinking) ToggleShowThinking();
14714         ResurrectChessProgram();
14715         SendToProgram("analyze\n", &first);
14716         first.analyzing = TRUE;
14717         /*first.maybeThinking = TRUE;*/
14718         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14719         EngineOutputPopUp();
14720     }
14721     if (!appData.icsEngineAnalyze) {
14722         gameMode = AnalyzeMode;
14723         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14724     }
14725     pausing = FALSE;
14726     ModeHighlight();
14727     SetGameInfo();
14728
14729     StartAnalysisClock();
14730     GetTimeMark(&lastNodeCountTime);
14731     lastNodeCount = 0;
14732     return 1;
14733 }
14734
14735 void
14736 AnalyzeFileEvent ()
14737 {
14738     if (appData.noChessProgram || gameMode == AnalyzeFile)
14739       return;
14740
14741     if (!first.analysisSupport) {
14742       char buf[MSG_SIZ];
14743       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14744       DisplayError(buf, 0);
14745       return;
14746     }
14747
14748     if (gameMode != AnalyzeMode) {
14749         keepInfo = 1; // mere annotating should not alter PGN tags
14750         EditGameEvent();
14751         keepInfo = 0;
14752         if (gameMode != EditGame) return;
14753         if (!appData.showThinking) ToggleShowThinking();
14754         ResurrectChessProgram();
14755         SendToProgram("analyze\n", &first);
14756         first.analyzing = TRUE;
14757         /*first.maybeThinking = TRUE;*/
14758         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14759         EngineOutputPopUp();
14760     }
14761     gameMode = AnalyzeFile;
14762     pausing = FALSE;
14763     ModeHighlight();
14764
14765     StartAnalysisClock();
14766     GetTimeMark(&lastNodeCountTime);
14767     lastNodeCount = 0;
14768     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14769     AnalysisPeriodicEvent(1);
14770 }
14771
14772 void
14773 MachineWhiteEvent ()
14774 {
14775     char buf[MSG_SIZ];
14776     char *bookHit = NULL;
14777
14778     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14779       return;
14780
14781
14782     if (gameMode == PlayFromGameFile ||
14783         gameMode == TwoMachinesPlay  ||
14784         gameMode == Training         ||
14785         gameMode == AnalyzeMode      ||
14786         gameMode == EndOfGame)
14787         EditGameEvent();
14788
14789     if (gameMode == EditPosition)
14790         EditPositionDone(TRUE);
14791
14792     if (!WhiteOnMove(currentMove)) {
14793         DisplayError(_("It is not White's turn"), 0);
14794         return;
14795     }
14796
14797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14798       ExitAnalyzeMode();
14799
14800     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14801         gameMode == AnalyzeFile)
14802         TruncateGame();
14803
14804     ResurrectChessProgram();    /* in case it isn't running */
14805     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14806         gameMode = MachinePlaysWhite;
14807         ResetClocks();
14808     } else
14809     gameMode = MachinePlaysWhite;
14810     pausing = FALSE;
14811     ModeHighlight();
14812     SetGameInfo();
14813     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14814     DisplayTitle(buf);
14815     if (first.sendName) {
14816       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14817       SendToProgram(buf, &first);
14818     }
14819     if (first.sendTime) {
14820       if (first.useColors) {
14821         SendToProgram("black\n", &first); /*gnu kludge*/
14822       }
14823       SendTimeRemaining(&first, TRUE);
14824     }
14825     if (first.useColors) {
14826       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14827     }
14828     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14829     SetMachineThinkingEnables();
14830     first.maybeThinking = TRUE;
14831     StartClocks();
14832     firstMove = FALSE;
14833
14834     if (appData.autoFlipView && !flipView) {
14835       flipView = !flipView;
14836       DrawPosition(FALSE, NULL);
14837       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14838     }
14839
14840     if(bookHit) { // [HGM] book: simulate book reply
14841         static char bookMove[MSG_SIZ]; // a bit generous?
14842
14843         programStats.nodes = programStats.depth = programStats.time =
14844         programStats.score = programStats.got_only_move = 0;
14845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14846
14847         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14848         strcat(bookMove, bookHit);
14849         HandleMachineMove(bookMove, &first);
14850     }
14851 }
14852
14853 void
14854 MachineBlackEvent ()
14855 {
14856   char buf[MSG_SIZ];
14857   char *bookHit = NULL;
14858
14859     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14860         return;
14861
14862
14863     if (gameMode == PlayFromGameFile ||
14864         gameMode == TwoMachinesPlay  ||
14865         gameMode == Training         ||
14866         gameMode == AnalyzeMode      ||
14867         gameMode == EndOfGame)
14868         EditGameEvent();
14869
14870     if (gameMode == EditPosition)
14871         EditPositionDone(TRUE);
14872
14873     if (WhiteOnMove(currentMove)) {
14874         DisplayError(_("It is not Black's turn"), 0);
14875         return;
14876     }
14877
14878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14879       ExitAnalyzeMode();
14880
14881     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14882         gameMode == AnalyzeFile)
14883         TruncateGame();
14884
14885     ResurrectChessProgram();    /* in case it isn't running */
14886     gameMode = MachinePlaysBlack;
14887     pausing = FALSE;
14888     ModeHighlight();
14889     SetGameInfo();
14890     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14891     DisplayTitle(buf);
14892     if (first.sendName) {
14893       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14894       SendToProgram(buf, &first);
14895     }
14896     if (first.sendTime) {
14897       if (first.useColors) {
14898         SendToProgram("white\n", &first); /*gnu kludge*/
14899       }
14900       SendTimeRemaining(&first, FALSE);
14901     }
14902     if (first.useColors) {
14903       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14904     }
14905     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14906     SetMachineThinkingEnables();
14907     first.maybeThinking = TRUE;
14908     StartClocks();
14909
14910     if (appData.autoFlipView && flipView) {
14911       flipView = !flipView;
14912       DrawPosition(FALSE, NULL);
14913       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14914     }
14915     if(bookHit) { // [HGM] book: simulate book reply
14916         static char bookMove[MSG_SIZ]; // a bit generous?
14917
14918         programStats.nodes = programStats.depth = programStats.time =
14919         programStats.score = programStats.got_only_move = 0;
14920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14921
14922         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14923         strcat(bookMove, bookHit);
14924         HandleMachineMove(bookMove, &first);
14925     }
14926 }
14927
14928
14929 void
14930 DisplayTwoMachinesTitle ()
14931 {
14932     char buf[MSG_SIZ];
14933     if (appData.matchGames > 0) {
14934         if(appData.tourneyFile[0]) {
14935           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14936                    gameInfo.white, _("vs."), gameInfo.black,
14937                    nextGame+1, appData.matchGames+1,
14938                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14939         } else
14940         if (first.twoMachinesColor[0] == 'w') {
14941           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14942                    gameInfo.white, _("vs."),  gameInfo.black,
14943                    first.matchWins, second.matchWins,
14944                    matchGame - 1 - (first.matchWins + second.matchWins));
14945         } else {
14946           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14947                    gameInfo.white, _("vs."), gameInfo.black,
14948                    second.matchWins, first.matchWins,
14949                    matchGame - 1 - (first.matchWins + second.matchWins));
14950         }
14951     } else {
14952       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14953     }
14954     DisplayTitle(buf);
14955 }
14956
14957 void
14958 SettingsMenuIfReady ()
14959 {
14960   if (second.lastPing != second.lastPong) {
14961     DisplayMessage("", _("Waiting for second chess program"));
14962     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14963     return;
14964   }
14965   ThawUI();
14966   DisplayMessage("", "");
14967   SettingsPopUp(&second);
14968 }
14969
14970 int
14971 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14972 {
14973     char buf[MSG_SIZ];
14974     if (cps->pr == NoProc) {
14975         StartChessProgram(cps);
14976         if (cps->protocolVersion == 1) {
14977           retry();
14978           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14979         } else {
14980           /* kludge: allow timeout for initial "feature" command */
14981           if(retry != TwoMachinesEventIfReady) FreezeUI();
14982           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14983           DisplayMessage("", buf);
14984           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14985         }
14986         return 1;
14987     }
14988     return 0;
14989 }
14990
14991 void
14992 TwoMachinesEvent P((void))
14993 {
14994     int i;
14995     char buf[MSG_SIZ];
14996     ChessProgramState *onmove;
14997     char *bookHit = NULL;
14998     static int stalling = 0;
14999     TimeMark now;
15000     long wait;
15001
15002     if (appData.noChessProgram) return;
15003
15004     switch (gameMode) {
15005       case TwoMachinesPlay:
15006         return;
15007       case MachinePlaysWhite:
15008       case MachinePlaysBlack:
15009         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15010             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15011             return;
15012         }
15013         /* fall through */
15014       case BeginningOfGame:
15015       case PlayFromGameFile:
15016       case EndOfGame:
15017         EditGameEvent();
15018         if (gameMode != EditGame) return;
15019         break;
15020       case EditPosition:
15021         EditPositionDone(TRUE);
15022         break;
15023       case AnalyzeMode:
15024       case AnalyzeFile:
15025         ExitAnalyzeMode();
15026         break;
15027       case EditGame:
15028       default:
15029         break;
15030     }
15031
15032 //    forwardMostMove = currentMove;
15033     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15034     startingEngine = TRUE;
15035
15036     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15037
15038     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15039     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15040       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15041       return;
15042     }
15043   if(!appData.epd) {
15044     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15045
15046     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15047                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15048         startingEngine = matchMode = FALSE;
15049         DisplayError("second engine does not play this", 0);
15050         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15051         EditGameEvent(); // switch back to EditGame mode
15052         return;
15053     }
15054
15055     if(!stalling) {
15056       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15057       SendToProgram("force\n", &second);
15058       stalling = 1;
15059       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15060       return;
15061     }
15062   }
15063     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15064     if(appData.matchPause>10000 || appData.matchPause<10)
15065                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15066     wait = SubtractTimeMarks(&now, &pauseStart);
15067     if(wait < appData.matchPause) {
15068         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15069         return;
15070     }
15071     // we are now committed to starting the game
15072     stalling = 0;
15073     DisplayMessage("", "");
15074   if(!appData.epd) {
15075     if (startedFromSetupPosition) {
15076         SendBoard(&second, backwardMostMove);
15077     if (appData.debugMode) {
15078         fprintf(debugFP, "Two Machines\n");
15079     }
15080     }
15081     for (i = backwardMostMove; i < forwardMostMove; i++) {
15082         SendMoveToProgram(i, &second);
15083     }
15084   }
15085
15086     gameMode = TwoMachinesPlay;
15087     pausing = startingEngine = FALSE;
15088     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15089     SetGameInfo();
15090     DisplayTwoMachinesTitle();
15091     firstMove = TRUE;
15092     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15093         onmove = &first;
15094     } else {
15095         onmove = &second;
15096     }
15097     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15098     SendToProgram(first.computerString, &first);
15099     if (first.sendName) {
15100       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15101       SendToProgram(buf, &first);
15102     }
15103   if(!appData.epd) {
15104     SendToProgram(second.computerString, &second);
15105     if (second.sendName) {
15106       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15107       SendToProgram(buf, &second);
15108     }
15109   }
15110
15111     ResetClocks();
15112     if (!first.sendTime || !second.sendTime) {
15113         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15114         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15115     }
15116     if (onmove->sendTime) {
15117       if (onmove->useColors) {
15118         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15119       }
15120       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15121     }
15122     if (onmove->useColors) {
15123       SendToProgram(onmove->twoMachinesColor, onmove);
15124     }
15125     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15126 //    SendToProgram("go\n", onmove);
15127     onmove->maybeThinking = TRUE;
15128     SetMachineThinkingEnables();
15129
15130     StartClocks();
15131
15132     if(bookHit) { // [HGM] book: simulate book reply
15133         static char bookMove[MSG_SIZ]; // a bit generous?
15134
15135         programStats.nodes = programStats.depth = programStats.time =
15136         programStats.score = programStats.got_only_move = 0;
15137         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15138
15139         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15140         strcat(bookMove, bookHit);
15141         savedMessage = bookMove; // args for deferred call
15142         savedState = onmove;
15143         ScheduleDelayedEvent(DeferredBookMove, 1);
15144     }
15145 }
15146
15147 void
15148 TrainingEvent ()
15149 {
15150     if (gameMode == Training) {
15151       SetTrainingModeOff();
15152       gameMode = PlayFromGameFile;
15153       DisplayMessage("", _("Training mode off"));
15154     } else {
15155       gameMode = Training;
15156       animateTraining = appData.animate;
15157
15158       /* make sure we are not already at the end of the game */
15159       if (currentMove < forwardMostMove) {
15160         SetTrainingModeOn();
15161         DisplayMessage("", _("Training mode on"));
15162       } else {
15163         gameMode = PlayFromGameFile;
15164         DisplayError(_("Already at end of game"), 0);
15165       }
15166     }
15167     ModeHighlight();
15168 }
15169
15170 void
15171 IcsClientEvent ()
15172 {
15173     if (!appData.icsActive) return;
15174     switch (gameMode) {
15175       case IcsPlayingWhite:
15176       case IcsPlayingBlack:
15177       case IcsObserving:
15178       case IcsIdle:
15179       case BeginningOfGame:
15180       case IcsExamining:
15181         return;
15182
15183       case EditGame:
15184         break;
15185
15186       case EditPosition:
15187         EditPositionDone(TRUE);
15188         break;
15189
15190       case AnalyzeMode:
15191       case AnalyzeFile:
15192         ExitAnalyzeMode();
15193         break;
15194
15195       default:
15196         EditGameEvent();
15197         break;
15198     }
15199
15200     gameMode = IcsIdle;
15201     ModeHighlight();
15202     return;
15203 }
15204
15205 void
15206 EditGameEvent ()
15207 {
15208     int i;
15209
15210     switch (gameMode) {
15211       case Training:
15212         SetTrainingModeOff();
15213         break;
15214       case MachinePlaysWhite:
15215       case MachinePlaysBlack:
15216       case BeginningOfGame:
15217         SendToProgram("force\n", &first);
15218         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15219             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15220                 char buf[MSG_SIZ];
15221                 abortEngineThink = TRUE;
15222                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15223                 SendToProgram(buf, &first);
15224                 DisplayMessage("Aborting engine think", "");
15225                 FreezeUI();
15226             }
15227         }
15228         SetUserThinkingEnables();
15229         break;
15230       case PlayFromGameFile:
15231         (void) StopLoadGameTimer();
15232         if (gameFileFP != NULL) {
15233             gameFileFP = NULL;
15234         }
15235         break;
15236       case EditPosition:
15237         EditPositionDone(TRUE);
15238         break;
15239       case AnalyzeMode:
15240       case AnalyzeFile:
15241         ExitAnalyzeMode();
15242         SendToProgram("force\n", &first);
15243         break;
15244       case TwoMachinesPlay:
15245         GameEnds(EndOfFile, NULL, GE_PLAYER);
15246         ResurrectChessProgram();
15247         SetUserThinkingEnables();
15248         break;
15249       case EndOfGame:
15250         ResurrectChessProgram();
15251         break;
15252       case IcsPlayingBlack:
15253       case IcsPlayingWhite:
15254         DisplayError(_("Warning: You are still playing a game"), 0);
15255         break;
15256       case IcsObserving:
15257         DisplayError(_("Warning: You are still observing a game"), 0);
15258         break;
15259       case IcsExamining:
15260         DisplayError(_("Warning: You are still examining a game"), 0);
15261         break;
15262       case IcsIdle:
15263         break;
15264       case EditGame:
15265       default:
15266         return;
15267     }
15268
15269     pausing = FALSE;
15270     StopClocks();
15271     first.offeredDraw = second.offeredDraw = 0;
15272
15273     if (gameMode == PlayFromGameFile) {
15274         whiteTimeRemaining = timeRemaining[0][currentMove];
15275         blackTimeRemaining = timeRemaining[1][currentMove];
15276         DisplayTitle("");
15277     }
15278
15279     if (gameMode == MachinePlaysWhite ||
15280         gameMode == MachinePlaysBlack ||
15281         gameMode == TwoMachinesPlay ||
15282         gameMode == EndOfGame) {
15283         i = forwardMostMove;
15284         while (i > currentMove) {
15285             SendToProgram("undo\n", &first);
15286             i--;
15287         }
15288         if(!adjustedClock) {
15289         whiteTimeRemaining = timeRemaining[0][currentMove];
15290         blackTimeRemaining = timeRemaining[1][currentMove];
15291         DisplayBothClocks();
15292         }
15293         if (whiteFlag || blackFlag) {
15294             whiteFlag = blackFlag = 0;
15295         }
15296         DisplayTitle("");
15297     }
15298
15299     gameMode = EditGame;
15300     ModeHighlight();
15301     SetGameInfo();
15302 }
15303
15304 void
15305 EditPositionEvent ()
15306 {
15307     int i;
15308     if (gameMode == EditPosition) {
15309         EditGameEvent();
15310         return;
15311     }
15312
15313     EditGameEvent();
15314     if (gameMode != EditGame) return;
15315
15316     gameMode = EditPosition;
15317     ModeHighlight();
15318     SetGameInfo();
15319     CopyBoard(rightsBoard, nullBoard);
15320     if (currentMove > 0)
15321       CopyBoard(boards[0], boards[currentMove]);
15322     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15323       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15324
15325     blackPlaysFirst = !WhiteOnMove(currentMove);
15326     ResetClocks();
15327     currentMove = forwardMostMove = backwardMostMove = 0;
15328     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15329     DisplayMove(-1);
15330     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15331 }
15332
15333 void
15334 ExitAnalyzeMode ()
15335 {
15336     /* [DM] icsEngineAnalyze - possible call from other functions */
15337     if (appData.icsEngineAnalyze) {
15338         appData.icsEngineAnalyze = FALSE;
15339
15340         DisplayMessage("",_("Close ICS engine analyze..."));
15341     }
15342     if (first.analysisSupport && first.analyzing) {
15343       SendToBoth("exit\n");
15344       first.analyzing = second.analyzing = FALSE;
15345     }
15346     thinkOutput[0] = NULLCHAR;
15347 }
15348
15349 void
15350 EditPositionDone (Boolean fakeRights)
15351 {
15352     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15353
15354     startedFromSetupPosition = TRUE;
15355     InitChessProgram(&first, FALSE);
15356     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15357       int r, f;
15358       boards[0][EP_STATUS] = EP_NONE;
15359       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15360       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15361         if(rightsBoard[r][f]) {
15362           ChessSquare p = boards[0][r][f];
15363           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15364           else if(p == king) boards[0][CASTLING][2] = f;
15365           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15366           else rightsBoard[r][f] = 2; // mark for second pass
15367         }
15368       }
15369       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15370         if(rightsBoard[r][f] == 2) {
15371           ChessSquare p = boards[0][r][f];
15372           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15373           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15374         }
15375       }
15376     }
15377     SendToProgram("force\n", &first);
15378     if (blackPlaysFirst) {
15379         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15380         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15381         currentMove = forwardMostMove = backwardMostMove = 1;
15382         CopyBoard(boards[1], boards[0]);
15383     } else {
15384         currentMove = forwardMostMove = backwardMostMove = 0;
15385     }
15386     SendBoard(&first, forwardMostMove);
15387     if (appData.debugMode) {
15388         fprintf(debugFP, "EditPosDone\n");
15389     }
15390     DisplayTitle("");
15391     DisplayMessage("", "");
15392     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15393     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15394     gameMode = EditGame;
15395     ModeHighlight();
15396     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15397     ClearHighlights(); /* [AS] */
15398 }
15399
15400 /* Pause for `ms' milliseconds */
15401 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15402 void
15403 TimeDelay (long ms)
15404 {
15405     TimeMark m1, m2;
15406
15407     GetTimeMark(&m1);
15408     do {
15409         GetTimeMark(&m2);
15410     } while (SubtractTimeMarks(&m2, &m1) < ms);
15411 }
15412
15413 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15414 void
15415 SendMultiLineToICS (char *buf)
15416 {
15417     char temp[MSG_SIZ+1], *p;
15418     int len;
15419
15420     len = strlen(buf);
15421     if (len > MSG_SIZ)
15422       len = MSG_SIZ;
15423
15424     strncpy(temp, buf, len);
15425     temp[len] = 0;
15426
15427     p = temp;
15428     while (*p) {
15429         if (*p == '\n' || *p == '\r')
15430           *p = ' ';
15431         ++p;
15432     }
15433
15434     strcat(temp, "\n");
15435     SendToICS(temp);
15436     SendToPlayer(temp, strlen(temp));
15437 }
15438
15439 void
15440 SetWhiteToPlayEvent ()
15441 {
15442     if (gameMode == EditPosition) {
15443         blackPlaysFirst = FALSE;
15444         DisplayBothClocks();    /* works because currentMove is 0 */
15445     } else if (gameMode == IcsExamining) {
15446         SendToICS(ics_prefix);
15447         SendToICS("tomove white\n");
15448     }
15449 }
15450
15451 void
15452 SetBlackToPlayEvent ()
15453 {
15454     if (gameMode == EditPosition) {
15455         blackPlaysFirst = TRUE;
15456         currentMove = 1;        /* kludge */
15457         DisplayBothClocks();
15458         currentMove = 0;
15459     } else if (gameMode == IcsExamining) {
15460         SendToICS(ics_prefix);
15461         SendToICS("tomove black\n");
15462     }
15463 }
15464
15465 void
15466 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15467 {
15468     char buf[MSG_SIZ];
15469     ChessSquare piece = boards[0][y][x];
15470     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15471     static int lastVariant;
15472     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15473
15474     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15475
15476     switch (selection) {
15477       case ClearBoard:
15478         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15479         MarkTargetSquares(1);
15480         CopyBoard(currentBoard, boards[0]);
15481         CopyBoard(menuBoard, initialPosition);
15482         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15483             SendToICS(ics_prefix);
15484             SendToICS("bsetup clear\n");
15485         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15486             SendToICS(ics_prefix);
15487             SendToICS("clearboard\n");
15488         } else {
15489             int nonEmpty = 0;
15490             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15491                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15492                 for (y = 0; y < BOARD_HEIGHT; y++) {
15493                     if (gameMode == IcsExamining) {
15494                         if (boards[currentMove][y][x] != EmptySquare) {
15495                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15496                                     AAA + x, ONE + y);
15497                             SendToICS(buf);
15498                         }
15499                     } else if(boards[0][y][x] != DarkSquare) {
15500                         if(boards[0][y][x] != p) nonEmpty++;
15501                         boards[0][y][x] = p;
15502                     }
15503                 }
15504             }
15505             CopyBoard(rightsBoard, nullBoard);
15506             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15507                 int r, i;
15508                 for(r = 0; r < BOARD_HEIGHT; r++) {
15509                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15510                     ChessSquare p = menuBoard[r][x];
15511                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15512                   }
15513                 }
15514                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15515                 DisplayMessage("Clicking clock again restores position", "");
15516                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15517                 if(!nonEmpty) { // asked to clear an empty board
15518                     CopyBoard(boards[0], menuBoard);
15519                 } else
15520                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15521                     CopyBoard(boards[0], initialPosition);
15522                 } else
15523                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15524                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15525                     CopyBoard(boards[0], erasedBoard);
15526                 } else
15527                     CopyBoard(erasedBoard, currentBoard);
15528
15529                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15530                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15531             }
15532         }
15533         if (gameMode == EditPosition) {
15534             DrawPosition(FALSE, boards[0]);
15535         }
15536         break;
15537
15538       case WhitePlay:
15539         SetWhiteToPlayEvent();
15540         break;
15541
15542       case BlackPlay:
15543         SetBlackToPlayEvent();
15544         break;
15545
15546       case EmptySquare:
15547         if (gameMode == IcsExamining) {
15548             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15549             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15550             SendToICS(buf);
15551         } else {
15552             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15553                 if(x == BOARD_LEFT-2) {
15554                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15555                     boards[0][y][1] = 0;
15556                 } else
15557                 if(x == BOARD_RGHT+1) {
15558                     if(y >= gameInfo.holdingsSize) break;
15559                     boards[0][y][BOARD_WIDTH-2] = 0;
15560                 } else break;
15561             }
15562             boards[0][y][x] = EmptySquare;
15563             DrawPosition(FALSE, boards[0]);
15564         }
15565         break;
15566
15567       case PromotePiece:
15568         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15569            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15570             selection = (ChessSquare) (PROMOTED(piece));
15571         } else if(piece == EmptySquare) selection = WhiteSilver;
15572         else selection = (ChessSquare)((int)piece - 1);
15573         goto defaultlabel;
15574
15575       case DemotePiece:
15576         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15577            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15578             selection = (ChessSquare) (DEMOTED(piece));
15579         } else if(piece == EmptySquare) selection = BlackSilver;
15580         else selection = (ChessSquare)((int)piece + 1);
15581         goto defaultlabel;
15582
15583       case WhiteQueen:
15584       case BlackQueen:
15585         if(gameInfo.variant == VariantShatranj ||
15586            gameInfo.variant == VariantXiangqi  ||
15587            gameInfo.variant == VariantCourier  ||
15588            gameInfo.variant == VariantASEAN    ||
15589            gameInfo.variant == VariantMakruk     )
15590             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15591         goto defaultlabel;
15592
15593       case WhiteRook:
15594         baseRank = 0;
15595       case BlackRook:
15596         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15597         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15598         goto defaultlabel;
15599
15600       case WhiteKing:
15601         baseRank = 0;
15602       case BlackKing:
15603         if(gameInfo.variant == VariantXiangqi)
15604             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15605         if(gameInfo.variant == VariantKnightmate)
15606             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15607         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15608       default:
15609         defaultlabel:
15610         if (gameMode == IcsExamining) {
15611             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15612             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15613                      PieceToChar(selection), AAA + x, ONE + y);
15614             SendToICS(buf);
15615         } else {
15616             rightsBoard[y][x] = hasRights;
15617             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15618                 int n;
15619                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15620                     n = PieceToNumber(selection - BlackPawn);
15621                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15622                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15623                     boards[0][BOARD_HEIGHT-1-n][1]++;
15624                 } else
15625                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15626                     n = PieceToNumber(selection);
15627                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15628                     boards[0][n][BOARD_WIDTH-1] = selection;
15629                     boards[0][n][BOARD_WIDTH-2]++;
15630                 }
15631             } else
15632             boards[0][y][x] = selection;
15633             DrawPosition(TRUE, boards[0]);
15634             ClearHighlights();
15635             fromX = fromY = -1;
15636         }
15637         break;
15638     }
15639 }
15640
15641
15642 void
15643 DropMenuEvent (ChessSquare selection, int x, int y)
15644 {
15645     ChessMove moveType;
15646
15647     switch (gameMode) {
15648       case IcsPlayingWhite:
15649       case MachinePlaysBlack:
15650         if (!WhiteOnMove(currentMove)) {
15651             DisplayMoveError(_("It is Black's turn"));
15652             return;
15653         }
15654         moveType = WhiteDrop;
15655         break;
15656       case IcsPlayingBlack:
15657       case MachinePlaysWhite:
15658         if (WhiteOnMove(currentMove)) {
15659             DisplayMoveError(_("It is White's turn"));
15660             return;
15661         }
15662         moveType = BlackDrop;
15663         break;
15664       case EditGame:
15665         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15666         break;
15667       default:
15668         return;
15669     }
15670
15671     if (moveType == BlackDrop && selection < BlackPawn) {
15672       selection = (ChessSquare) ((int) selection
15673                                  + (int) BlackPawn - (int) WhitePawn);
15674     }
15675     if (boards[currentMove][y][x] != EmptySquare) {
15676         DisplayMoveError(_("That square is occupied"));
15677         return;
15678     }
15679
15680     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15681 }
15682
15683 void
15684 AcceptEvent ()
15685 {
15686     /* Accept a pending offer of any kind from opponent */
15687
15688     if (appData.icsActive) {
15689         SendToICS(ics_prefix);
15690         SendToICS("accept\n");
15691     } else if (cmailMsgLoaded) {
15692         if (currentMove == cmailOldMove &&
15693             commentList[cmailOldMove] != NULL &&
15694             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15695                    "Black offers a draw" : "White offers a draw")) {
15696             TruncateGame();
15697             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15698             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15699         } else {
15700             DisplayError(_("There is no pending offer on this move"), 0);
15701             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15702         }
15703     } else {
15704         /* Not used for offers from chess program */
15705     }
15706 }
15707
15708 void
15709 DeclineEvent ()
15710 {
15711     /* Decline a pending offer of any kind from opponent */
15712
15713     if (appData.icsActive) {
15714         SendToICS(ics_prefix);
15715         SendToICS("decline\n");
15716     } else if (cmailMsgLoaded) {
15717         if (currentMove == cmailOldMove &&
15718             commentList[cmailOldMove] != NULL &&
15719             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15720                    "Black offers a draw" : "White offers a draw")) {
15721 #ifdef NOTDEF
15722             AppendComment(cmailOldMove, "Draw declined", TRUE);
15723             DisplayComment(cmailOldMove - 1, "Draw declined");
15724 #endif /*NOTDEF*/
15725         } else {
15726             DisplayError(_("There is no pending offer on this move"), 0);
15727         }
15728     } else {
15729         /* Not used for offers from chess program */
15730     }
15731 }
15732
15733 void
15734 RematchEvent ()
15735 {
15736     /* Issue ICS rematch command */
15737     if (appData.icsActive) {
15738         SendToICS(ics_prefix);
15739         SendToICS("rematch\n");
15740     }
15741 }
15742
15743 void
15744 CallFlagEvent ()
15745 {
15746     /* Call your opponent's flag (claim a win on time) */
15747     if (appData.icsActive) {
15748         SendToICS(ics_prefix);
15749         SendToICS("flag\n");
15750     } else {
15751         switch (gameMode) {
15752           default:
15753             return;
15754           case MachinePlaysWhite:
15755             if (whiteFlag) {
15756                 if (blackFlag)
15757                   GameEnds(GameIsDrawn, "Both players ran out of time",
15758                            GE_PLAYER);
15759                 else
15760                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15761             } else {
15762                 DisplayError(_("Your opponent is not out of time"), 0);
15763             }
15764             break;
15765           case MachinePlaysBlack:
15766             if (blackFlag) {
15767                 if (whiteFlag)
15768                   GameEnds(GameIsDrawn, "Both players ran out of time",
15769                            GE_PLAYER);
15770                 else
15771                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15772             } else {
15773                 DisplayError(_("Your opponent is not out of time"), 0);
15774             }
15775             break;
15776         }
15777     }
15778 }
15779
15780 void
15781 ClockClick (int which)
15782 {       // [HGM] code moved to back-end from winboard.c
15783         if(which) { // black clock
15784           if (gameMode == EditPosition || gameMode == IcsExamining) {
15785             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15786             SetBlackToPlayEvent();
15787           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15788                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15789           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15790           } else if (shiftKey) {
15791             AdjustClock(which, -1);
15792           } else if (gameMode == IcsPlayingWhite ||
15793                      gameMode == MachinePlaysBlack) {
15794             CallFlagEvent();
15795           }
15796         } else { // white clock
15797           if (gameMode == EditPosition || gameMode == IcsExamining) {
15798             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15799             SetWhiteToPlayEvent();
15800           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15801                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15802           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15803           } else if (shiftKey) {
15804             AdjustClock(which, -1);
15805           } else if (gameMode == IcsPlayingBlack ||
15806                    gameMode == MachinePlaysWhite) {
15807             CallFlagEvent();
15808           }
15809         }
15810 }
15811
15812 void
15813 DrawEvent ()
15814 {
15815     /* Offer draw or accept pending draw offer from opponent */
15816
15817     if (appData.icsActive) {
15818         /* Note: tournament rules require draw offers to be
15819            made after you make your move but before you punch
15820            your clock.  Currently ICS doesn't let you do that;
15821            instead, you immediately punch your clock after making
15822            a move, but you can offer a draw at any time. */
15823
15824         SendToICS(ics_prefix);
15825         SendToICS("draw\n");
15826         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15827     } else if (cmailMsgLoaded) {
15828         if (currentMove == cmailOldMove &&
15829             commentList[cmailOldMove] != NULL &&
15830             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15831                    "Black offers a draw" : "White offers a draw")) {
15832             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15833             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15834         } else if (currentMove == cmailOldMove + 1) {
15835             char *offer = WhiteOnMove(cmailOldMove) ?
15836               "White offers a draw" : "Black offers a draw";
15837             AppendComment(currentMove, offer, TRUE);
15838             DisplayComment(currentMove - 1, offer);
15839             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15840         } else {
15841             DisplayError(_("You must make your move before offering a draw"), 0);
15842             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15843         }
15844     } else if (first.offeredDraw) {
15845         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15846     } else {
15847         if (first.sendDrawOffers) {
15848             SendToProgram("draw\n", &first);
15849             userOfferedDraw = TRUE;
15850         }
15851     }
15852 }
15853
15854 void
15855 AdjournEvent ()
15856 {
15857     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15858
15859     if (appData.icsActive) {
15860         SendToICS(ics_prefix);
15861         SendToICS("adjourn\n");
15862     } else {
15863         /* Currently GNU Chess doesn't offer or accept Adjourns */
15864     }
15865 }
15866
15867
15868 void
15869 AbortEvent ()
15870 {
15871     /* Offer Abort or accept pending Abort offer from opponent */
15872
15873     if (appData.icsActive) {
15874         SendToICS(ics_prefix);
15875         SendToICS("abort\n");
15876     } else {
15877         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15878     }
15879 }
15880
15881 void
15882 ResignEvent ()
15883 {
15884     /* Resign.  You can do this even if it's not your turn. */
15885
15886     if (appData.icsActive) {
15887         SendToICS(ics_prefix);
15888         SendToICS("resign\n");
15889     } else {
15890         switch (gameMode) {
15891           case MachinePlaysWhite:
15892             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15893             break;
15894           case MachinePlaysBlack:
15895             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15896             break;
15897           case EditGame:
15898             if (cmailMsgLoaded) {
15899                 TruncateGame();
15900                 if (WhiteOnMove(cmailOldMove)) {
15901                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15902                 } else {
15903                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15904                 }
15905                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15906             }
15907             break;
15908           default:
15909             break;
15910         }
15911     }
15912 }
15913
15914
15915 void
15916 StopObservingEvent ()
15917 {
15918     /* Stop observing current games */
15919     SendToICS(ics_prefix);
15920     SendToICS("unobserve\n");
15921 }
15922
15923 void
15924 StopExaminingEvent ()
15925 {
15926     /* Stop observing current game */
15927     SendToICS(ics_prefix);
15928     SendToICS("unexamine\n");
15929 }
15930
15931 void
15932 ForwardInner (int target)
15933 {
15934     int limit; int oldSeekGraphUp = seekGraphUp;
15935
15936     if (appData.debugMode)
15937         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15938                 target, currentMove, forwardMostMove);
15939
15940     if (gameMode == EditPosition)
15941       return;
15942
15943     seekGraphUp = FALSE;
15944     MarkTargetSquares(1);
15945     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15946
15947     if (gameMode == PlayFromGameFile && !pausing)
15948       PauseEvent();
15949
15950     if (gameMode == IcsExamining && pausing)
15951       limit = pauseExamForwardMostMove;
15952     else
15953       limit = forwardMostMove;
15954
15955     if (target > limit) target = limit;
15956
15957     if (target > 0 && moveList[target - 1][0]) {
15958         int fromX, fromY, toX, toY;
15959         toX = moveList[target - 1][2] - AAA;
15960         toY = moveList[target - 1][3] - ONE;
15961         if (moveList[target - 1][1] == '@') {
15962             if (appData.highlightLastMove) {
15963                 SetHighlights(-1, -1, toX, toY);
15964             }
15965         } else {
15966             fromX = moveList[target - 1][0] - AAA;
15967             fromY = moveList[target - 1][1] - ONE;
15968             if (target == currentMove + 1) {
15969                 if(moveList[target - 1][4] == ';') { // multi-leg
15970                     killX = moveList[target - 1][5] - AAA;
15971                     killY = moveList[target - 1][6] - ONE;
15972                 }
15973                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15974                 killX = killY = -1;
15975             }
15976             if (appData.highlightLastMove) {
15977                 SetHighlights(fromX, fromY, toX, toY);
15978             }
15979         }
15980     }
15981     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15982         gameMode == Training || gameMode == PlayFromGameFile ||
15983         gameMode == AnalyzeFile) {
15984         while (currentMove < target) {
15985             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15986             SendMoveToProgram(currentMove++, &first);
15987         }
15988     } else {
15989         currentMove = target;
15990     }
15991
15992     if (gameMode == EditGame || gameMode == EndOfGame) {
15993         whiteTimeRemaining = timeRemaining[0][currentMove];
15994         blackTimeRemaining = timeRemaining[1][currentMove];
15995     }
15996     DisplayBothClocks();
15997     DisplayMove(currentMove - 1);
15998     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15999     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16000     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16001         DisplayComment(currentMove - 1, commentList[currentMove]);
16002     }
16003     ClearMap(); // [HGM] exclude: invalidate map
16004 }
16005
16006
16007 void
16008 ForwardEvent ()
16009 {
16010     if (gameMode == IcsExamining && !pausing) {
16011         SendToICS(ics_prefix);
16012         SendToICS("forward\n");
16013     } else {
16014         ForwardInner(currentMove + 1);
16015     }
16016 }
16017
16018 void
16019 ToEndEvent ()
16020 {
16021     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16022         /* to optimze, we temporarily turn off analysis mode while we feed
16023          * the remaining moves to the engine. Otherwise we get analysis output
16024          * after each move.
16025          */
16026         if (first.analysisSupport) {
16027           SendToProgram("exit\nforce\n", &first);
16028           first.analyzing = FALSE;
16029         }
16030     }
16031
16032     if (gameMode == IcsExamining && !pausing) {
16033         SendToICS(ics_prefix);
16034         SendToICS("forward 999999\n");
16035     } else {
16036         ForwardInner(forwardMostMove);
16037     }
16038
16039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16040         /* we have fed all the moves, so reactivate analysis mode */
16041         SendToProgram("analyze\n", &first);
16042         first.analyzing = TRUE;
16043         /*first.maybeThinking = TRUE;*/
16044         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16045     }
16046 }
16047
16048 void
16049 BackwardInner (int target)
16050 {
16051     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16052
16053     if (appData.debugMode)
16054         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16055                 target, currentMove, forwardMostMove);
16056
16057     if (gameMode == EditPosition) return;
16058     seekGraphUp = FALSE;
16059     MarkTargetSquares(1);
16060     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16061     if (currentMove <= backwardMostMove) {
16062         ClearHighlights();
16063         DrawPosition(full_redraw, boards[currentMove]);
16064         return;
16065     }
16066     if (gameMode == PlayFromGameFile && !pausing)
16067       PauseEvent();
16068
16069     if (moveList[target][0]) {
16070         int fromX, fromY, toX, toY;
16071         toX = moveList[target][2] - AAA;
16072         toY = moveList[target][3] - ONE;
16073         if (moveList[target][1] == '@') {
16074             if (appData.highlightLastMove) {
16075                 SetHighlights(-1, -1, toX, toY);
16076             }
16077         } else {
16078             fromX = moveList[target][0] - AAA;
16079             fromY = moveList[target][1] - ONE;
16080             if (target == currentMove - 1) {
16081                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16082             }
16083             if (appData.highlightLastMove) {
16084                 SetHighlights(fromX, fromY, toX, toY);
16085             }
16086         }
16087     }
16088     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16089         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16090         while (currentMove > target) {
16091             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16092                 // null move cannot be undone. Reload program with move history before it.
16093                 int i;
16094                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16095                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16096                 }
16097                 SendBoard(&first, i);
16098               if(second.analyzing) SendBoard(&second, i);
16099                 for(currentMove=i; currentMove<target; currentMove++) {
16100                     SendMoveToProgram(currentMove, &first);
16101                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16102                 }
16103                 break;
16104             }
16105             SendToBoth("undo\n");
16106             currentMove--;
16107         }
16108     } else {
16109         currentMove = target;
16110     }
16111
16112     if (gameMode == EditGame || gameMode == EndOfGame) {
16113         whiteTimeRemaining = timeRemaining[0][currentMove];
16114         blackTimeRemaining = timeRemaining[1][currentMove];
16115     }
16116     DisplayBothClocks();
16117     DisplayMove(currentMove - 1);
16118     DrawPosition(full_redraw, boards[currentMove]);
16119     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16120     // [HGM] PV info: routine tests if comment empty
16121     DisplayComment(currentMove - 1, commentList[currentMove]);
16122     ClearMap(); // [HGM] exclude: invalidate map
16123 }
16124
16125 void
16126 BackwardEvent ()
16127 {
16128     if (gameMode == IcsExamining && !pausing) {
16129         SendToICS(ics_prefix);
16130         SendToICS("backward\n");
16131     } else {
16132         BackwardInner(currentMove - 1);
16133     }
16134 }
16135
16136 void
16137 ToStartEvent ()
16138 {
16139     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16140         /* to optimize, we temporarily turn off analysis mode while we undo
16141          * all the moves. Otherwise we get analysis output after each undo.
16142          */
16143         if (first.analysisSupport) {
16144           SendToProgram("exit\nforce\n", &first);
16145           first.analyzing = FALSE;
16146         }
16147     }
16148
16149     if (gameMode == IcsExamining && !pausing) {
16150         SendToICS(ics_prefix);
16151         SendToICS("backward 999999\n");
16152     } else {
16153         BackwardInner(backwardMostMove);
16154     }
16155
16156     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16157         /* we have fed all the moves, so reactivate analysis mode */
16158         SendToProgram("analyze\n", &first);
16159         first.analyzing = TRUE;
16160         /*first.maybeThinking = TRUE;*/
16161         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16162     }
16163 }
16164
16165 void
16166 ToNrEvent (int to)
16167 {
16168   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16169   if (to >= forwardMostMove) to = forwardMostMove;
16170   if (to <= backwardMostMove) to = backwardMostMove;
16171   if (to < currentMove) {
16172     BackwardInner(to);
16173   } else {
16174     ForwardInner(to);
16175   }
16176 }
16177
16178 void
16179 RevertEvent (Boolean annotate)
16180 {
16181     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16182         return;
16183     }
16184     if (gameMode != IcsExamining) {
16185         DisplayError(_("You are not examining a game"), 0);
16186         return;
16187     }
16188     if (pausing) {
16189         DisplayError(_("You can't revert while pausing"), 0);
16190         return;
16191     }
16192     SendToICS(ics_prefix);
16193     SendToICS("revert\n");
16194 }
16195
16196 void
16197 RetractMoveEvent ()
16198 {
16199     switch (gameMode) {
16200       case MachinePlaysWhite:
16201       case MachinePlaysBlack:
16202         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16203             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16204             return;
16205         }
16206         if (forwardMostMove < 2) return;
16207         currentMove = forwardMostMove = forwardMostMove - 2;
16208         whiteTimeRemaining = timeRemaining[0][currentMove];
16209         blackTimeRemaining = timeRemaining[1][currentMove];
16210         DisplayBothClocks();
16211         DisplayMove(currentMove - 1);
16212         ClearHighlights();/*!! could figure this out*/
16213         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16214         SendToProgram("remove\n", &first);
16215         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16216         break;
16217
16218       case BeginningOfGame:
16219       default:
16220         break;
16221
16222       case IcsPlayingWhite:
16223       case IcsPlayingBlack:
16224         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16225             SendToICS(ics_prefix);
16226             SendToICS("takeback 2\n");
16227         } else {
16228             SendToICS(ics_prefix);
16229             SendToICS("takeback 1\n");
16230         }
16231         break;
16232     }
16233 }
16234
16235 void
16236 MoveNowEvent ()
16237 {
16238     ChessProgramState *cps;
16239
16240     switch (gameMode) {
16241       case MachinePlaysWhite:
16242         if (!WhiteOnMove(forwardMostMove)) {
16243             DisplayError(_("It is your turn"), 0);
16244             return;
16245         }
16246         cps = &first;
16247         break;
16248       case MachinePlaysBlack:
16249         if (WhiteOnMove(forwardMostMove)) {
16250             DisplayError(_("It is your turn"), 0);
16251             return;
16252         }
16253         cps = &first;
16254         break;
16255       case TwoMachinesPlay:
16256         if (WhiteOnMove(forwardMostMove) ==
16257             (first.twoMachinesColor[0] == 'w')) {
16258             cps = &first;
16259         } else {
16260             cps = &second;
16261         }
16262         break;
16263       case BeginningOfGame:
16264       default:
16265         return;
16266     }
16267     SendToProgram("?\n", cps);
16268 }
16269
16270 void
16271 TruncateGameEvent ()
16272 {
16273     EditGameEvent();
16274     if (gameMode != EditGame) return;
16275     TruncateGame();
16276 }
16277
16278 void
16279 TruncateGame ()
16280 {
16281     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16282     if (forwardMostMove > currentMove) {
16283         if (gameInfo.resultDetails != NULL) {
16284             free(gameInfo.resultDetails);
16285             gameInfo.resultDetails = NULL;
16286             gameInfo.result = GameUnfinished;
16287         }
16288         forwardMostMove = currentMove;
16289         HistorySet(parseList, backwardMostMove, forwardMostMove,
16290                    currentMove-1);
16291     }
16292 }
16293
16294 void
16295 HintEvent ()
16296 {
16297     if (appData.noChessProgram) return;
16298     switch (gameMode) {
16299       case MachinePlaysWhite:
16300         if (WhiteOnMove(forwardMostMove)) {
16301             DisplayError(_("Wait until your turn."), 0);
16302             return;
16303         }
16304         break;
16305       case BeginningOfGame:
16306       case MachinePlaysBlack:
16307         if (!WhiteOnMove(forwardMostMove)) {
16308             DisplayError(_("Wait until your turn."), 0);
16309             return;
16310         }
16311         break;
16312       default:
16313         DisplayError(_("No hint available"), 0);
16314         return;
16315     }
16316     SendToProgram("hint\n", &first);
16317     hintRequested = TRUE;
16318 }
16319
16320 int
16321 SaveSelected (FILE *g, int dummy, char *dummy2)
16322 {
16323     ListGame * lg = (ListGame *) gameList.head;
16324     int nItem, cnt=0;
16325     FILE *f;
16326
16327     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16328         DisplayError(_("Game list not loaded or empty"), 0);
16329         return 0;
16330     }
16331
16332     creatingBook = TRUE; // suppresses stuff during load game
16333
16334     /* Get list size */
16335     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16336         if(lg->position >= 0) { // selected?
16337             LoadGame(f, nItem, "", TRUE);
16338             SaveGamePGN2(g); // leaves g open
16339             cnt++; DoEvents();
16340         }
16341         lg = (ListGame *) lg->node.succ;
16342     }
16343
16344     fclose(g);
16345     creatingBook = FALSE;
16346
16347     return cnt;
16348 }
16349
16350 void
16351 CreateBookEvent ()
16352 {
16353     ListGame * lg = (ListGame *) gameList.head;
16354     FILE *f, *g;
16355     int nItem;
16356     static int secondTime = FALSE;
16357
16358     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16359         DisplayError(_("Game list not loaded or empty"), 0);
16360         return;
16361     }
16362
16363     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16364         fclose(g);
16365         secondTime++;
16366         DisplayNote(_("Book file exists! Try again for overwrite."));
16367         return;
16368     }
16369
16370     creatingBook = TRUE;
16371     secondTime = FALSE;
16372
16373     /* Get list size */
16374     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16375         if(lg->position >= 0) {
16376             LoadGame(f, nItem, "", TRUE);
16377             AddGameToBook(TRUE);
16378             DoEvents();
16379         }
16380         lg = (ListGame *) lg->node.succ;
16381     }
16382
16383     creatingBook = FALSE;
16384     FlushBook();
16385 }
16386
16387 void
16388 BookEvent ()
16389 {
16390     if (appData.noChessProgram) return;
16391     switch (gameMode) {
16392       case MachinePlaysWhite:
16393         if (WhiteOnMove(forwardMostMove)) {
16394             DisplayError(_("Wait until your turn."), 0);
16395             return;
16396         }
16397         break;
16398       case BeginningOfGame:
16399       case MachinePlaysBlack:
16400         if (!WhiteOnMove(forwardMostMove)) {
16401             DisplayError(_("Wait until your turn."), 0);
16402             return;
16403         }
16404         break;
16405       case EditPosition:
16406         EditPositionDone(TRUE);
16407         break;
16408       case TwoMachinesPlay:
16409         return;
16410       default:
16411         break;
16412     }
16413     SendToProgram("bk\n", &first);
16414     bookOutput[0] = NULLCHAR;
16415     bookRequested = TRUE;
16416 }
16417
16418 void
16419 AboutGameEvent ()
16420 {
16421     char *tags = PGNTags(&gameInfo);
16422     TagsPopUp(tags, CmailMsg());
16423     free(tags);
16424 }
16425
16426 /* end button procedures */
16427
16428 void
16429 PrintPosition (FILE *fp, int move)
16430 {
16431     int i, j;
16432
16433     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16434         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16435             char c = PieceToChar(boards[move][i][j]);
16436             fputc(c == '?' ? '.' : c, fp);
16437             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16438         }
16439     }
16440     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16441       fprintf(fp, "white to play\n");
16442     else
16443       fprintf(fp, "black to play\n");
16444 }
16445
16446 void
16447 PrintOpponents (FILE *fp)
16448 {
16449     if (gameInfo.white != NULL) {
16450         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16451     } else {
16452         fprintf(fp, "\n");
16453     }
16454 }
16455
16456 /* Find last component of program's own name, using some heuristics */
16457 void
16458 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16459 {
16460     char *p, *q, c;
16461     int local = (strcmp(host, "localhost") == 0);
16462     while (!local && (p = strchr(prog, ';')) != NULL) {
16463         p++;
16464         while (*p == ' ') p++;
16465         prog = p;
16466     }
16467     if (*prog == '"' || *prog == '\'') {
16468         q = strchr(prog + 1, *prog);
16469     } else {
16470         q = strchr(prog, ' ');
16471     }
16472     if (q == NULL) q = prog + strlen(prog);
16473     p = q;
16474     while (p >= prog && *p != '/' && *p != '\\') p--;
16475     p++;
16476     if(p == prog && *p == '"') p++;
16477     c = *q; *q = 0;
16478     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16479     memcpy(buf, p, q - p);
16480     buf[q - p] = NULLCHAR;
16481     if (!local) {
16482         strcat(buf, "@");
16483         strcat(buf, host);
16484     }
16485 }
16486
16487 char *
16488 TimeControlTagValue ()
16489 {
16490     char buf[MSG_SIZ];
16491     if (!appData.clockMode) {
16492       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16493     } else if (movesPerSession > 0) {
16494       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16495     } else if (timeIncrement == 0) {
16496       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16497     } else {
16498       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16499     }
16500     return StrSave(buf);
16501 }
16502
16503 void
16504 SetGameInfo ()
16505 {
16506     /* This routine is used only for certain modes */
16507     VariantClass v = gameInfo.variant;
16508     ChessMove r = GameUnfinished;
16509     char *p = NULL;
16510
16511     if(keepInfo) return;
16512
16513     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16514         r = gameInfo.result;
16515         p = gameInfo.resultDetails;
16516         gameInfo.resultDetails = NULL;
16517     }
16518     ClearGameInfo(&gameInfo);
16519     gameInfo.variant = v;
16520
16521     switch (gameMode) {
16522       case MachinePlaysWhite:
16523         gameInfo.event = StrSave( appData.pgnEventHeader );
16524         gameInfo.site = StrSave(HostName());
16525         gameInfo.date = PGNDate();
16526         gameInfo.round = StrSave("-");
16527         gameInfo.white = StrSave(first.tidy);
16528         gameInfo.black = StrSave(UserName());
16529         gameInfo.timeControl = TimeControlTagValue();
16530         break;
16531
16532       case MachinePlaysBlack:
16533         gameInfo.event = StrSave( appData.pgnEventHeader );
16534         gameInfo.site = StrSave(HostName());
16535         gameInfo.date = PGNDate();
16536         gameInfo.round = StrSave("-");
16537         gameInfo.white = StrSave(UserName());
16538         gameInfo.black = StrSave(first.tidy);
16539         gameInfo.timeControl = TimeControlTagValue();
16540         break;
16541
16542       case TwoMachinesPlay:
16543         gameInfo.event = StrSave( appData.pgnEventHeader );
16544         gameInfo.site = StrSave(HostName());
16545         gameInfo.date = PGNDate();
16546         if (roundNr > 0) {
16547             char buf[MSG_SIZ];
16548             snprintf(buf, MSG_SIZ, "%d", roundNr);
16549             gameInfo.round = StrSave(buf);
16550         } else {
16551             gameInfo.round = StrSave("-");
16552         }
16553         if (first.twoMachinesColor[0] == 'w') {
16554             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16555             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16556         } else {
16557             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16558             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16559         }
16560         gameInfo.timeControl = TimeControlTagValue();
16561         break;
16562
16563       case EditGame:
16564         gameInfo.event = StrSave("Edited game");
16565         gameInfo.site = StrSave(HostName());
16566         gameInfo.date = PGNDate();
16567         gameInfo.round = StrSave("-");
16568         gameInfo.white = StrSave("-");
16569         gameInfo.black = StrSave("-");
16570         gameInfo.result = r;
16571         gameInfo.resultDetails = p;
16572         break;
16573
16574       case EditPosition:
16575         gameInfo.event = StrSave("Edited position");
16576         gameInfo.site = StrSave(HostName());
16577         gameInfo.date = PGNDate();
16578         gameInfo.round = StrSave("-");
16579         gameInfo.white = StrSave("-");
16580         gameInfo.black = StrSave("-");
16581         break;
16582
16583       case IcsPlayingWhite:
16584       case IcsPlayingBlack:
16585       case IcsObserving:
16586       case IcsExamining:
16587         break;
16588
16589       case PlayFromGameFile:
16590         gameInfo.event = StrSave("Game from non-PGN file");
16591         gameInfo.site = StrSave(HostName());
16592         gameInfo.date = PGNDate();
16593         gameInfo.round = StrSave("-");
16594         gameInfo.white = StrSave("?");
16595         gameInfo.black = StrSave("?");
16596         break;
16597
16598       default:
16599         break;
16600     }
16601 }
16602
16603 void
16604 ReplaceComment (int index, char *text)
16605 {
16606     int len;
16607     char *p;
16608     float score;
16609
16610     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16611        pvInfoList[index-1].depth == len &&
16612        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16613        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16614     while (*text == '\n') text++;
16615     len = strlen(text);
16616     while (len > 0 && text[len - 1] == '\n') len--;
16617
16618     if (commentList[index] != NULL)
16619       free(commentList[index]);
16620
16621     if (len == 0) {
16622         commentList[index] = NULL;
16623         return;
16624     }
16625   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16626       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16627       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16628     commentList[index] = (char *) malloc(len + 2);
16629     strncpy(commentList[index], text, len);
16630     commentList[index][len] = '\n';
16631     commentList[index][len + 1] = NULLCHAR;
16632   } else {
16633     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16634     char *p;
16635     commentList[index] = (char *) malloc(len + 7);
16636     safeStrCpy(commentList[index], "{\n", 3);
16637     safeStrCpy(commentList[index]+2, text, len+1);
16638     commentList[index][len+2] = NULLCHAR;
16639     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16640     strcat(commentList[index], "\n}\n");
16641   }
16642 }
16643
16644 void
16645 CrushCRs (char *text)
16646 {
16647   char *p = text;
16648   char *q = text;
16649   char ch;
16650
16651   do {
16652     ch = *p++;
16653     if (ch == '\r') continue;
16654     *q++ = ch;
16655   } while (ch != '\0');
16656 }
16657
16658 void
16659 AppendComment (int index, char *text, Boolean addBraces)
16660 /* addBraces  tells if we should add {} */
16661 {
16662     int oldlen, len;
16663     char *old;
16664
16665 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16666     if(addBraces == 3) addBraces = 0; else // force appending literally
16667     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16668
16669     CrushCRs(text);
16670     while (*text == '\n') text++;
16671     len = strlen(text);
16672     while (len > 0 && text[len - 1] == '\n') len--;
16673     text[len] = NULLCHAR;
16674
16675     if (len == 0) return;
16676
16677     if (commentList[index] != NULL) {
16678       Boolean addClosingBrace = addBraces;
16679         old = commentList[index];
16680         oldlen = strlen(old);
16681         while(commentList[index][oldlen-1] ==  '\n')
16682           commentList[index][--oldlen] = NULLCHAR;
16683         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16684         safeStrCpy(commentList[index], old, oldlen + len + 6);
16685         free(old);
16686         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16687         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16688           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16689           while (*text == '\n') { text++; len--; }
16690           commentList[index][--oldlen] = NULLCHAR;
16691       }
16692         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16693         else          strcat(commentList[index], "\n");
16694         strcat(commentList[index], text);
16695         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16696         else          strcat(commentList[index], "\n");
16697     } else {
16698         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16699         if(addBraces)
16700           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16701         else commentList[index][0] = NULLCHAR;
16702         strcat(commentList[index], text);
16703         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16704         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16705     }
16706 }
16707
16708 static char *
16709 FindStr (char * text, char * sub_text)
16710 {
16711     char * result = strstr( text, sub_text );
16712
16713     if( result != NULL ) {
16714         result += strlen( sub_text );
16715     }
16716
16717     return result;
16718 }
16719
16720 /* [AS] Try to extract PV info from PGN comment */
16721 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16722 char *
16723 GetInfoFromComment (int index, char * text)
16724 {
16725     char * sep = text, *p;
16726
16727     if( text != NULL && index > 0 ) {
16728         int score = 0;
16729         int depth = 0;
16730         int time = -1, sec = 0, deci;
16731         char * s_eval = FindStr( text, "[%eval " );
16732         char * s_emt = FindStr( text, "[%emt " );
16733 #if 0
16734         if( s_eval != NULL || s_emt != NULL ) {
16735 #else
16736         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16737 #endif
16738             /* New style */
16739             char delim;
16740
16741             if( s_eval != NULL ) {
16742                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16743                     return text;
16744                 }
16745
16746                 if( delim != ']' ) {
16747                     return text;
16748                 }
16749             }
16750
16751             if( s_emt != NULL ) {
16752             }
16753                 return text;
16754         }
16755         else {
16756             /* We expect something like: [+|-]nnn.nn/dd */
16757             int score_lo = 0;
16758
16759             if(*text != '{') return text; // [HGM] braces: must be normal comment
16760
16761             sep = strchr( text, '/' );
16762             if( sep == NULL || sep < (text+4) ) {
16763                 return text;
16764             }
16765
16766             p = text;
16767             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16768             if(p[1] == '(') { // comment starts with PV
16769                p = strchr(p, ')'); // locate end of PV
16770                if(p == NULL || sep < p+5) return text;
16771                // at this point we have something like "{(.*) +0.23/6 ..."
16772                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16773                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16774                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16775             }
16776             time = -1; sec = -1; deci = -1;
16777             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16778                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16779                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16780                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16781                 return text;
16782             }
16783
16784             if( score_lo < 0 || score_lo >= 100 ) {
16785                 return text;
16786             }
16787
16788             if(sec >= 0) time = 600*time + 10*sec; else
16789             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16790
16791             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16792
16793             /* [HGM] PV time: now locate end of PV info */
16794             while( *++sep >= '0' && *sep <= '9'); // strip depth
16795             if(time >= 0)
16796             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16797             if(sec >= 0)
16798             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16799             if(deci >= 0)
16800             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16801             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16802         }
16803
16804         if( depth <= 0 ) {
16805             return text;
16806         }
16807
16808         if( time < 0 ) {
16809             time = -1;
16810         }
16811
16812         pvInfoList[index-1].depth = depth;
16813         pvInfoList[index-1].score = score;
16814         pvInfoList[index-1].time  = 10*time; // centi-sec
16815         if(*sep == '}') *sep = 0; else *--sep = '{';
16816         if(p != text) {
16817             while(*p++ = *sep++)
16818                                 ;
16819             sep = text;
16820         } // squeeze out space between PV and comment, and return both
16821     }
16822     return sep;
16823 }
16824
16825 void
16826 SendToProgram (char *message, ChessProgramState *cps)
16827 {
16828     int count, outCount, error;
16829     char buf[MSG_SIZ];
16830
16831     if (cps->pr == NoProc) return;
16832     Attention(cps);
16833
16834     if (appData.debugMode) {
16835         TimeMark now;
16836         GetTimeMark(&now);
16837         fprintf(debugFP, "%ld >%-6s: %s",
16838                 SubtractTimeMarks(&now, &programStartTime),
16839                 cps->which, message);
16840         if(serverFP)
16841             fprintf(serverFP, "%ld >%-6s: %s",
16842                 SubtractTimeMarks(&now, &programStartTime),
16843                 cps->which, message), fflush(serverFP);
16844     }
16845
16846     count = strlen(message);
16847     outCount = OutputToProcess(cps->pr, message, count, &error);
16848     if (outCount < count && !exiting
16849                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16850       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16851       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16852         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16853             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16854                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16855                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16856                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16857             } else {
16858                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16859                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16860                 gameInfo.result = res;
16861             }
16862             gameInfo.resultDetails = StrSave(buf);
16863         }
16864         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16865         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16866     }
16867 }
16868
16869 void
16870 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16871 {
16872     char *end_str;
16873     char buf[MSG_SIZ];
16874     ChessProgramState *cps = (ChessProgramState *)closure;
16875
16876     if (isr != cps->isr) return; /* Killed intentionally */
16877     if (count <= 0) {
16878         if (count == 0) {
16879             RemoveInputSource(cps->isr);
16880             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16881                     _(cps->which), cps->program);
16882             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16883             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16884                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16885                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16886                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16887                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16888                 } else {
16889                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16890                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16891                     gameInfo.result = res;
16892                 }
16893                 gameInfo.resultDetails = StrSave(buf);
16894             }
16895             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16896             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16897         } else {
16898             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16899                     _(cps->which), cps->program);
16900             RemoveInputSource(cps->isr);
16901
16902             /* [AS] Program is misbehaving badly... kill it */
16903             if( count == -2 ) {
16904                 DestroyChildProcess( cps->pr, 9 );
16905                 cps->pr = NoProc;
16906             }
16907
16908             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16909         }
16910         return;
16911     }
16912
16913     if ((end_str = strchr(message, '\r')) != NULL)
16914       *end_str = NULLCHAR;
16915     if ((end_str = strchr(message, '\n')) != NULL)
16916       *end_str = NULLCHAR;
16917
16918     if (appData.debugMode) {
16919         TimeMark now; int print = 1;
16920         char *quote = ""; char c; int i;
16921
16922         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16923                 char start = message[0];
16924                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16925                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16926                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16927                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16928                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16929                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16930                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16931                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16932                    sscanf(message, "hint: %c", &c)!=1 &&
16933                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16934                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16935                     print = (appData.engineComments >= 2);
16936                 }
16937                 message[0] = start; // restore original message
16938         }
16939         if(print) {
16940                 GetTimeMark(&now);
16941                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16942                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16943                         quote,
16944                         message);
16945                 if(serverFP)
16946                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16947                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16948                         quote,
16949                         message), fflush(serverFP);
16950         }
16951     }
16952
16953     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16954     if (appData.icsEngineAnalyze) {
16955         if (strstr(message, "whisper") != NULL ||
16956              strstr(message, "kibitz") != NULL ||
16957             strstr(message, "tellics") != NULL) return;
16958     }
16959
16960     HandleMachineMove(message, cps);
16961 }
16962
16963
16964 void
16965 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16966 {
16967     char buf[MSG_SIZ];
16968     int seconds;
16969
16970     if( timeControl_2 > 0 ) {
16971         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16972             tc = timeControl_2;
16973         }
16974     }
16975     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16976     inc /= cps->timeOdds;
16977     st  /= cps->timeOdds;
16978
16979     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16980
16981     if (st > 0) {
16982       /* Set exact time per move, normally using st command */
16983       if (cps->stKludge) {
16984         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16985         seconds = st % 60;
16986         if (seconds == 0) {
16987           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16988         } else {
16989           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16990         }
16991       } else {
16992         snprintf(buf, MSG_SIZ, "st %d\n", st);
16993       }
16994     } else {
16995       /* Set conventional or incremental time control, using level command */
16996       if (seconds == 0) {
16997         /* Note old gnuchess bug -- minutes:seconds used to not work.
16998            Fixed in later versions, but still avoid :seconds
16999            when seconds is 0. */
17000         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17001       } else {
17002         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17003                  seconds, inc/1000.);
17004       }
17005     }
17006     SendToProgram(buf, cps);
17007
17008     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17009     /* Orthogonally, limit search to given depth */
17010     if (sd > 0) {
17011       if (cps->sdKludge) {
17012         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17013       } else {
17014         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17015       }
17016       SendToProgram(buf, cps);
17017     }
17018
17019     if(cps->nps >= 0) { /* [HGM] nps */
17020         if(cps->supportsNPS == FALSE)
17021           cps->nps = -1; // don't use if engine explicitly says not supported!
17022         else {
17023           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17024           SendToProgram(buf, cps);
17025         }
17026     }
17027 }
17028
17029 ChessProgramState *
17030 WhitePlayer ()
17031 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17032 {
17033     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17034        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17035         return &second;
17036     return &first;
17037 }
17038
17039 void
17040 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17041 {
17042     char message[MSG_SIZ];
17043     long time, otime;
17044
17045     /* Note: this routine must be called when the clocks are stopped
17046        or when they have *just* been set or switched; otherwise
17047        it will be off by the time since the current tick started.
17048     */
17049     if (machineWhite) {
17050         time = whiteTimeRemaining / 10;
17051         otime = blackTimeRemaining / 10;
17052     } else {
17053         time = blackTimeRemaining / 10;
17054         otime = whiteTimeRemaining / 10;
17055     }
17056     /* [HGM] translate opponent's time by time-odds factor */
17057     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17058
17059     if (time <= 0) time = 1;
17060     if (otime <= 0) otime = 1;
17061
17062     snprintf(message, MSG_SIZ, "time %ld\n", time);
17063     SendToProgram(message, cps);
17064
17065     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17066     SendToProgram(message, cps);
17067 }
17068
17069 char *
17070 EngineDefinedVariant (ChessProgramState *cps, int n)
17071 {   // return name of n-th unknown variant that engine supports
17072     static char buf[MSG_SIZ];
17073     char *p, *s = cps->variants;
17074     if(!s) return NULL;
17075     do { // parse string from variants feature
17076       VariantClass v;
17077         p = strchr(s, ',');
17078         if(p) *p = NULLCHAR;
17079       v = StringToVariant(s);
17080       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17081         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17082             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17083                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17084                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17085                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17086             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17087         }
17088         if(p) *p++ = ',';
17089         if(n < 0) return buf;
17090     } while(s = p);
17091     return NULL;
17092 }
17093
17094 int
17095 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17096 {
17097   char buf[MSG_SIZ];
17098   int len = strlen(name);
17099   int val;
17100
17101   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17102     (*p) += len + 1;
17103     sscanf(*p, "%d", &val);
17104     *loc = (val != 0);
17105     while (**p && **p != ' ')
17106       (*p)++;
17107     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17108     SendToProgram(buf, cps);
17109     return TRUE;
17110   }
17111   return FALSE;
17112 }
17113
17114 int
17115 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17116 {
17117   char buf[MSG_SIZ];
17118   int len = strlen(name);
17119   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17120     (*p) += len + 1;
17121     sscanf(*p, "%d", loc);
17122     while (**p && **p != ' ') (*p)++;
17123     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17124     SendToProgram(buf, cps);
17125     return TRUE;
17126   }
17127   return FALSE;
17128 }
17129
17130 int
17131 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17132 {
17133   char buf[MSG_SIZ];
17134   int len = strlen(name);
17135   if (strncmp((*p), name, len) == 0
17136       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17137     (*p) += len + 2;
17138     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17139     sscanf(*p, "%[^\"]", *loc);
17140     while (**p && **p != '\"') (*p)++;
17141     if (**p == '\"') (*p)++;
17142     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17143     SendToProgram(buf, cps);
17144     return TRUE;
17145   }
17146   return FALSE;
17147 }
17148
17149 int
17150 ParseOption (Option *opt, ChessProgramState *cps)
17151 // [HGM] options: process the string that defines an engine option, and determine
17152 // name, type, default value, and allowed value range
17153 {
17154         char *p, *q, buf[MSG_SIZ];
17155         int n, min = (-1)<<31, max = 1<<31, def;
17156
17157         opt->target = &opt->value;   // OK for spin/slider and checkbox
17158         if(p = strstr(opt->name, " -spin ")) {
17159             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17160             if(max < min) max = min; // enforce consistency
17161             if(def < min) def = min;
17162             if(def > max) def = max;
17163             opt->value = def;
17164             opt->min = min;
17165             opt->max = max;
17166             opt->type = Spin;
17167         } else if((p = strstr(opt->name, " -slider "))) {
17168             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17169             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17170             if(max < min) max = min; // enforce consistency
17171             if(def < min) def = min;
17172             if(def > max) def = max;
17173             opt->value = def;
17174             opt->min = min;
17175             opt->max = max;
17176             opt->type = Spin; // Slider;
17177         } else if((p = strstr(opt->name, " -string "))) {
17178             opt->textValue = p+9;
17179             opt->type = TextBox;
17180             opt->target = &opt->textValue;
17181         } else if((p = strstr(opt->name, " -file "))) {
17182             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17183             opt->target = opt->textValue = p+7;
17184             opt->type = FileName; // FileName;
17185             opt->target = &opt->textValue;
17186         } else if((p = strstr(opt->name, " -path "))) {
17187             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17188             opt->target = opt->textValue = p+7;
17189             opt->type = PathName; // PathName;
17190             opt->target = &opt->textValue;
17191         } else if(p = strstr(opt->name, " -check ")) {
17192             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17193             opt->value = (def != 0);
17194             opt->type = CheckBox;
17195         } else if(p = strstr(opt->name, " -combo ")) {
17196             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17197             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17198             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17199             opt->value = n = 0;
17200             while(q = StrStr(q, " /// ")) {
17201                 n++; *q = 0;    // count choices, and null-terminate each of them
17202                 q += 5;
17203                 if(*q == '*') { // remember default, which is marked with * prefix
17204                     q++;
17205                     opt->value = n;
17206                 }
17207                 cps->comboList[cps->comboCnt++] = q;
17208             }
17209             cps->comboList[cps->comboCnt++] = NULL;
17210             opt->max = n + 1;
17211             opt->type = ComboBox;
17212         } else if(p = strstr(opt->name, " -button")) {
17213             opt->type = Button;
17214         } else if(p = strstr(opt->name, " -save")) {
17215             opt->type = SaveButton;
17216         } else return FALSE;
17217         *p = 0; // terminate option name
17218         // now look if the command-line options define a setting for this engine option.
17219         if(cps->optionSettings && cps->optionSettings[0])
17220             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17221         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17222           snprintf(buf, MSG_SIZ, "option %s", p);
17223                 if(p = strstr(buf, ",")) *p = 0;
17224                 if(q = strchr(buf, '=')) switch(opt->type) {
17225                     case ComboBox:
17226                         for(n=0; n<opt->max; n++)
17227                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17228                         break;
17229                     case TextBox:
17230                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17231                         break;
17232                     case Spin:
17233                     case CheckBox:
17234                         opt->value = atoi(q+1);
17235                     default:
17236                         break;
17237                 }
17238                 strcat(buf, "\n");
17239                 SendToProgram(buf, cps);
17240         }
17241         return TRUE;
17242 }
17243
17244 void
17245 FeatureDone (ChessProgramState *cps, int val)
17246 {
17247   DelayedEventCallback cb = GetDelayedEvent();
17248   if ((cb == InitBackEnd3 && cps == &first) ||
17249       (cb == SettingsMenuIfReady && cps == &second) ||
17250       (cb == LoadEngine) ||
17251       (cb == TwoMachinesEventIfReady)) {
17252     CancelDelayedEvent();
17253     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17254   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17255   cps->initDone = val;
17256   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17257 }
17258
17259 /* Parse feature command from engine */
17260 void
17261 ParseFeatures (char *args, ChessProgramState *cps)
17262 {
17263   char *p = args;
17264   char *q = NULL;
17265   int val;
17266   char buf[MSG_SIZ];
17267
17268   for (;;) {
17269     while (*p == ' ') p++;
17270     if (*p == NULLCHAR) return;
17271
17272     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17273     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17274     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17275     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17276     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17277     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17278     if (BoolFeature(&p, "reuse", &val, cps)) {
17279       /* Engine can disable reuse, but can't enable it if user said no */
17280       if (!val) cps->reuse = FALSE;
17281       continue;
17282     }
17283     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17284     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17285       if (gameMode == TwoMachinesPlay) {
17286         DisplayTwoMachinesTitle();
17287       } else {
17288         DisplayTitle("");
17289       }
17290       continue;
17291     }
17292     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17293     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17294     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17295     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17296     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17297     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17298     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17299     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17300     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17301     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17302     if (IntFeature(&p, "done", &val, cps)) {
17303       FeatureDone(cps, val);
17304       continue;
17305     }
17306     /* Added by Tord: */
17307     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17308     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17309     /* End of additions by Tord */
17310
17311     /* [HGM] added features: */
17312     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17313     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17314     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17315     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17316     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17317     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17318     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17319     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17320         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17321         FREE(cps->option[cps->nrOptions].name);
17322         cps->option[cps->nrOptions].name = q; q = NULL;
17323         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17324           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17325             SendToProgram(buf, cps);
17326             continue;
17327         }
17328         if(cps->nrOptions >= MAX_OPTIONS) {
17329             cps->nrOptions--;
17330             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17331             DisplayError(buf, 0);
17332         }
17333         continue;
17334     }
17335     /* End of additions by HGM */
17336
17337     /* unknown feature: complain and skip */
17338     q = p;
17339     while (*q && *q != '=') q++;
17340     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17341     SendToProgram(buf, cps);
17342     p = q;
17343     if (*p == '=') {
17344       p++;
17345       if (*p == '\"') {
17346         p++;
17347         while (*p && *p != '\"') p++;
17348         if (*p == '\"') p++;
17349       } else {
17350         while (*p && *p != ' ') p++;
17351       }
17352     }
17353   }
17354
17355 }
17356
17357 void
17358 PeriodicUpdatesEvent (int newState)
17359 {
17360     if (newState == appData.periodicUpdates)
17361       return;
17362
17363     appData.periodicUpdates=newState;
17364
17365     /* Display type changes, so update it now */
17366 //    DisplayAnalysis();
17367
17368     /* Get the ball rolling again... */
17369     if (newState) {
17370         AnalysisPeriodicEvent(1);
17371         StartAnalysisClock();
17372     }
17373 }
17374
17375 void
17376 PonderNextMoveEvent (int newState)
17377 {
17378     if (newState == appData.ponderNextMove) return;
17379     if (gameMode == EditPosition) EditPositionDone(TRUE);
17380     if (newState) {
17381         SendToProgram("hard\n", &first);
17382         if (gameMode == TwoMachinesPlay) {
17383             SendToProgram("hard\n", &second);
17384         }
17385     } else {
17386         SendToProgram("easy\n", &first);
17387         thinkOutput[0] = NULLCHAR;
17388         if (gameMode == TwoMachinesPlay) {
17389             SendToProgram("easy\n", &second);
17390         }
17391     }
17392     appData.ponderNextMove = newState;
17393 }
17394
17395 void
17396 NewSettingEvent (int option, int *feature, char *command, int value)
17397 {
17398     char buf[MSG_SIZ];
17399
17400     if (gameMode == EditPosition) EditPositionDone(TRUE);
17401     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17402     if(feature == NULL || *feature) SendToProgram(buf, &first);
17403     if (gameMode == TwoMachinesPlay) {
17404         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17405     }
17406 }
17407
17408 void
17409 ShowThinkingEvent ()
17410 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17411 {
17412     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17413     int newState = appData.showThinking
17414         // [HGM] thinking: other features now need thinking output as well
17415         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17416
17417     if (oldState == newState) return;
17418     oldState = newState;
17419     if (gameMode == EditPosition) EditPositionDone(TRUE);
17420     if (oldState) {
17421         SendToProgram("post\n", &first);
17422         if (gameMode == TwoMachinesPlay) {
17423             SendToProgram("post\n", &second);
17424         }
17425     } else {
17426         SendToProgram("nopost\n", &first);
17427         thinkOutput[0] = NULLCHAR;
17428         if (gameMode == TwoMachinesPlay) {
17429             SendToProgram("nopost\n", &second);
17430         }
17431     }
17432 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17433 }
17434
17435 void
17436 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17437 {
17438   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17439   if (pr == NoProc) return;
17440   AskQuestion(title, question, replyPrefix, pr);
17441 }
17442
17443 void
17444 TypeInEvent (char firstChar)
17445 {
17446     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17447         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17448         gameMode == AnalyzeMode || gameMode == EditGame ||
17449         gameMode == EditPosition || gameMode == IcsExamining ||
17450         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17451         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17452                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17453                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17454         gameMode == Training) PopUpMoveDialog(firstChar);
17455 }
17456
17457 void
17458 TypeInDoneEvent (char *move)
17459 {
17460         Board board;
17461         int n, fromX, fromY, toX, toY;
17462         char promoChar;
17463         ChessMove moveType;
17464
17465         // [HGM] FENedit
17466         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17467                 EditPositionPasteFEN(move);
17468                 return;
17469         }
17470         // [HGM] movenum: allow move number to be typed in any mode
17471         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17472           ToNrEvent(2*n-1);
17473           return;
17474         }
17475         // undocumented kludge: allow command-line option to be typed in!
17476         // (potentially fatal, and does not implement the effect of the option.)
17477         // should only be used for options that are values on which future decisions will be made,
17478         // and definitely not on options that would be used during initialization.
17479         if(strstr(move, "!!! -") == move) {
17480             ParseArgsFromString(move+4);
17481             return;
17482         }
17483
17484       if (gameMode != EditGame && currentMove != forwardMostMove &&
17485         gameMode != Training) {
17486         DisplayMoveError(_("Displayed move is not current"));
17487       } else {
17488         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17489           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17490         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17491         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17492           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17493           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17494         } else {
17495           DisplayMoveError(_("Could not parse move"));
17496         }
17497       }
17498 }
17499
17500 void
17501 DisplayMove (int moveNumber)
17502 {
17503     char message[MSG_SIZ];
17504     char res[MSG_SIZ];
17505     char cpThinkOutput[MSG_SIZ];
17506
17507     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17508
17509     if (moveNumber == forwardMostMove - 1 ||
17510         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17511
17512         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17513
17514         if (strchr(cpThinkOutput, '\n')) {
17515             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17516         }
17517     } else {
17518         *cpThinkOutput = NULLCHAR;
17519     }
17520
17521     /* [AS] Hide thinking from human user */
17522     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17523         *cpThinkOutput = NULLCHAR;
17524         if( thinkOutput[0] != NULLCHAR ) {
17525             int i;
17526
17527             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17528                 cpThinkOutput[i] = '.';
17529             }
17530             cpThinkOutput[i] = NULLCHAR;
17531             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17532         }
17533     }
17534
17535     if (moveNumber == forwardMostMove - 1 &&
17536         gameInfo.resultDetails != NULL) {
17537         if (gameInfo.resultDetails[0] == NULLCHAR) {
17538           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17539         } else {
17540           snprintf(res, MSG_SIZ, " {%s} %s",
17541                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17542         }
17543     } else {
17544         res[0] = NULLCHAR;
17545     }
17546
17547     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17548         DisplayMessage(res, cpThinkOutput);
17549     } else {
17550       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17551                 WhiteOnMove(moveNumber) ? " " : ".. ",
17552                 parseList[moveNumber], res);
17553         DisplayMessage(message, cpThinkOutput);
17554     }
17555 }
17556
17557 void
17558 DisplayComment (int moveNumber, char *text)
17559 {
17560     char title[MSG_SIZ];
17561
17562     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17563       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17564     } else {
17565       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17566               WhiteOnMove(moveNumber) ? " " : ".. ",
17567               parseList[moveNumber]);
17568     }
17569     if (text != NULL && (appData.autoDisplayComment || commentUp))
17570         CommentPopUp(title, text);
17571 }
17572
17573 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17574  * might be busy thinking or pondering.  It can be omitted if your
17575  * gnuchess is configured to stop thinking immediately on any user
17576  * input.  However, that gnuchess feature depends on the FIONREAD
17577  * ioctl, which does not work properly on some flavors of Unix.
17578  */
17579 void
17580 Attention (ChessProgramState *cps)
17581 {
17582 #if ATTENTION
17583     if (!cps->useSigint) return;
17584     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17585     switch (gameMode) {
17586       case MachinePlaysWhite:
17587       case MachinePlaysBlack:
17588       case TwoMachinesPlay:
17589       case IcsPlayingWhite:
17590       case IcsPlayingBlack:
17591       case AnalyzeMode:
17592       case AnalyzeFile:
17593         /* Skip if we know it isn't thinking */
17594         if (!cps->maybeThinking) return;
17595         if (appData.debugMode)
17596           fprintf(debugFP, "Interrupting %s\n", cps->which);
17597         InterruptChildProcess(cps->pr);
17598         cps->maybeThinking = FALSE;
17599         break;
17600       default:
17601         break;
17602     }
17603 #endif /*ATTENTION*/
17604 }
17605
17606 int
17607 CheckFlags ()
17608 {
17609     if (whiteTimeRemaining <= 0) {
17610         if (!whiteFlag) {
17611             whiteFlag = TRUE;
17612             if (appData.icsActive) {
17613                 if (appData.autoCallFlag &&
17614                     gameMode == IcsPlayingBlack && !blackFlag) {
17615                   SendToICS(ics_prefix);
17616                   SendToICS("flag\n");
17617                 }
17618             } else {
17619                 if (blackFlag) {
17620                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17621                 } else {
17622                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17623                     if (appData.autoCallFlag) {
17624                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17625                         return TRUE;
17626                     }
17627                 }
17628             }
17629         }
17630     }
17631     if (blackTimeRemaining <= 0) {
17632         if (!blackFlag) {
17633             blackFlag = TRUE;
17634             if (appData.icsActive) {
17635                 if (appData.autoCallFlag &&
17636                     gameMode == IcsPlayingWhite && !whiteFlag) {
17637                   SendToICS(ics_prefix);
17638                   SendToICS("flag\n");
17639                 }
17640             } else {
17641                 if (whiteFlag) {
17642                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17643                 } else {
17644                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17645                     if (appData.autoCallFlag) {
17646                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17647                         return TRUE;
17648                     }
17649                 }
17650             }
17651         }
17652     }
17653     return FALSE;
17654 }
17655
17656 void
17657 CheckTimeControl ()
17658 {
17659     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17660         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17661
17662     /*
17663      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17664      */
17665     if ( !WhiteOnMove(forwardMostMove) ) {
17666         /* White made time control */
17667         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17668         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17669         /* [HGM] time odds: correct new time quota for time odds! */
17670                                             / WhitePlayer()->timeOdds;
17671         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17672     } else {
17673         lastBlack -= blackTimeRemaining;
17674         /* Black made time control */
17675         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17676                                             / WhitePlayer()->other->timeOdds;
17677         lastWhite = whiteTimeRemaining;
17678     }
17679 }
17680
17681 void
17682 DisplayBothClocks ()
17683 {
17684     int wom = gameMode == EditPosition ?
17685       !blackPlaysFirst : WhiteOnMove(currentMove);
17686     DisplayWhiteClock(whiteTimeRemaining, wom);
17687     DisplayBlackClock(blackTimeRemaining, !wom);
17688 }
17689
17690
17691 /* Timekeeping seems to be a portability nightmare.  I think everyone
17692    has ftime(), but I'm really not sure, so I'm including some ifdefs
17693    to use other calls if you don't.  Clocks will be less accurate if
17694    you have neither ftime nor gettimeofday.
17695 */
17696
17697 /* VS 2008 requires the #include outside of the function */
17698 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17699 #include <sys/timeb.h>
17700 #endif
17701
17702 /* Get the current time as a TimeMark */
17703 void
17704 GetTimeMark (TimeMark *tm)
17705 {
17706 #if HAVE_GETTIMEOFDAY
17707
17708     struct timeval timeVal;
17709     struct timezone timeZone;
17710
17711     gettimeofday(&timeVal, &timeZone);
17712     tm->sec = (long) timeVal.tv_sec;
17713     tm->ms = (int) (timeVal.tv_usec / 1000L);
17714
17715 #else /*!HAVE_GETTIMEOFDAY*/
17716 #if HAVE_FTIME
17717
17718 // include <sys/timeb.h> / moved to just above start of function
17719     struct timeb timeB;
17720
17721     ftime(&timeB);
17722     tm->sec = (long) timeB.time;
17723     tm->ms = (int) timeB.millitm;
17724
17725 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17726     tm->sec = (long) time(NULL);
17727     tm->ms = 0;
17728 #endif
17729 #endif
17730 }
17731
17732 /* Return the difference in milliseconds between two
17733    time marks.  We assume the difference will fit in a long!
17734 */
17735 long
17736 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17737 {
17738     return 1000L*(tm2->sec - tm1->sec) +
17739            (long) (tm2->ms - tm1->ms);
17740 }
17741
17742
17743 /*
17744  * Code to manage the game clocks.
17745  *
17746  * In tournament play, black starts the clock and then white makes a move.
17747  * We give the human user a slight advantage if he is playing white---the
17748  * clocks don't run until he makes his first move, so it takes zero time.
17749  * Also, we don't account for network lag, so we could get out of sync
17750  * with GNU Chess's clock -- but then, referees are always right.
17751  */
17752
17753 static TimeMark tickStartTM;
17754 static long intendedTickLength;
17755
17756 long
17757 NextTickLength (long timeRemaining)
17758 {
17759     long nominalTickLength, nextTickLength;
17760
17761     if (timeRemaining > 0L && timeRemaining <= 10000L)
17762       nominalTickLength = 100L;
17763     else
17764       nominalTickLength = 1000L;
17765     nextTickLength = timeRemaining % nominalTickLength;
17766     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17767
17768     return nextTickLength;
17769 }
17770
17771 /* Adjust clock one minute up or down */
17772 void
17773 AdjustClock (Boolean which, int dir)
17774 {
17775     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17776     if(which) blackTimeRemaining += 60000*dir;
17777     else      whiteTimeRemaining += 60000*dir;
17778     DisplayBothClocks();
17779     adjustedClock = TRUE;
17780 }
17781
17782 /* Stop clocks and reset to a fresh time control */
17783 void
17784 ResetClocks ()
17785 {
17786     (void) StopClockTimer();
17787     if (appData.icsActive) {
17788         whiteTimeRemaining = blackTimeRemaining = 0;
17789     } else if (searchTime) {
17790         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17791         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17792     } else { /* [HGM] correct new time quote for time odds */
17793         whiteTC = blackTC = fullTimeControlString;
17794         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17795         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17796     }
17797     if (whiteFlag || blackFlag) {
17798         DisplayTitle("");
17799         whiteFlag = blackFlag = FALSE;
17800     }
17801     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17802     DisplayBothClocks();
17803     adjustedClock = FALSE;
17804 }
17805
17806 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17807
17808 /* Decrement running clock by amount of time that has passed */
17809 void
17810 DecrementClocks ()
17811 {
17812     long timeRemaining;
17813     long lastTickLength, fudge;
17814     TimeMark now;
17815
17816     if (!appData.clockMode) return;
17817     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17818
17819     GetTimeMark(&now);
17820
17821     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17822
17823     /* Fudge if we woke up a little too soon */
17824     fudge = intendedTickLength - lastTickLength;
17825     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17826
17827     if (WhiteOnMove(forwardMostMove)) {
17828         if(whiteNPS >= 0) lastTickLength = 0;
17829         timeRemaining = whiteTimeRemaining -= lastTickLength;
17830         if(timeRemaining < 0 && !appData.icsActive) {
17831             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17832             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17833                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17834                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17835             }
17836         }
17837         DisplayWhiteClock(whiteTimeRemaining - fudge,
17838                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17839     } else {
17840         if(blackNPS >= 0) lastTickLength = 0;
17841         timeRemaining = blackTimeRemaining -= lastTickLength;
17842         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17843             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17844             if(suddenDeath) {
17845                 blackStartMove = forwardMostMove;
17846                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17847             }
17848         }
17849         DisplayBlackClock(blackTimeRemaining - fudge,
17850                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17851     }
17852     if (CheckFlags()) return;
17853
17854     if(twoBoards) { // count down secondary board's clocks as well
17855         activePartnerTime -= lastTickLength;
17856         partnerUp = 1;
17857         if(activePartner == 'W')
17858             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17859         else
17860             DisplayBlackClock(activePartnerTime, TRUE);
17861         partnerUp = 0;
17862     }
17863
17864     tickStartTM = now;
17865     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17866     StartClockTimer(intendedTickLength);
17867
17868     /* if the time remaining has fallen below the alarm threshold, sound the
17869      * alarm. if the alarm has sounded and (due to a takeback or time control
17870      * with increment) the time remaining has increased to a level above the
17871      * threshold, reset the alarm so it can sound again.
17872      */
17873
17874     if (appData.icsActive && appData.icsAlarm) {
17875
17876         /* make sure we are dealing with the user's clock */
17877         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17878                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17879            )) return;
17880
17881         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17882             alarmSounded = FALSE;
17883         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17884             PlayAlarmSound();
17885             alarmSounded = TRUE;
17886         }
17887     }
17888 }
17889
17890
17891 /* A player has just moved, so stop the previously running
17892    clock and (if in clock mode) start the other one.
17893    We redisplay both clocks in case we're in ICS mode, because
17894    ICS gives us an update to both clocks after every move.
17895    Note that this routine is called *after* forwardMostMove
17896    is updated, so the last fractional tick must be subtracted
17897    from the color that is *not* on move now.
17898 */
17899 void
17900 SwitchClocks (int newMoveNr)
17901 {
17902     long lastTickLength;
17903     TimeMark now;
17904     int flagged = FALSE;
17905
17906     GetTimeMark(&now);
17907
17908     if (StopClockTimer() && appData.clockMode) {
17909         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17910         if (!WhiteOnMove(forwardMostMove)) {
17911             if(blackNPS >= 0) lastTickLength = 0;
17912             blackTimeRemaining -= 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 =               // use GUI time
17916                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17917         } else {
17918            if(whiteNPS >= 0) lastTickLength = 0;
17919            whiteTimeRemaining -= lastTickLength;
17920            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17921 //         if(pvInfoList[forwardMostMove].time == -1)
17922                  pvInfoList[forwardMostMove].time =
17923                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17924         }
17925         flagged = CheckFlags();
17926     }
17927     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17928     CheckTimeControl();
17929
17930     if (flagged || !appData.clockMode) return;
17931
17932     switch (gameMode) {
17933       case MachinePlaysBlack:
17934       case MachinePlaysWhite:
17935       case BeginningOfGame:
17936         if (pausing) return;
17937         break;
17938
17939       case EditGame:
17940       case PlayFromGameFile:
17941       case IcsExamining:
17942         return;
17943
17944       default:
17945         break;
17946     }
17947
17948     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17949         if(WhiteOnMove(forwardMostMove))
17950              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17951         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17952     }
17953
17954     tickStartTM = now;
17955     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17956       whiteTimeRemaining : blackTimeRemaining);
17957     StartClockTimer(intendedTickLength);
17958 }
17959
17960
17961 /* Stop both clocks */
17962 void
17963 StopClocks ()
17964 {
17965     long lastTickLength;
17966     TimeMark now;
17967
17968     if (!StopClockTimer()) return;
17969     if (!appData.clockMode) return;
17970
17971     GetTimeMark(&now);
17972
17973     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17974     if (WhiteOnMove(forwardMostMove)) {
17975         if(whiteNPS >= 0) lastTickLength = 0;
17976         whiteTimeRemaining -= lastTickLength;
17977         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17978     } else {
17979         if(blackNPS >= 0) lastTickLength = 0;
17980         blackTimeRemaining -= lastTickLength;
17981         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17982     }
17983     CheckFlags();
17984 }
17985
17986 /* Start clock of player on move.  Time may have been reset, so
17987    if clock is already running, stop and restart it. */
17988 void
17989 StartClocks ()
17990 {
17991     (void) StopClockTimer(); /* in case it was running already */
17992     DisplayBothClocks();
17993     if (CheckFlags()) return;
17994
17995     if (!appData.clockMode) return;
17996     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17997
17998     GetTimeMark(&tickStartTM);
17999     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18000       whiteTimeRemaining : blackTimeRemaining);
18001
18002    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18003     whiteNPS = blackNPS = -1;
18004     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18005        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18006         whiteNPS = first.nps;
18007     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18008        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18009         blackNPS = first.nps;
18010     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18011         whiteNPS = second.nps;
18012     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18013         blackNPS = second.nps;
18014     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18015
18016     StartClockTimer(intendedTickLength);
18017 }
18018
18019 char *
18020 TimeString (long ms)
18021 {
18022     long second, minute, hour, day;
18023     char *sign = "";
18024     static char buf[32];
18025
18026     if (ms > 0 && ms <= 9900) {
18027       /* convert milliseconds to tenths, rounding up */
18028       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18029
18030       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18031       return buf;
18032     }
18033
18034     /* convert milliseconds to seconds, rounding up */
18035     /* use floating point to avoid strangeness of integer division
18036        with negative dividends on many machines */
18037     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18038
18039     if (second < 0) {
18040         sign = "-";
18041         second = -second;
18042     }
18043
18044     day = second / (60 * 60 * 24);
18045     second = second % (60 * 60 * 24);
18046     hour = second / (60 * 60);
18047     second = second % (60 * 60);
18048     minute = second / 60;
18049     second = second % 60;
18050
18051     if (day > 0)
18052       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18053               sign, day, hour, minute, second);
18054     else if (hour > 0)
18055       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18056     else
18057       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18058
18059     return buf;
18060 }
18061
18062
18063 /*
18064  * This is necessary because some C libraries aren't ANSI C compliant yet.
18065  */
18066 char *
18067 StrStr (char *string, char *match)
18068 {
18069     int i, length;
18070
18071     length = strlen(match);
18072
18073     for (i = strlen(string) - length; i >= 0; i--, string++)
18074       if (!strncmp(match, string, length))
18075         return string;
18076
18077     return NULL;
18078 }
18079
18080 char *
18081 StrCaseStr (char *string, char *match)
18082 {
18083     int i, j, length;
18084
18085     length = strlen(match);
18086
18087     for (i = strlen(string) - length; i >= 0; i--, string++) {
18088         for (j = 0; j < length; j++) {
18089             if (ToLower(match[j]) != ToLower(string[j]))
18090               break;
18091         }
18092         if (j == length) return string;
18093     }
18094
18095     return NULL;
18096 }
18097
18098 #ifndef _amigados
18099 int
18100 StrCaseCmp (char *s1, char *s2)
18101 {
18102     char c1, c2;
18103
18104     for (;;) {
18105         c1 = ToLower(*s1++);
18106         c2 = ToLower(*s2++);
18107         if (c1 > c2) return 1;
18108         if (c1 < c2) return -1;
18109         if (c1 == NULLCHAR) return 0;
18110     }
18111 }
18112
18113
18114 int
18115 ToLower (int c)
18116 {
18117     return isupper(c) ? tolower(c) : c;
18118 }
18119
18120
18121 int
18122 ToUpper (int c)
18123 {
18124     return islower(c) ? toupper(c) : c;
18125 }
18126 #endif /* !_amigados    */
18127
18128 char *
18129 StrSave (char *s)
18130 {
18131   char *ret;
18132
18133   if ((ret = (char *) malloc(strlen(s) + 1)))
18134     {
18135       safeStrCpy(ret, s, strlen(s)+1);
18136     }
18137   return ret;
18138 }
18139
18140 char *
18141 StrSavePtr (char *s, char **savePtr)
18142 {
18143     if (*savePtr) {
18144         free(*savePtr);
18145     }
18146     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18147       safeStrCpy(*savePtr, s, strlen(s)+1);
18148     }
18149     return(*savePtr);
18150 }
18151
18152 char *
18153 PGNDate ()
18154 {
18155     time_t clock;
18156     struct tm *tm;
18157     char buf[MSG_SIZ];
18158
18159     clock = time((time_t *)NULL);
18160     tm = localtime(&clock);
18161     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18162             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18163     return StrSave(buf);
18164 }
18165
18166
18167 char *
18168 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18169 {
18170     int i, j, fromX, fromY, toX, toY;
18171     int whiteToPlay, haveRights = nrCastlingRights;
18172     char buf[MSG_SIZ];
18173     char *p, *q;
18174     int emptycount;
18175     ChessSquare piece;
18176
18177     whiteToPlay = (gameMode == EditPosition) ?
18178       !blackPlaysFirst : (move % 2 == 0);
18179     p = buf;
18180
18181     /* Piece placement data */
18182     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18183         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18184         emptycount = 0;
18185         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18186             if (boards[move][i][j] == EmptySquare) {
18187                 emptycount++;
18188             } else { ChessSquare piece = boards[move][i][j];
18189                 if (emptycount > 0) {
18190                     if(emptycount<10) /* [HGM] can be >= 10 */
18191                         *p++ = '0' + emptycount;
18192                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18193                     emptycount = 0;
18194                 }
18195                 if(PieceToChar(piece) == '+') {
18196                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18197                     *p++ = '+';
18198                     piece = (ChessSquare)(CHUDEMOTED(piece));
18199                 }
18200                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18201                 if(*p = PieceSuffix(piece)) p++;
18202                 if(p[-1] == '~') {
18203                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18204                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18205                     *p++ = '~';
18206                 }
18207             }
18208         }
18209         if (emptycount > 0) {
18210             if(emptycount<10) /* [HGM] can be >= 10 */
18211                 *p++ = '0' + emptycount;
18212             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18213             emptycount = 0;
18214         }
18215         *p++ = '/';
18216     }
18217     *(p - 1) = ' ';
18218
18219     /* [HGM] print Crazyhouse or Shogi holdings */
18220     if( gameInfo.holdingsWidth ) {
18221         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18222         q = p;
18223         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18224             piece = boards[move][i][BOARD_WIDTH-1];
18225             if( piece != EmptySquare )
18226               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18227                   *p++ = PieceToChar(piece);
18228         }
18229         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18230             piece = boards[move][BOARD_HEIGHT-i-1][0];
18231             if( piece != EmptySquare )
18232               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18233                   *p++ = PieceToChar(piece);
18234         }
18235
18236         if( q == p ) *p++ = '-';
18237         *p++ = ']';
18238         *p++ = ' ';
18239     }
18240
18241     /* Active color */
18242     *p++ = whiteToPlay ? 'w' : 'b';
18243     *p++ = ' ';
18244
18245   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18246     haveRights = 0; q = p;
18247     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18248       piece = boards[move][0][i];
18249       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18250         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18251       }
18252     }
18253     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18254       piece = boards[move][BOARD_HEIGHT-1][i];
18255       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18256         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18257       }
18258     }
18259     if(p == q) *p++ = '-';
18260     *p++ = ' ';
18261   }
18262
18263   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18264     while(*p++ = *q++)
18265                       ;
18266     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18267   } else {
18268   if(haveRights) {
18269      int handW=0, handB=0;
18270      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18271         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18272         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18273      }
18274      q = p;
18275      if(appData.fischerCastling) {
18276         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18277            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18278                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18279         } else {
18280        /* [HGM] write directly from rights */
18281            if(boards[move][CASTLING][2] != NoRights &&
18282               boards[move][CASTLING][0] != NoRights   )
18283                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18284            if(boards[move][CASTLING][2] != NoRights &&
18285               boards[move][CASTLING][1] != NoRights   )
18286                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18287         }
18288         if(handB) {
18289            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18290                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18291         } else {
18292            if(boards[move][CASTLING][5] != NoRights &&
18293               boards[move][CASTLING][3] != NoRights   )
18294                 *p++ = boards[move][CASTLING][3] + AAA;
18295            if(boards[move][CASTLING][5] != NoRights &&
18296               boards[move][CASTLING][4] != NoRights   )
18297                 *p++ = boards[move][CASTLING][4] + AAA;
18298         }
18299      } else {
18300
18301         /* [HGM] write true castling rights */
18302         if( nrCastlingRights == 6 ) {
18303             int q, k=0;
18304             if(boards[move][CASTLING][0] != NoRights &&
18305                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18306             q = (boards[move][CASTLING][1] != NoRights &&
18307                  boards[move][CASTLING][2] != NoRights  );
18308             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18309                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18310                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18311                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18312             }
18313             if(q) *p++ = 'Q';
18314             k = 0;
18315             if(boards[move][CASTLING][3] != NoRights &&
18316                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18317             q = (boards[move][CASTLING][4] != NoRights &&
18318                  boards[move][CASTLING][5] != NoRights  );
18319             if(handB) {
18320                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18321                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18322                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18323             }
18324             if(q) *p++ = 'q';
18325         }
18326      }
18327      if (q == p) *p++ = '-'; /* No castling rights */
18328      *p++ = ' ';
18329   }
18330
18331   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18332      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18333      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18334     /* En passant target square */
18335     if (move > backwardMostMove) {
18336         fromX = moveList[move - 1][0] - AAA;
18337         fromY = moveList[move - 1][1] - ONE;
18338         toX = moveList[move - 1][2] - AAA;
18339         toY = moveList[move - 1][3] - ONE;
18340         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18341             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18342             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18343             fromX == toX) {
18344             /* 2-square pawn move just happened */
18345             *p++ = toX + AAA;
18346             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18347         } else {
18348             *p++ = '-';
18349         }
18350     } else if(move == backwardMostMove) {
18351         // [HGM] perhaps we should always do it like this, and forget the above?
18352         if((signed char)boards[move][EP_STATUS] >= 0) {
18353             *p++ = boards[move][EP_STATUS] + AAA;
18354             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18355         } else {
18356             *p++ = '-';
18357         }
18358     } else {
18359         *p++ = '-';
18360     }
18361     *p++ = ' ';
18362   }
18363   }
18364
18365     if(moveCounts)
18366     {   int i = 0, j=move;
18367
18368         /* [HGM] find reversible plies */
18369         if (appData.debugMode) { int k;
18370             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18371             for(k=backwardMostMove; k<=forwardMostMove; k++)
18372                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18373
18374         }
18375
18376         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18377         if( j == backwardMostMove ) i += initialRulePlies;
18378         sprintf(p, "%d ", i);
18379         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18380
18381         /* Fullmove number */
18382         sprintf(p, "%d", (move / 2) + 1);
18383     } else *--p = NULLCHAR;
18384
18385     return StrSave(buf);
18386 }
18387
18388 Boolean
18389 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18390 {
18391     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18392     char *p, c;
18393     int emptycount, virgin[BOARD_FILES];
18394     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18395
18396     p = fen;
18397
18398     /* Piece placement data */
18399     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18400         j = 0;
18401         for (;;) {
18402             if (*p == '/' || *p == ' ' || *p == '[' ) {
18403                 if(j > w) w = j;
18404                 emptycount = gameInfo.boardWidth - j;
18405                 while (emptycount--)
18406                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18407                 if (*p == '/') p++;
18408                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18409                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18410                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18411                     }
18412                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18413                 }
18414                 break;
18415 #if(BOARD_FILES >= 10)*0
18416             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18417                 p++; emptycount=10;
18418                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18419                 while (emptycount--)
18420                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18421 #endif
18422             } else if (*p == '*') {
18423                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18424             } else if (isdigit(*p)) {
18425                 emptycount = *p++ - '0';
18426                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18427                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18428                 while (emptycount--)
18429                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18430             } else if (*p == '<') {
18431                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18432                 else if (i != 0 || !shuffle) return FALSE;
18433                 p++;
18434             } else if (shuffle && *p == '>') {
18435                 p++; // for now ignore closing shuffle range, and assume rank-end
18436             } else if (*p == '?') {
18437                 if (j >= gameInfo.boardWidth) return FALSE;
18438                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18439                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18440             } else if (*p == '+' || isalpha(*p)) {
18441                 char *q, *s = SUFFIXES;
18442                 if (j >= gameInfo.boardWidth) return FALSE;
18443                 if(*p=='+') {
18444                     char c = *++p;
18445                     if(q = strchr(s, p[1])) p++;
18446                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18447                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18448                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18449                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18450                 } else {
18451                     char c = *p++;
18452                     if(q = strchr(s, *p)) p++;
18453                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18454                 }
18455
18456                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18457                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18458                     piece = (ChessSquare) (PROMOTED(piece));
18459                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18460                     p++;
18461                 }
18462                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18463                 if(piece == king) wKingRank = i;
18464                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18465             } else {
18466                 return FALSE;
18467             }
18468         }
18469     }
18470     while (*p == '/' || *p == ' ') p++;
18471
18472     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18473
18474     /* [HGM] by default clear Crazyhouse holdings, if present */
18475     if(gameInfo.holdingsWidth) {
18476        for(i=0; i<BOARD_HEIGHT; i++) {
18477            board[i][0]             = EmptySquare; /* black holdings */
18478            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18479            board[i][1]             = (ChessSquare) 0; /* black counts */
18480            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18481        }
18482     }
18483
18484     /* [HGM] look for Crazyhouse holdings here */
18485     while(*p==' ') p++;
18486     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18487         int swap=0, wcnt=0, bcnt=0;
18488         if(*p == '[') p++;
18489         if(*p == '<') swap++, p++;
18490         if(*p == '-' ) p++; /* empty holdings */ else {
18491             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18492             /* if we would allow FEN reading to set board size, we would   */
18493             /* have to add holdings and shift the board read so far here   */
18494             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18495                 p++;
18496                 if((int) piece >= (int) BlackPawn ) {
18497                     i = (int)piece - (int)BlackPawn;
18498                     i = PieceToNumber((ChessSquare)i);
18499                     if( i >= gameInfo.holdingsSize ) return FALSE;
18500                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18501                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18502                     bcnt++;
18503                 } else {
18504                     i = (int)piece - (int)WhitePawn;
18505                     i = PieceToNumber((ChessSquare)i);
18506                     if( i >= gameInfo.holdingsSize ) return FALSE;
18507                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18508                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18509                     wcnt++;
18510                 }
18511             }
18512             if(subst) { // substitute back-rank question marks by holdings pieces
18513                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18514                     int k, m, n = bcnt + 1;
18515                     if(board[0][j] == ClearBoard) {
18516                         if(!wcnt) return FALSE;
18517                         n = rand() % wcnt;
18518                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18519                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18520                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18521                             break;
18522                         }
18523                     }
18524                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18525                         if(!bcnt) return FALSE;
18526                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18527                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18528                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18529                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18530                             break;
18531                         }
18532                     }
18533                 }
18534                 subst = 0;
18535             }
18536         }
18537         if(*p == ']') p++;
18538     }
18539
18540     if(subst) return FALSE; // substitution requested, but no holdings
18541
18542     while(*p == ' ') p++;
18543
18544     /* Active color */
18545     c = *p++;
18546     if(appData.colorNickNames) {
18547       if( c == appData.colorNickNames[0] ) c = 'w'; else
18548       if( c == appData.colorNickNames[1] ) c = 'b';
18549     }
18550     switch (c) {
18551       case 'w':
18552         *blackPlaysFirst = FALSE;
18553         break;
18554       case 'b':
18555         *blackPlaysFirst = TRUE;
18556         break;
18557       default:
18558         return FALSE;
18559     }
18560
18561     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18562     /* return the extra info in global variiables             */
18563
18564     while(*p==' ') p++;
18565
18566     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18567         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18568         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18569     }
18570
18571     /* set defaults in case FEN is incomplete */
18572     board[EP_STATUS] = EP_UNKNOWN;
18573     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18574     for(i=0; i<nrCastlingRights; i++ ) {
18575         board[CASTLING][i] =
18576             appData.fischerCastling ? NoRights : initialRights[i];
18577     }   /* assume possible unless obviously impossible */
18578     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18579     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18580     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18581                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18582     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18583     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18584     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18585                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18586     FENrulePlies = 0;
18587
18588     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18589       char *q = p;
18590       int w=0, b=0;
18591       while(isalpha(*p)) {
18592         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18593         if(islower(*p)) b |= 1 << (*p++ - 'a');
18594       }
18595       if(*p == '-') p++;
18596       if(p != q) {
18597         board[TOUCHED_W] = ~w;
18598         board[TOUCHED_B] = ~b;
18599         while(*p == ' ') p++;
18600       }
18601     } else
18602
18603     if(nrCastlingRights) {
18604       int fischer = 0;
18605       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18606       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18607           /* castling indicator present, so default becomes no castlings */
18608           for(i=0; i<nrCastlingRights; i++ ) {
18609                  board[CASTLING][i] = NoRights;
18610           }
18611       }
18612       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18613              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18614              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18615              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18616         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18617
18618         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18619             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18620             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18621         }
18622         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18623             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18624         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18625                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18626         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18627                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18628         switch(c) {
18629           case'K':
18630               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18631               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18632               board[CASTLING][2] = whiteKingFile;
18633               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18634               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18635               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18636               break;
18637           case'Q':
18638               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18639               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18640               board[CASTLING][2] = whiteKingFile;
18641               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18642               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18643               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18644               break;
18645           case'k':
18646               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18647               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18648               board[CASTLING][5] = blackKingFile;
18649               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18650               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18651               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18652               break;
18653           case'q':
18654               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18655               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18656               board[CASTLING][5] = blackKingFile;
18657               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18658               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18659               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18660           case '-':
18661               break;
18662           default: /* FRC castlings */
18663               if(c >= 'a') { /* black rights */
18664                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18665                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18666                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18667                   if(i == BOARD_RGHT) break;
18668                   board[CASTLING][5] = i;
18669                   c -= AAA;
18670                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18671                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18672                   if(c > i)
18673                       board[CASTLING][3] = c;
18674                   else
18675                       board[CASTLING][4] = c;
18676               } else { /* white rights */
18677                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18678                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18679                     if(board[0][i] == WhiteKing) break;
18680                   if(i == BOARD_RGHT) break;
18681                   board[CASTLING][2] = i;
18682                   c -= AAA - 'a' + 'A';
18683                   if(board[0][c] >= WhiteKing) break;
18684                   if(c > i)
18685                       board[CASTLING][0] = c;
18686                   else
18687                       board[CASTLING][1] = c;
18688               }
18689         }
18690       }
18691       for(i=0; i<nrCastlingRights; i++)
18692         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18693       if(gameInfo.variant == VariantSChess)
18694         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18695       if(fischer && shuffle) appData.fischerCastling = TRUE;
18696     if (appData.debugMode) {
18697         fprintf(debugFP, "FEN castling rights:");
18698         for(i=0; i<nrCastlingRights; i++)
18699         fprintf(debugFP, " %d", board[CASTLING][i]);
18700         fprintf(debugFP, "\n");
18701     }
18702
18703       while(*p==' ') p++;
18704     }
18705
18706     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18707
18708     /* read e.p. field in games that know e.p. capture */
18709     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18710        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18711        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18712       if(*p=='-') {
18713         p++; board[EP_STATUS] = EP_NONE;
18714       } else {
18715          char c = *p++ - AAA;
18716
18717          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18718          if(*p >= '0' && *p <='9') p++;
18719          board[EP_STATUS] = c;
18720       }
18721     }
18722
18723
18724     if(sscanf(p, "%d", &i) == 1) {
18725         FENrulePlies = i; /* 50-move ply counter */
18726         /* (The move number is still ignored)    */
18727     }
18728
18729     return TRUE;
18730 }
18731
18732 void
18733 EditPositionPasteFEN (char *fen)
18734 {
18735   if (fen != NULL) {
18736     Board initial_position;
18737
18738     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18739       DisplayError(_("Bad FEN position in clipboard"), 0);
18740       return ;
18741     } else {
18742       int savedBlackPlaysFirst = blackPlaysFirst;
18743       EditPositionEvent();
18744       blackPlaysFirst = savedBlackPlaysFirst;
18745       CopyBoard(boards[0], initial_position);
18746       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18747       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18748       DisplayBothClocks();
18749       DrawPosition(FALSE, boards[currentMove]);
18750     }
18751   }
18752 }
18753
18754 static char cseq[12] = "\\   ";
18755
18756 Boolean
18757 set_cont_sequence (char *new_seq)
18758 {
18759     int len;
18760     Boolean ret;
18761
18762     // handle bad attempts to set the sequence
18763         if (!new_seq)
18764                 return 0; // acceptable error - no debug
18765
18766     len = strlen(new_seq);
18767     ret = (len > 0) && (len < sizeof(cseq));
18768     if (ret)
18769       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18770     else if (appData.debugMode)
18771       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18772     return ret;
18773 }
18774
18775 /*
18776     reformat a source message so words don't cross the width boundary.  internal
18777     newlines are not removed.  returns the wrapped size (no null character unless
18778     included in source message).  If dest is NULL, only calculate the size required
18779     for the dest buffer.  lp argument indicats line position upon entry, and it's
18780     passed back upon exit.
18781 */
18782 int
18783 wrap (char *dest, char *src, int count, int width, int *lp)
18784 {
18785     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18786
18787     cseq_len = strlen(cseq);
18788     old_line = line = *lp;
18789     ansi = len = clen = 0;
18790
18791     for (i=0; i < count; i++)
18792     {
18793         if (src[i] == '\033')
18794             ansi = 1;
18795
18796         // if we hit the width, back up
18797         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18798         {
18799             // store i & len in case the word is too long
18800             old_i = i, old_len = len;
18801
18802             // find the end of the last word
18803             while (i && src[i] != ' ' && src[i] != '\n')
18804             {
18805                 i--;
18806                 len--;
18807             }
18808
18809             // word too long?  restore i & len before splitting it
18810             if ((old_i-i+clen) >= width)
18811             {
18812                 i = old_i;
18813                 len = old_len;
18814             }
18815
18816             // extra space?
18817             if (i && src[i-1] == ' ')
18818                 len--;
18819
18820             if (src[i] != ' ' && src[i] != '\n')
18821             {
18822                 i--;
18823                 if (len)
18824                     len--;
18825             }
18826
18827             // now append the newline and continuation sequence
18828             if (dest)
18829                 dest[len] = '\n';
18830             len++;
18831             if (dest)
18832                 strncpy(dest+len, cseq, cseq_len);
18833             len += cseq_len;
18834             line = cseq_len;
18835             clen = cseq_len;
18836             continue;
18837         }
18838
18839         if (dest)
18840             dest[len] = src[i];
18841         len++;
18842         if (!ansi)
18843             line++;
18844         if (src[i] == '\n')
18845             line = 0;
18846         if (src[i] == 'm')
18847             ansi = 0;
18848     }
18849     if (dest && appData.debugMode)
18850     {
18851         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18852             count, width, line, len, *lp);
18853         show_bytes(debugFP, src, count);
18854         fprintf(debugFP, "\ndest: ");
18855         show_bytes(debugFP, dest, len);
18856         fprintf(debugFP, "\n");
18857     }
18858     *lp = dest ? line : old_line;
18859
18860     return len;
18861 }
18862
18863 // [HGM] vari: routines for shelving variations
18864 Boolean modeRestore = FALSE;
18865
18866 void
18867 PushInner (int firstMove, int lastMove)
18868 {
18869         int i, j, nrMoves = lastMove - firstMove;
18870
18871         // push current tail of game on stack
18872         savedResult[storedGames] = gameInfo.result;
18873         savedDetails[storedGames] = gameInfo.resultDetails;
18874         gameInfo.resultDetails = NULL;
18875         savedFirst[storedGames] = firstMove;
18876         savedLast [storedGames] = lastMove;
18877         savedFramePtr[storedGames] = framePtr;
18878         framePtr -= nrMoves; // reserve space for the boards
18879         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18880             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18881             for(j=0; j<MOVE_LEN; j++)
18882                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18883             for(j=0; j<2*MOVE_LEN; j++)
18884                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18885             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18886             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18887             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18888             pvInfoList[firstMove+i-1].depth = 0;
18889             commentList[framePtr+i] = commentList[firstMove+i];
18890             commentList[firstMove+i] = NULL;
18891         }
18892
18893         storedGames++;
18894         forwardMostMove = firstMove; // truncate game so we can start variation
18895 }
18896
18897 void
18898 PushTail (int firstMove, int lastMove)
18899 {
18900         if(appData.icsActive) { // only in local mode
18901                 forwardMostMove = currentMove; // mimic old ICS behavior
18902                 return;
18903         }
18904         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18905
18906         PushInner(firstMove, lastMove);
18907         if(storedGames == 1) GreyRevert(FALSE);
18908         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18909 }
18910
18911 void
18912 PopInner (Boolean annotate)
18913 {
18914         int i, j, nrMoves;
18915         char buf[8000], moveBuf[20];
18916
18917         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18918         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18919         nrMoves = savedLast[storedGames] - currentMove;
18920         if(annotate) {
18921                 int cnt = 10;
18922                 if(!WhiteOnMove(currentMove))
18923                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18924                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18925                 for(i=currentMove; i<forwardMostMove; i++) {
18926                         if(WhiteOnMove(i))
18927                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18928                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18929                         strcat(buf, moveBuf);
18930                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18931                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18932                 }
18933                 strcat(buf, ")");
18934         }
18935         for(i=1; i<=nrMoves; i++) { // copy last variation back
18936             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18937             for(j=0; j<MOVE_LEN; j++)
18938                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18939             for(j=0; j<2*MOVE_LEN; j++)
18940                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18941             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18942             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18943             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18944             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18945             commentList[currentMove+i] = commentList[framePtr+i];
18946             commentList[framePtr+i] = NULL;
18947         }
18948         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18949         framePtr = savedFramePtr[storedGames];
18950         gameInfo.result = savedResult[storedGames];
18951         if(gameInfo.resultDetails != NULL) {
18952             free(gameInfo.resultDetails);
18953       }
18954         gameInfo.resultDetails = savedDetails[storedGames];
18955         forwardMostMove = currentMove + nrMoves;
18956 }
18957
18958 Boolean
18959 PopTail (Boolean annotate)
18960 {
18961         if(appData.icsActive) return FALSE; // only in local mode
18962         if(!storedGames) return FALSE; // sanity
18963         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18964
18965         PopInner(annotate);
18966         if(currentMove < forwardMostMove) ForwardEvent(); else
18967         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18968
18969         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18970         return TRUE;
18971 }
18972
18973 void
18974 CleanupTail ()
18975 {       // remove all shelved variations
18976         int i;
18977         for(i=0; i<storedGames; i++) {
18978             if(savedDetails[i])
18979                 free(savedDetails[i]);
18980             savedDetails[i] = NULL;
18981         }
18982         for(i=framePtr; i<MAX_MOVES; i++) {
18983                 if(commentList[i]) free(commentList[i]);
18984                 commentList[i] = NULL;
18985         }
18986         framePtr = MAX_MOVES-1;
18987         storedGames = 0;
18988 }
18989
18990 void
18991 LoadVariation (int index, char *text)
18992 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18993         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18994         int level = 0, move;
18995
18996         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18997         // first find outermost bracketing variation
18998         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18999             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19000                 if(*p == '{') wait = '}'; else
19001                 if(*p == '[') wait = ']'; else
19002                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19003                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19004             }
19005             if(*p == wait) wait = NULLCHAR; // closing ]} found
19006             p++;
19007         }
19008         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19009         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19010         end[1] = NULLCHAR; // clip off comment beyond variation
19011         ToNrEvent(currentMove-1);
19012         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19013         // kludge: use ParsePV() to append variation to game
19014         move = currentMove;
19015         ParsePV(start, TRUE, TRUE);
19016         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19017         ClearPremoveHighlights();
19018         CommentPopDown();
19019         ToNrEvent(currentMove+1);
19020 }
19021
19022 void
19023 LoadTheme ()
19024 {
19025     char *p, *q, buf[MSG_SIZ];
19026     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19027         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19028         ParseArgsFromString(buf);
19029         ActivateTheme(TRUE); // also redo colors
19030         return;
19031     }
19032     p = nickName;
19033     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19034     {
19035         int len;
19036         q = appData.themeNames;
19037         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19038       if(appData.useBitmaps) {
19039         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19040                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19041                 appData.liteBackTextureMode,
19042                 appData.darkBackTextureMode );
19043       } else {
19044         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19045                 Col2Text(2),   // lightSquareColor
19046                 Col2Text(3) ); // darkSquareColor
19047       }
19048       if(appData.useBorder) {
19049         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19050                 appData.border);
19051       } else {
19052         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19053       }
19054       if(appData.useFont) {
19055         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19056                 appData.renderPiecesWithFont,
19057                 appData.fontToPieceTable,
19058                 Col2Text(9),    // appData.fontBackColorWhite
19059                 Col2Text(10) ); // appData.fontForeColorBlack
19060       } else {
19061         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19062                 appData.pieceDirectory);
19063         if(!appData.pieceDirectory[0])
19064           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19065                 Col2Text(0),   // whitePieceColor
19066                 Col2Text(1) ); // blackPieceColor
19067       }
19068       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19069                 Col2Text(4),   // highlightSquareColor
19070                 Col2Text(5) ); // premoveHighlightColor
19071         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19072         if(insert != q) insert[-1] = NULLCHAR;
19073         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19074         if(q)   free(q);
19075     }
19076     ActivateTheme(FALSE);
19077 }