a67ca7ceab7d3c57dcef59e338288d7acafa49b6
[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 int deadRanks;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
992         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
993         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
994         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
995         appData.firstProtocolVersion = PROTOVER;
996         ParseArgsFromString(buf);
997         SwapEngines(i);
998         ReplaceEngine(cps, i);
999         FloatToFront(&appData.recentEngineList, engineLine);
1000         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1001         return;
1002     }
1003     p = engineName;
1004     while(q = strchr(p, SLASH)) p = q+1;
1005     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1006     if(engineDir[0] != NULLCHAR) {
1007         ASSIGN(appData.directory[i], engineDir); p = engineName;
1008     } else if(p != engineName) { // derive directory from engine path, when not given
1009         p[-1] = 0;
1010         ASSIGN(appData.directory[i], engineName);
1011         p[-1] = SLASH;
1012         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1013     } else { ASSIGN(appData.directory[i], "."); }
1014     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1015     if(params[0]) {
1016         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1017         snprintf(command, MSG_SIZ, "%s %s", p, params);
1018         p = command;
1019     }
1020     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1021     ASSIGN(appData.chessProgram[i], p);
1022     appData.isUCI[i] = isUCI;
1023     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1024     appData.hasOwnBookUCI[i] = hasBook;
1025     if(!nickName[0]) useNick = FALSE;
1026     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1027     if(addToList) {
1028         int len;
1029         char quote;
1030         q = firstChessProgramNames;
1031         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1032         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1033         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1034                         quote, p, quote, appData.directory[i],
1035                         useNick ? " -fn \"" : "",
1036                         useNick ? nickName : "",
1037                         useNick ? "\"" : "",
1038                         v1 ? " -firstProtocolVersion 1" : "",
1039                         hasBook ? "" : " -fNoOwnBookUCI",
1040                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1041                         storeVariant ? " -variant " : "",
1042                         storeVariant ? VariantName(gameInfo.variant) : "");
1043         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1044         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1045         if(insert != q) insert[-1] = NULLCHAR;
1046         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1047         if(q)   free(q);
1048         FloatToFront(&appData.recentEngineList, buf);
1049     }
1050     ReplaceEngine(cps, i);
1051 }
1052
1053 void
1054 InitTimeControls ()
1055 {
1056     int matched, min, sec;
1057     /*
1058      * Parse timeControl resource
1059      */
1060     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1061                           appData.movesPerSession)) {
1062         char buf[MSG_SIZ];
1063         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1064         DisplayFatalError(buf, 0, 2);
1065     }
1066
1067     /*
1068      * Parse searchTime resource
1069      */
1070     if (*appData.searchTime != NULLCHAR) {
1071         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1072         if (matched == 1) {
1073             searchTime = min * 60;
1074         } else if (matched == 2) {
1075             searchTime = min * 60 + sec;
1076         } else {
1077             char buf[MSG_SIZ];
1078             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1079             DisplayFatalError(buf, 0, 2);
1080         }
1081     }
1082 }
1083
1084 void
1085 InitBackEnd1 ()
1086 {
1087
1088     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1089     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1090
1091     GetTimeMark(&programStartTime);
1092     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1093     appData.seedBase = random() + (random()<<15);
1094     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1095
1096     ClearProgramStats();
1097     programStats.ok_to_send = 1;
1098     programStats.seen_stat = 0;
1099
1100     /*
1101      * Initialize game list
1102      */
1103     ListNew(&gameList);
1104
1105
1106     /*
1107      * Internet chess server status
1108      */
1109     if (appData.icsActive) {
1110         appData.matchMode = FALSE;
1111         appData.matchGames = 0;
1112 #if ZIPPY
1113         appData.noChessProgram = !appData.zippyPlay;
1114 #else
1115         appData.zippyPlay = FALSE;
1116         appData.zippyTalk = FALSE;
1117         appData.noChessProgram = TRUE;
1118 #endif
1119         if (*appData.icsHelper != NULLCHAR) {
1120             appData.useTelnet = TRUE;
1121             appData.telnetProgram = appData.icsHelper;
1122         }
1123     } else {
1124         appData.zippyTalk = appData.zippyPlay = FALSE;
1125     }
1126
1127     /* [AS] Initialize pv info list [HGM] and game state */
1128     {
1129         int i, j;
1130
1131         for( i=0; i<=framePtr; i++ ) {
1132             pvInfoList[i].depth = -1;
1133             boards[i][EP_STATUS] = EP_NONE;
1134             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1135         }
1136     }
1137
1138     InitTimeControls();
1139
1140     /* [AS] Adjudication threshold */
1141     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1142
1143     InitEngine(&first, 0);
1144     InitEngine(&second, 1);
1145     CommonEngineInit();
1146
1147     pairing.which = "pairing"; // pairing engine
1148     pairing.pr = NoProc;
1149     pairing.isr = NULL;
1150     pairing.program = appData.pairingEngine;
1151     pairing.host = "localhost";
1152     pairing.dir = ".";
1153
1154     if (appData.icsActive) {
1155         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1156     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1157         appData.clockMode = FALSE;
1158         first.sendTime = second.sendTime = 0;
1159     }
1160
1161 #if ZIPPY
1162     /* Override some settings from environment variables, for backward
1163        compatibility.  Unfortunately it's not feasible to have the env
1164        vars just set defaults, at least in xboard.  Ugh.
1165     */
1166     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1167       ZippyInit();
1168     }
1169 #endif
1170
1171     if (!appData.icsActive) {
1172       char buf[MSG_SIZ];
1173       int len;
1174
1175       /* Check for variants that are supported only in ICS mode,
1176          or not at all.  Some that are accepted here nevertheless
1177          have bugs; see comments below.
1178       */
1179       VariantClass variant = StringToVariant(appData.variant);
1180       switch (variant) {
1181       case VariantBughouse:     /* need four players and two boards */
1182       case VariantKriegspiel:   /* need to hide pieces and move details */
1183         /* case VariantFischeRandom: (Fabien: moved below) */
1184         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1185         if( (len >= MSG_SIZ) && appData.debugMode )
1186           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1187
1188         DisplayFatalError(buf, 0, 2);
1189         return;
1190
1191       case VariantUnknown:
1192       case VariantLoadable:
1193       case Variant29:
1194       case Variant30:
1195       case Variant31:
1196       case Variant32:
1197       case Variant33:
1198       case Variant34:
1199       case Variant35:
1200       case Variant36:
1201       default:
1202         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1203         if( (len >= MSG_SIZ) && appData.debugMode )
1204           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1205
1206         DisplayFatalError(buf, 0, 2);
1207         return;
1208
1209       case VariantNormal:     /* definitely works! */
1210         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1211           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1212           return;
1213         }
1214       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1215       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1216       case VariantGothic:     /* [HGM] should work */
1217       case VariantCapablanca: /* [HGM] should work */
1218       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1219       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1220       case VariantChu:        /* [HGM] experimental */
1221       case VariantKnightmate: /* [HGM] should work */
1222       case VariantCylinder:   /* [HGM] untested */
1223       case VariantFalcon:     /* [HGM] untested */
1224       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1225                                  offboard interposition not understood */
1226       case VariantWildCastle: /* pieces not automatically shuffled */
1227       case VariantNoCastle:   /* pieces not automatically shuffled */
1228       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1229       case VariantLosers:     /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantSuicide:    /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantGiveaway:   /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantTwoKings:   /* should work */
1236       case VariantAtomic:     /* should work except for win condition */
1237       case Variant3Check:     /* should work except for win condition */
1238       case VariantShatranj:   /* should work except for all win conditions */
1239       case VariantMakruk:     /* should work except for draw countdown */
1240       case VariantASEAN :     /* should work except for draw countdown */
1241       case VariantBerolina:   /* might work if TestLegality is off */
1242       case VariantCapaRandom: /* should work */
1243       case VariantJanus:      /* should work */
1244       case VariantSuper:      /* experimental */
1245       case VariantGreat:      /* experimental, requires legality testing to be off */
1246       case VariantSChess:     /* S-Chess, should work */
1247       case VariantGrand:      /* should work */
1248       case VariantSpartan:    /* should work */
1249       case VariantLion:       /* should work */
1250       case VariantChuChess:   /* should work */
1251         break;
1252       }
1253     }
1254
1255 }
1256
1257 int
1258 NextIntegerFromString (char ** str, long * value)
1259 {
1260     int result = -1;
1261     char * s = *str;
1262
1263     while( *s == ' ' || *s == '\t' ) {
1264         s++;
1265     }
1266
1267     *value = 0;
1268
1269     if( *s >= '0' && *s <= '9' ) {
1270         while( *s >= '0' && *s <= '9' ) {
1271             *value = *value * 10 + (*s - '0');
1272             s++;
1273         }
1274
1275         result = 0;
1276     }
1277
1278     *str = s;
1279
1280     return result;
1281 }
1282
1283 int
1284 NextTimeControlFromString (char ** str, long * value)
1285 {
1286     long temp;
1287     int result = NextIntegerFromString( str, &temp );
1288
1289     if( result == 0 ) {
1290         *value = temp * 60; /* Minutes */
1291         if( **str == ':' ) {
1292             (*str)++;
1293             result = NextIntegerFromString( str, &temp );
1294             *value += temp; /* Seconds */
1295         }
1296     }
1297
1298     return result;
1299 }
1300
1301 int
1302 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1303 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1304     int result = -1, type = 0; long temp, temp2;
1305
1306     if(**str != ':') return -1; // old params remain in force!
1307     (*str)++;
1308     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1309     if( NextIntegerFromString( str, &temp ) ) return -1;
1310     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1311
1312     if(**str != '/') {
1313         /* time only: incremental or sudden-death time control */
1314         if(**str == '+') { /* increment follows; read it */
1315             (*str)++;
1316             if(**str == '!') type = *(*str)++; // Bronstein TC
1317             if(result = NextIntegerFromString( str, &temp2)) return -1;
1318             *inc = temp2 * 1000;
1319             if(**str == '.') { // read fraction of increment
1320                 char *start = ++(*str);
1321                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1322                 temp2 *= 1000;
1323                 while(start++ < *str) temp2 /= 10;
1324                 *inc += temp2;
1325             }
1326         } else *inc = 0;
1327         *moves = 0; *tc = temp * 1000; *incType = type;
1328         return 0;
1329     }
1330
1331     (*str)++; /* classical time control */
1332     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1333
1334     if(result == 0) {
1335         *moves = temp;
1336         *tc    = temp2 * 1000;
1337         *inc   = 0;
1338         *incType = type;
1339     }
1340     return result;
1341 }
1342
1343 int
1344 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1345 {   /* [HGM] get time to add from the multi-session time-control string */
1346     int incType, moves=1; /* kludge to force reading of first session */
1347     long time, increment;
1348     char *s = tcString;
1349
1350     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1351     do {
1352         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1353         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1354         if(movenr == -1) return time;    /* last move before new session     */
1355         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1356         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1357         if(!moves) return increment;     /* current session is incremental   */
1358         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1359     } while(movenr >= -1);               /* try again for next session       */
1360
1361     return 0; // no new time quota on this move
1362 }
1363
1364 int
1365 ParseTimeControl (char *tc, float ti, int mps)
1366 {
1367   long tc1;
1368   long tc2;
1369   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1370   int min, sec=0;
1371
1372   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1373   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1374       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1375   if(ti > 0) {
1376
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1381   } else {
1382     if(mps)
1383       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1384     else
1385       snprintf(buf, MSG_SIZ, ":%s", mytc);
1386   }
1387   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1388
1389   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1390     return FALSE;
1391   }
1392
1393   if( *tc == '/' ) {
1394     /* Parse second time control */
1395     tc++;
1396
1397     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1398       return FALSE;
1399     }
1400
1401     if( tc2 == 0 ) {
1402       return FALSE;
1403     }
1404
1405     timeControl_2 = tc2 * 1000;
1406   }
1407   else {
1408     timeControl_2 = 0;
1409   }
1410
1411   if( tc1 == 0 ) {
1412     return FALSE;
1413   }
1414
1415   timeControl = tc1 * 1000;
1416
1417   if (ti >= 0) {
1418     timeIncrement = ti * 1000;  /* convert to ms */
1419     movesPerSession = 0;
1420   } else {
1421     timeIncrement = 0;
1422     movesPerSession = mps;
1423   }
1424   return TRUE;
1425 }
1426
1427 void
1428 InitBackEnd2 ()
1429 {
1430     if (appData.debugMode) {
1431 #    ifdef __GIT_VERSION
1432       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1433 #    else
1434       fprintf(debugFP, "Version: %s\n", programVersion);
1435 #    endif
1436     }
1437     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1438
1439     set_cont_sequence(appData.wrapContSeq);
1440     if (appData.matchGames > 0) {
1441         appData.matchMode = TRUE;
1442     } else if (appData.matchMode) {
1443         appData.matchGames = 1;
1444     }
1445     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1446         appData.matchGames = appData.sameColorGames;
1447     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1448         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1449         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1450     }
1451     Reset(TRUE, FALSE);
1452     if (appData.noChessProgram || first.protocolVersion == 1) {
1453       InitBackEnd3();
1454     } else {
1455       /* kludge: allow timeout for initial "feature" commands */
1456       FreezeUI();
1457       DisplayMessage("", _("Starting chess program"));
1458       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1459     }
1460 }
1461
1462 int
1463 CalculateIndex (int index, int gameNr)
1464 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1465     int res;
1466     if(index > 0) return index; // fixed nmber
1467     if(index == 0) return 1;
1468     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1469     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1470     return res;
1471 }
1472
1473 int
1474 LoadGameOrPosition (int gameNr)
1475 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1476     if (*appData.loadGameFile != NULLCHAR) {
1477         if (!LoadGameFromFile(appData.loadGameFile,
1478                 CalculateIndex(appData.loadGameIndex, gameNr),
1479                               appData.loadGameFile, FALSE)) {
1480             DisplayFatalError(_("Bad game file"), 0, 1);
1481             return 0;
1482         }
1483     } else if (*appData.loadPositionFile != NULLCHAR) {
1484         if (!LoadPositionFromFile(appData.loadPositionFile,
1485                 CalculateIndex(appData.loadPositionIndex, gameNr),
1486                                   appData.loadPositionFile)) {
1487             DisplayFatalError(_("Bad position file"), 0, 1);
1488             return 0;
1489         }
1490     }
1491     return 1;
1492 }
1493
1494 void
1495 ReserveGame (int gameNr, char resChar)
1496 {
1497     FILE *tf = fopen(appData.tourneyFile, "r+");
1498     char *p, *q, c, buf[MSG_SIZ];
1499     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1500     safeStrCpy(buf, lastMsg, MSG_SIZ);
1501     DisplayMessage(_("Pick new game"), "");
1502     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1503     ParseArgsFromFile(tf);
1504     p = q = appData.results;
1505     if(appData.debugMode) {
1506       char *r = appData.participants;
1507       fprintf(debugFP, "results = '%s'\n", p);
1508       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1509       fprintf(debugFP, "\n");
1510     }
1511     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1512     nextGame = q - p;
1513     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1514     safeStrCpy(q, p, strlen(p) + 2);
1515     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1516     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1517     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1518         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1519         q[nextGame] = '*';
1520     }
1521     fseek(tf, -(strlen(p)+4), SEEK_END);
1522     c = fgetc(tf);
1523     if(c != '"') // depending on DOS or Unix line endings we can be one off
1524          fseek(tf, -(strlen(p)+2), SEEK_END);
1525     else fseek(tf, -(strlen(p)+3), SEEK_END);
1526     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1527     DisplayMessage(buf, "");
1528     free(p); appData.results = q;
1529     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1530        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1531       int round = appData.defaultMatchGames * appData.tourneyType;
1532       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1533          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1534         UnloadEngine(&first);  // next game belongs to other pairing;
1535         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1536     }
1537     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1538 }
1539
1540 void
1541 MatchEvent (int mode)
1542 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1543         int dummy;
1544         if(matchMode) { // already in match mode: switch it off
1545             abortMatch = TRUE;
1546             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1547             return;
1548         }
1549 //      if(gameMode != BeginningOfGame) {
1550 //          DisplayError(_("You can only start a match from the initial position."), 0);
1551 //          return;
1552 //      }
1553         abortMatch = FALSE;
1554         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1555         /* Set up machine vs. machine match */
1556         nextGame = 0;
1557         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1558         if(appData.tourneyFile[0]) {
1559             ReserveGame(-1, 0);
1560             if(nextGame > appData.matchGames) {
1561                 char buf[MSG_SIZ];
1562                 if(strchr(appData.results, '*') == NULL) {
1563                     FILE *f;
1564                     appData.tourneyCycles++;
1565                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1566                         fclose(f);
1567                         NextTourneyGame(-1, &dummy);
1568                         ReserveGame(-1, 0);
1569                         if(nextGame <= appData.matchGames) {
1570                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1571                             matchMode = mode;
1572                             ScheduleDelayedEvent(NextMatchGame, 10000);
1573                             return;
1574                         }
1575                     }
1576                 }
1577                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1578                 DisplayError(buf, 0);
1579                 appData.tourneyFile[0] = 0;
1580                 return;
1581             }
1582         } else
1583         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1584             DisplayFatalError(_("Can't have a match with no chess programs"),
1585                               0, 2);
1586             return;
1587         }
1588         matchMode = mode;
1589         matchGame = roundNr = 1;
1590         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1591         NextMatchGame();
1592 }
1593
1594 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1604        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1605         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1606        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1607        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1608         char c, *q = first.variants, *p = strchr(q, ',');
1609         if(p) *p = NULLCHAR;
1610         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1611             int w, h, s;
1612             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1613                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1614             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1615             Reset(TRUE, FALSE);         // and re-initialize
1616         }
1617         if(p) *p = ',';
1618     }
1619
1620     InitChessProgram(&first, startedFromSetupPosition);
1621
1622     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1623         free(programVersion);
1624         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1625         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1626         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1627     }
1628
1629     if (appData.icsActive) {
1630 #ifdef WIN32
1631         /* [DM] Make a console window if needed [HGM] merged ifs */
1632         ConsoleCreate();
1633 #endif
1634         err = establish();
1635         if (err != 0)
1636           {
1637             if (*appData.icsCommPort != NULLCHAR)
1638               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1639                              appData.icsCommPort);
1640             else
1641               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1642                         appData.icsHost, appData.icsPort);
1643
1644             if( (len >= MSG_SIZ) && appData.debugMode )
1645               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1646
1647             DisplayFatalError(buf, err, 1);
1648             return;
1649         }
1650         SetICSMode();
1651         telnetISR =
1652           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1653         fromUserISR =
1654           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1655         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1656             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1657     } else if (appData.noChessProgram) {
1658         SetNCPMode();
1659     } else {
1660         SetGNUMode();
1661     }
1662
1663     if (*appData.cmailGameName != NULLCHAR) {
1664         SetCmailMode();
1665         OpenLoopback(&cmailPR);
1666         cmailISR =
1667           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1668     }
1669
1670     ThawUI();
1671     DisplayMessage("", "");
1672     if (StrCaseCmp(appData.initialMode, "") == 0) {
1673       initialMode = BeginningOfGame;
1674       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1675         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1676         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1677         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1678         ModeHighlight();
1679       }
1680     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1681       initialMode = TwoMachinesPlay;
1682     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1683       initialMode = AnalyzeFile;
1684     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1685       initialMode = AnalyzeMode;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1687       initialMode = MachinePlaysWhite;
1688     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1689       initialMode = MachinePlaysBlack;
1690     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1691       initialMode = EditGame;
1692     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1693       initialMode = EditPosition;
1694     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1695       initialMode = Training;
1696     } else {
1697       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1698       if( (len >= MSG_SIZ) && appData.debugMode )
1699         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1700
1701       DisplayFatalError(buf, 0, 2);
1702       return;
1703     }
1704
1705     if (appData.matchMode) {
1706         if(appData.tourneyFile[0]) { // start tourney from command line
1707             FILE *f;
1708             if(f = fopen(appData.tourneyFile, "r")) {
1709                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1710                 fclose(f);
1711                 appData.clockMode = TRUE;
1712                 SetGNUMode();
1713             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1714         }
1715         MatchEvent(TRUE);
1716     } else if (*appData.cmailGameName != NULLCHAR) {
1717         /* Set up cmail mode */
1718         ReloadCmailMsgEvent(TRUE);
1719     } else {
1720         /* Set up other modes */
1721         if (initialMode == AnalyzeFile) {
1722           if (*appData.loadGameFile == NULLCHAR) {
1723             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1724             return;
1725           }
1726         }
1727         if (*appData.loadGameFile != NULLCHAR) {
1728             (void) LoadGameFromFile(appData.loadGameFile,
1729                                     appData.loadGameIndex,
1730                                     appData.loadGameFile, TRUE);
1731         } else if (*appData.loadPositionFile != NULLCHAR) {
1732             (void) LoadPositionFromFile(appData.loadPositionFile,
1733                                         appData.loadPositionIndex,
1734                                         appData.loadPositionFile);
1735             /* [HGM] try to make self-starting even after FEN load */
1736             /* to allow automatic setup of fairy variants with wtm */
1737             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1738                 gameMode = BeginningOfGame;
1739                 setboardSpoiledMachineBlack = 1;
1740             }
1741             /* [HGM] loadPos: make that every new game uses the setup */
1742             /* from file as long as we do not switch variant          */
1743             if(!blackPlaysFirst) {
1744                 startedFromPositionFile = TRUE;
1745                 CopyBoard(filePosition, boards[0]);
1746                 CopyBoard(initialPosition, boards[0]);
1747             }
1748         } else if(*appData.fen != NULLCHAR) {
1749             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1750                 startedFromPositionFile = TRUE;
1751                 Reset(TRUE, TRUE);
1752             }
1753         }
1754         if (initialMode == AnalyzeMode) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1761             return;
1762           }
1763           AnalyzeModeEvent();
1764         } else if (initialMode == AnalyzeFile) {
1765           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1766           ShowThinkingEvent();
1767           AnalyzeFileEvent();
1768           AnalysisPeriodicEvent(1);
1769         } else if (initialMode == MachinePlaysWhite) {
1770           if (appData.noChessProgram) {
1771             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1772                               0, 2);
1773             return;
1774           }
1775           if (appData.icsActive) {
1776             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1777                               0, 2);
1778             return;
1779           }
1780           MachineWhiteEvent();
1781         } else if (initialMode == MachinePlaysBlack) {
1782           if (appData.noChessProgram) {
1783             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1784                               0, 2);
1785             return;
1786           }
1787           if (appData.icsActive) {
1788             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1789                               0, 2);
1790             return;
1791           }
1792           MachineBlackEvent();
1793         } else if (initialMode == TwoMachinesPlay) {
1794           if (appData.noChessProgram) {
1795             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1796                               0, 2);
1797             return;
1798           }
1799           if (appData.icsActive) {
1800             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1801                               0, 2);
1802             return;
1803           }
1804           TwoMachinesEvent();
1805         } else if (initialMode == EditGame) {
1806           EditGameEvent();
1807         } else if (initialMode == EditPosition) {
1808           EditPositionEvent();
1809         } else if (initialMode == Training) {
1810           if (*appData.loadGameFile == NULLCHAR) {
1811             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1812             return;
1813           }
1814           TrainingEvent();
1815         }
1816     }
1817 }
1818
1819 void
1820 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1821 {
1822     DisplayBook(current+1);
1823
1824     MoveHistorySet( movelist, first, last, current, pvInfoList );
1825
1826     EvalGraphSet( first, last, current, pvInfoList );
1827
1828     MakeEngineOutputTitle();
1829 }
1830
1831 /*
1832  * Establish will establish a contact to a remote host.port.
1833  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1834  *  used to talk to the host.
1835  * Returns 0 if okay, error code if not.
1836  */
1837 int
1838 establish ()
1839 {
1840     char buf[MSG_SIZ];
1841
1842     if (*appData.icsCommPort != NULLCHAR) {
1843         /* Talk to the host through a serial comm port */
1844         return OpenCommPort(appData.icsCommPort, &icsPR);
1845
1846     } else if (*appData.gateway != NULLCHAR) {
1847         if (*appData.remoteShell == NULLCHAR) {
1848             /* Use the rcmd protocol to run telnet program on a gateway host */
1849             snprintf(buf, sizeof(buf), "%s %s %s",
1850                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1851             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1852
1853         } else {
1854             /* Use the rsh program to run telnet program on a gateway host */
1855             if (*appData.remoteUser == NULLCHAR) {
1856                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1857                         appData.gateway, appData.telnetProgram,
1858                         appData.icsHost, appData.icsPort);
1859             } else {
1860                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1861                         appData.remoteShell, appData.gateway,
1862                         appData.remoteUser, appData.telnetProgram,
1863                         appData.icsHost, appData.icsPort);
1864             }
1865             return StartChildProcess(buf, "", &icsPR);
1866
1867         }
1868     } else if (appData.useTelnet) {
1869         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1870
1871     } else {
1872         /* TCP socket interface differs somewhat between
1873            Unix and NT; handle details in the front end.
1874            */
1875         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1876     }
1877 }
1878
1879 void
1880 EscapeExpand (char *p, char *q)
1881 {       // [HGM] initstring: routine to shape up string arguments
1882         while(*p++ = *q++) if(p[-1] == '\\')
1883             switch(*q++) {
1884                 case 'n': p[-1] = '\n'; break;
1885                 case 'r': p[-1] = '\r'; break;
1886                 case 't': p[-1] = '\t'; break;
1887                 case '\\': p[-1] = '\\'; break;
1888                 case 0: *p = 0; return;
1889                 default: p[-1] = q[-1]; break;
1890             }
1891 }
1892
1893 void
1894 show_bytes (FILE *fp, char *buf, int count)
1895 {
1896     while (count--) {
1897         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1898             fprintf(fp, "\\%03o", *buf & 0xff);
1899         } else {
1900             putc(*buf, fp);
1901         }
1902         buf++;
1903     }
1904     fflush(fp);
1905 }
1906
1907 /* Returns an errno value */
1908 int
1909 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1910 {
1911     char buf[8192], *p, *q, *buflim;
1912     int left, newcount, outcount;
1913
1914     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1915         *appData.gateway != NULLCHAR) {
1916         if (appData.debugMode) {
1917             fprintf(debugFP, ">ICS: ");
1918             show_bytes(debugFP, message, count);
1919             fprintf(debugFP, "\n");
1920         }
1921         return OutputToProcess(pr, message, count, outError);
1922     }
1923
1924     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1925     p = message;
1926     q = buf;
1927     left = count;
1928     newcount = 0;
1929     while (left) {
1930         if (q >= buflim) {
1931             if (appData.debugMode) {
1932                 fprintf(debugFP, ">ICS: ");
1933                 show_bytes(debugFP, buf, newcount);
1934                 fprintf(debugFP, "\n");
1935             }
1936             outcount = OutputToProcess(pr, buf, newcount, outError);
1937             if (outcount < newcount) return -1; /* to be sure */
1938             q = buf;
1939             newcount = 0;
1940         }
1941         if (*p == '\n') {
1942             *q++ = '\r';
1943             newcount++;
1944         } else if (((unsigned char) *p) == TN_IAC) {
1945             *q++ = (char) TN_IAC;
1946             newcount ++;
1947         }
1948         *q++ = *p++;
1949         newcount++;
1950         left--;
1951     }
1952     if (appData.debugMode) {
1953         fprintf(debugFP, ">ICS: ");
1954         show_bytes(debugFP, buf, newcount);
1955         fprintf(debugFP, "\n");
1956     }
1957     outcount = OutputToProcess(pr, buf, newcount, outError);
1958     if (outcount < newcount) return -1; /* to be sure */
1959     return count;
1960 }
1961
1962 void
1963 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1964 {
1965     int outError, outCount;
1966     static int gotEof = 0;
1967     static FILE *ini;
1968
1969     /* Pass data read from player on to ICS */
1970     if (count > 0) {
1971         gotEof = 0;
1972         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1973         if (outCount < count) {
1974             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975         }
1976         if(have_sent_ICS_logon == 2) {
1977           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1978             fprintf(ini, "%s", message);
1979             have_sent_ICS_logon = 3;
1980           } else
1981             have_sent_ICS_logon = 1;
1982         } else if(have_sent_ICS_logon == 3) {
1983             fprintf(ini, "%s", message);
1984             fclose(ini);
1985           have_sent_ICS_logon = 1;
1986         }
1987     } else if (count < 0) {
1988         RemoveInputSource(isr);
1989         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1990     } else if (gotEof++ > 0) {
1991         RemoveInputSource(isr);
1992         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1993     }
1994 }
1995
1996 void
1997 KeepAlive ()
1998 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1999     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2000     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2001     SendToICS("date\n");
2002     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2003 }
2004
2005 /* added routine for printf style output to ics */
2006 void
2007 ics_printf (char *format, ...)
2008 {
2009     char buffer[MSG_SIZ];
2010     va_list args;
2011
2012     va_start(args, format);
2013     vsnprintf(buffer, sizeof(buffer), format, args);
2014     buffer[sizeof(buffer)-1] = '\0';
2015     SendToICS(buffer);
2016     va_end(args);
2017 }
2018
2019 void
2020 SendToICS (char *s)
2021 {
2022     int count, outCount, outError;
2023
2024     if (icsPR == NoProc) return;
2025
2026     count = strlen(s);
2027     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2028     if (outCount < count) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 /* This is used for sending logon scripts to the ICS. Sending
2034    without a delay causes problems when using timestamp on ICC
2035    (at least on my machine). */
2036 void
2037 SendToICSDelayed (char *s, long msdelay)
2038 {
2039     int count, outCount, outError;
2040
2041     if (icsPR == NoProc) return;
2042
2043     count = strlen(s);
2044     if (appData.debugMode) {
2045         fprintf(debugFP, ">ICS: ");
2046         show_bytes(debugFP, s, count);
2047         fprintf(debugFP, "\n");
2048     }
2049     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2050                                       msdelay);
2051     if (outCount < count) {
2052         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2053     }
2054 }
2055
2056
2057 /* Remove all highlighting escape sequences in s
2058    Also deletes any suffix starting with '('
2059    */
2060 char *
2061 StripHighlightAndTitle (char *s)
2062 {
2063     static char retbuf[MSG_SIZ];
2064     char *p = retbuf;
2065
2066     while (*s != NULLCHAR) {
2067         while (*s == '\033') {
2068             while (*s != NULLCHAR && !isalpha(*s)) s++;
2069             if (*s != NULLCHAR) s++;
2070         }
2071         while (*s != NULLCHAR && *s != '\033') {
2072             if (*s == '(' || *s == '[') {
2073                 *p = NULLCHAR;
2074                 return retbuf;
2075             }
2076             *p++ = *s++;
2077         }
2078     }
2079     *p = NULLCHAR;
2080     return retbuf;
2081 }
2082
2083 /* Remove all highlighting escape sequences in s */
2084 char *
2085 StripHighlight (char *s)
2086 {
2087     static char retbuf[MSG_SIZ];
2088     char *p = retbuf;
2089
2090     while (*s != NULLCHAR) {
2091         while (*s == '\033') {
2092             while (*s != NULLCHAR && !isalpha(*s)) s++;
2093             if (*s != NULLCHAR) s++;
2094         }
2095         while (*s != NULLCHAR && *s != '\033') {
2096             *p++ = *s++;
2097         }
2098     }
2099     *p = NULLCHAR;
2100     return retbuf;
2101 }
2102
2103 char engineVariant[MSG_SIZ];
2104 char *variantNames[] = VARIANT_NAMES;
2105 char *
2106 VariantName (VariantClass v)
2107 {
2108     if(v == VariantUnknown || *engineVariant) return engineVariant;
2109     return variantNames[v];
2110 }
2111
2112
2113 /* Identify a variant from the strings the chess servers use or the
2114    PGN Variant tag names we use. */
2115 VariantClass
2116 StringToVariant (char *e)
2117 {
2118     char *p;
2119     int wnum = -1;
2120     VariantClass v = VariantNormal;
2121     int i, found = FALSE;
2122     char buf[MSG_SIZ], c;
2123     int len;
2124
2125     if (!e) return v;
2126
2127     /* [HGM] skip over optional board-size prefixes */
2128     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2129         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2130         while( *e++ != '_');
2131     }
2132
2133     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2134         v = VariantNormal;
2135         found = TRUE;
2136     } else
2137     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2138       if (p = StrCaseStr(e, variantNames[i])) {
2139         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2140         v = (VariantClass) i;
2141         found = TRUE;
2142         break;
2143       }
2144     }
2145
2146     if (!found) {
2147       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2148           || StrCaseStr(e, "wild/fr")
2149           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2150         v = VariantFischeRandom;
2151       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2152                  (i = 1, p = StrCaseStr(e, "w"))) {
2153         p += i;
2154         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2155         if (isdigit(*p)) {
2156           wnum = atoi(p);
2157         } else {
2158           wnum = -1;
2159         }
2160         switch (wnum) {
2161         case 0: /* FICS only, actually */
2162         case 1:
2163           /* Castling legal even if K starts on d-file */
2164           v = VariantWildCastle;
2165           break;
2166         case 2:
2167         case 3:
2168         case 4:
2169           /* Castling illegal even if K & R happen to start in
2170              normal positions. */
2171           v = VariantNoCastle;
2172           break;
2173         case 5:
2174         case 7:
2175         case 8:
2176         case 10:
2177         case 11:
2178         case 12:
2179         case 13:
2180         case 14:
2181         case 15:
2182         case 18:
2183         case 19:
2184           /* Castling legal iff K & R start in normal positions */
2185           v = VariantNormal;
2186           break;
2187         case 6:
2188         case 20:
2189         case 21:
2190           /* Special wilds for position setup; unclear what to do here */
2191           v = VariantLoadable;
2192           break;
2193         case 9:
2194           /* Bizarre ICC game */
2195           v = VariantTwoKings;
2196           break;
2197         case 16:
2198           v = VariantKriegspiel;
2199           break;
2200         case 17:
2201           v = VariantLosers;
2202           break;
2203         case 22:
2204           v = VariantFischeRandom;
2205           break;
2206         case 23:
2207           v = VariantCrazyhouse;
2208           break;
2209         case 24:
2210           v = VariantBughouse;
2211           break;
2212         case 25:
2213           v = Variant3Check;
2214           break;
2215         case 26:
2216           /* Not quite the same as FICS suicide! */
2217           v = VariantGiveaway;
2218           break;
2219         case 27:
2220           v = VariantAtomic;
2221           break;
2222         case 28:
2223           v = VariantShatranj;
2224           break;
2225
2226         /* Temporary names for future ICC types.  The name *will* change in
2227            the next xboard/WinBoard release after ICC defines it. */
2228         case 29:
2229           v = Variant29;
2230           break;
2231         case 30:
2232           v = Variant30;
2233           break;
2234         case 31:
2235           v = Variant31;
2236           break;
2237         case 32:
2238           v = Variant32;
2239           break;
2240         case 33:
2241           v = Variant33;
2242           break;
2243         case 34:
2244           v = Variant34;
2245           break;
2246         case 35:
2247           v = Variant35;
2248           break;
2249         case 36:
2250           v = Variant36;
2251           break;
2252         case 37:
2253           v = VariantShogi;
2254           break;
2255         case 38:
2256           v = VariantXiangqi;
2257           break;
2258         case 39:
2259           v = VariantCourier;
2260           break;
2261         case 40:
2262           v = VariantGothic;
2263           break;
2264         case 41:
2265           v = VariantCapablanca;
2266           break;
2267         case 42:
2268           v = VariantKnightmate;
2269           break;
2270         case 43:
2271           v = VariantFairy;
2272           break;
2273         case 44:
2274           v = VariantCylinder;
2275           break;
2276         case 45:
2277           v = VariantFalcon;
2278           break;
2279         case 46:
2280           v = VariantCapaRandom;
2281           break;
2282         case 47:
2283           v = VariantBerolina;
2284           break;
2285         case 48:
2286           v = VariantJanus;
2287           break;
2288         case 49:
2289           v = VariantSuper;
2290           break;
2291         case 50:
2292           v = VariantGreat;
2293           break;
2294         case -1:
2295           /* Found "wild" or "w" in the string but no number;
2296              must assume it's normal chess. */
2297           v = VariantNormal;
2298           break;
2299         default:
2300           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2301           if( (len >= MSG_SIZ) && appData.debugMode )
2302             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2303
2304           DisplayError(buf, 0);
2305           v = VariantUnknown;
2306           break;
2307         }
2308       }
2309     }
2310     if (appData.debugMode) {
2311       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2312               e, wnum, VariantName(v));
2313     }
2314     return v;
2315 }
2316
2317 static int leftover_start = 0, leftover_len = 0;
2318 char star_match[STAR_MATCH_N][MSG_SIZ];
2319
2320 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2321    advance *index beyond it, and set leftover_start to the new value of
2322    *index; else return FALSE.  If pattern contains the character '*', it
2323    matches any sequence of characters not containing '\r', '\n', or the
2324    character following the '*' (if any), and the matched sequence(s) are
2325    copied into star_match.
2326    */
2327 int
2328 looking_at ( char *buf, int *index, char *pattern)
2329 {
2330     char *bufp = &buf[*index], *patternp = pattern;
2331     int star_count = 0;
2332     char *matchp = star_match[0];
2333
2334     for (;;) {
2335         if (*patternp == NULLCHAR) {
2336             *index = leftover_start = bufp - buf;
2337             *matchp = NULLCHAR;
2338             return TRUE;
2339         }
2340         if (*bufp == NULLCHAR) return FALSE;
2341         if (*patternp == '*') {
2342             if (*bufp == *(patternp + 1)) {
2343                 *matchp = NULLCHAR;
2344                 matchp = star_match[++star_count];
2345                 patternp += 2;
2346                 bufp++;
2347                 continue;
2348             } else if (*bufp == '\n' || *bufp == '\r') {
2349                 patternp++;
2350                 if (*patternp == NULLCHAR)
2351                   continue;
2352                 else
2353                   return FALSE;
2354             } else {
2355                 *matchp++ = *bufp++;
2356                 continue;
2357             }
2358         }
2359         if (*patternp != *bufp) return FALSE;
2360         patternp++;
2361         bufp++;
2362     }
2363 }
2364
2365 void
2366 SendToPlayer (char *data, int length)
2367 {
2368     int error, outCount;
2369     outCount = OutputToProcess(NoProc, data, length, &error);
2370     if (outCount < length) {
2371         DisplayFatalError(_("Error writing to display"), error, 1);
2372     }
2373 }
2374
2375 void
2376 PackHolding (char packed[], char *holding)
2377 {
2378     char *p = holding;
2379     char *q = packed;
2380     int runlength = 0;
2381     int curr = 9999;
2382     do {
2383         if (*p == curr) {
2384             runlength++;
2385         } else {
2386             switch (runlength) {
2387               case 0:
2388                 break;
2389               case 1:
2390                 *q++ = curr;
2391                 break;
2392               case 2:
2393                 *q++ = curr;
2394                 *q++ = curr;
2395                 break;
2396               default:
2397                 sprintf(q, "%d", runlength);
2398                 while (*q) q++;
2399                 *q++ = curr;
2400                 break;
2401             }
2402             runlength = 1;
2403             curr = *p;
2404         }
2405     } while (*p++);
2406     *q = NULLCHAR;
2407 }
2408
2409 /* Telnet protocol requests from the front end */
2410 void
2411 TelnetRequest (unsigned char ddww, unsigned char option)
2412 {
2413     unsigned char msg[3];
2414     int outCount, outError;
2415
2416     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2417
2418     if (appData.debugMode) {
2419         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2420         switch (ddww) {
2421           case TN_DO:
2422             ddwwStr = "DO";
2423             break;
2424           case TN_DONT:
2425             ddwwStr = "DONT";
2426             break;
2427           case TN_WILL:
2428             ddwwStr = "WILL";
2429             break;
2430           case TN_WONT:
2431             ddwwStr = "WONT";
2432             break;
2433           default:
2434             ddwwStr = buf1;
2435             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2436             break;
2437         }
2438         switch (option) {
2439           case TN_ECHO:
2440             optionStr = "ECHO";
2441             break;
2442           default:
2443             optionStr = buf2;
2444             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2445             break;
2446         }
2447         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2448     }
2449     msg[0] = TN_IAC;
2450     msg[1] = ddww;
2451     msg[2] = option;
2452     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2453     if (outCount < 3) {
2454         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2455     }
2456 }
2457
2458 void
2459 DoEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DO, TN_ECHO);
2463 }
2464
2465 void
2466 DontEcho ()
2467 {
2468     if (!appData.icsActive) return;
2469     TelnetRequest(TN_DONT, TN_ECHO);
2470 }
2471
2472 void
2473 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2474 {
2475     /* put the holdings sent to us by the server on the board holdings area */
2476     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2477     char p;
2478     ChessSquare piece;
2479
2480     if(gameInfo.holdingsWidth < 2)  return;
2481     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2482         return; // prevent overwriting by pre-board holdings
2483
2484     if( (int)lowestPiece >= BlackPawn ) {
2485         holdingsColumn = 0;
2486         countsColumn = 1;
2487         holdingsStartRow = BOARD_HEIGHT-1;
2488         direction = -1;
2489     } else {
2490         holdingsColumn = BOARD_WIDTH-1;
2491         countsColumn = BOARD_WIDTH-2;
2492         holdingsStartRow = 0;
2493         direction = 1;
2494     }
2495
2496     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2497         board[i][holdingsColumn] = EmptySquare;
2498         board[i][countsColumn]   = (ChessSquare) 0;
2499     }
2500     while( (p=*holdings++) != NULLCHAR ) {
2501         piece = CharToPiece( ToUpper(p) );
2502         if(piece == EmptySquare) continue;
2503         /*j = (int) piece - (int) WhitePawn;*/
2504         j = PieceToNumber(piece);
2505         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2506         if(j < 0) continue;               /* should not happen */
2507         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2508         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2509         board[holdingsStartRow+j*direction][countsColumn]++;
2510     }
2511 }
2512
2513
2514 void
2515 VariantSwitch (Board board, VariantClass newVariant)
2516 {
2517    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2518    static Board oldBoard;
2519
2520    startedFromPositionFile = FALSE;
2521    if(gameInfo.variant == newVariant) return;
2522
2523    /* [HGM] This routine is called each time an assignment is made to
2524     * gameInfo.variant during a game, to make sure the board sizes
2525     * are set to match the new variant. If that means adding or deleting
2526     * holdings, we shift the playing board accordingly
2527     * This kludge is needed because in ICS observe mode, we get boards
2528     * of an ongoing game without knowing the variant, and learn about the
2529     * latter only later. This can be because of the move list we requested,
2530     * in which case the game history is refilled from the beginning anyway,
2531     * but also when receiving holdings of a crazyhouse game. In the latter
2532     * case we want to add those holdings to the already received position.
2533     */
2534
2535
2536    if (appData.debugMode) {
2537      fprintf(debugFP, "Switch board from %s to %s\n",
2538              VariantName(gameInfo.variant), VariantName(newVariant));
2539      setbuf(debugFP, NULL);
2540    }
2541    shuffleOpenings = 0;       /* [HGM] shuffle */
2542    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2543    switch(newVariant)
2544      {
2545      case VariantShogi:
2546        newWidth = 9;  newHeight = 9;
2547        gameInfo.holdingsSize = 7;
2548      case VariantBughouse:
2549      case VariantCrazyhouse:
2550        newHoldingsWidth = 2; break;
2551      case VariantGreat:
2552        newWidth = 10;
2553      case VariantSuper:
2554        newHoldingsWidth = 2;
2555        gameInfo.holdingsSize = 8;
2556        break;
2557      case VariantGothic:
2558      case VariantCapablanca:
2559      case VariantCapaRandom:
2560        newWidth = 10;
2561      default:
2562        newHoldingsWidth = gameInfo.holdingsSize = 0;
2563      };
2564
2565    if(newWidth  != gameInfo.boardWidth  ||
2566       newHeight != gameInfo.boardHeight ||
2567       newHoldingsWidth != gameInfo.holdingsWidth ) {
2568
2569      /* shift position to new playing area, if needed */
2570      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2571        for(i=0; i<BOARD_HEIGHT; i++)
2572          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2573            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574              board[i][j];
2575        for(i=0; i<newHeight; i++) {
2576          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2577          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2578        }
2579      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2580        for(i=0; i<BOARD_HEIGHT; i++)
2581          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2582            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583              board[i][j];
2584      }
2585      board[HOLDINGS_SET] = 0;
2586      gameInfo.boardWidth  = newWidth;
2587      gameInfo.boardHeight = newHeight;
2588      gameInfo.holdingsWidth = newHoldingsWidth;
2589      gameInfo.variant = newVariant;
2590      InitDrawingSizes(-2, 0);
2591    } else gameInfo.variant = newVariant;
2592    CopyBoard(oldBoard, board);   // remember correctly formatted board
2593      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2594    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2595 }
2596
2597 static int loggedOn = FALSE;
2598
2599 /*-- Game start info cache: --*/
2600 int gs_gamenum;
2601 char gs_kind[MSG_SIZ];
2602 static char player1Name[128] = "";
2603 static char player2Name[128] = "";
2604 static char cont_seq[] = "\n\\   ";
2605 static int player1Rating = -1;
2606 static int player2Rating = -1;
2607 /*----------------------------*/
2608
2609 ColorClass curColor = ColorNormal;
2610 int suppressKibitz = 0;
2611
2612 // [HGM] seekgraph
2613 Boolean soughtPending = FALSE;
2614 Boolean seekGraphUp;
2615 #define MAX_SEEK_ADS 200
2616 #define SQUARE 0x80
2617 char *seekAdList[MAX_SEEK_ADS];
2618 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2619 float tcList[MAX_SEEK_ADS];
2620 char colorList[MAX_SEEK_ADS];
2621 int nrOfSeekAds = 0;
2622 int minRating = 1010, maxRating = 2800;
2623 int hMargin = 10, vMargin = 20, h, w;
2624 extern int squareSize, lineGap;
2625
2626 void
2627 PlotSeekAd (int i)
2628 {
2629         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2630         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2631         if(r < minRating+100 && r >=0 ) r = minRating+100;
2632         if(r > maxRating) r = maxRating;
2633         if(tc < 1.f) tc = 1.f;
2634         if(tc > 95.f) tc = 95.f;
2635         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2636         y = ((double)r - minRating)/(maxRating - minRating)
2637             * (h-vMargin-squareSize/8-1) + vMargin;
2638         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2639         if(strstr(seekAdList[i], " u ")) color = 1;
2640         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2641            !strstr(seekAdList[i], "bullet") &&
2642            !strstr(seekAdList[i], "blitz") &&
2643            !strstr(seekAdList[i], "standard") ) color = 2;
2644         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2645         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2646 }
2647
2648 void
2649 PlotSingleSeekAd (int i)
2650 {
2651         PlotSeekAd(i);
2652 }
2653
2654 void
2655 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2656 {
2657         char buf[MSG_SIZ], *ext = "";
2658         VariantClass v = StringToVariant(type);
2659         if(strstr(type, "wild")) {
2660             ext = type + 4; // append wild number
2661             if(v == VariantFischeRandom) type = "chess960"; else
2662             if(v == VariantLoadable) type = "setup"; else
2663             type = VariantName(v);
2664         }
2665         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2666         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2667             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2668             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2669             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2670             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2671             seekNrList[nrOfSeekAds] = nr;
2672             zList[nrOfSeekAds] = 0;
2673             seekAdList[nrOfSeekAds++] = StrSave(buf);
2674             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2675         }
2676 }
2677
2678 void
2679 EraseSeekDot (int i)
2680 {
2681     int x = xList[i], y = yList[i], d=squareSize/4, k;
2682     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2683     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2684     // now replot every dot that overlapped
2685     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2686         int xx = xList[k], yy = yList[k];
2687         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2688             DrawSeekDot(xx, yy, colorList[k]);
2689     }
2690 }
2691
2692 void
2693 RemoveSeekAd (int nr)
2694 {
2695         int i;
2696         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2697             EraseSeekDot(i);
2698             if(seekAdList[i]) free(seekAdList[i]);
2699             seekAdList[i] = seekAdList[--nrOfSeekAds];
2700             seekNrList[i] = seekNrList[nrOfSeekAds];
2701             ratingList[i] = ratingList[nrOfSeekAds];
2702             colorList[i]  = colorList[nrOfSeekAds];
2703             tcList[i] = tcList[nrOfSeekAds];
2704             xList[i]  = xList[nrOfSeekAds];
2705             yList[i]  = yList[nrOfSeekAds];
2706             zList[i]  = zList[nrOfSeekAds];
2707             seekAdList[nrOfSeekAds] = NULL;
2708             break;
2709         }
2710 }
2711
2712 Boolean
2713 MatchSoughtLine (char *line)
2714 {
2715     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2716     int nr, base, inc, u=0; char dummy;
2717
2718     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2719        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2720        (u=1) &&
2721        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2723         // match: compact and save the line
2724         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2725         return TRUE;
2726     }
2727     return FALSE;
2728 }
2729
2730 int
2731 DrawSeekGraph ()
2732 {
2733     int i;
2734     if(!seekGraphUp) return FALSE;
2735     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2736     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2737
2738     DrawSeekBackground(0, 0, w, h);
2739     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2740     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2741     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2742         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2743         yy = h-1-yy;
2744         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2745         if(i%500 == 0) {
2746             char buf[MSG_SIZ];
2747             snprintf(buf, MSG_SIZ, "%d", i);
2748             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2749         }
2750     }
2751     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2752     for(i=1; i<100; i+=(i<10?1:5)) {
2753         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2754         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2755         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2756             char buf[MSG_SIZ];
2757             snprintf(buf, MSG_SIZ, "%d", i);
2758             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2759         }
2760     }
2761     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2762     return TRUE;
2763 }
2764
2765 int
2766 SeekGraphClick (ClickType click, int x, int y, int moving)
2767 {
2768     static int lastDown = 0, displayed = 0, lastSecond;
2769     if(y < 0) return FALSE;
2770     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2771         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2772         if(!seekGraphUp) return FALSE;
2773         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2774         DrawPosition(TRUE, NULL);
2775         return TRUE;
2776     }
2777     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2778         if(click == Release || moving) return FALSE;
2779         nrOfSeekAds = 0;
2780         soughtPending = TRUE;
2781         SendToICS(ics_prefix);
2782         SendToICS("sought\n"); // should this be "sought all"?
2783     } else { // issue challenge based on clicked ad
2784         int dist = 10000; int i, closest = 0, second = 0;
2785         for(i=0; i<nrOfSeekAds; i++) {
2786             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2787             if(d < dist) { dist = d; closest = i; }
2788             second += (d - zList[i] < 120); // count in-range ads
2789             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2790         }
2791         if(dist < 120) {
2792             char buf[MSG_SIZ];
2793             second = (second > 1);
2794             if(displayed != closest || second != lastSecond) {
2795                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2796                 lastSecond = second; displayed = closest;
2797             }
2798             if(click == Press) {
2799                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2800                 lastDown = closest;
2801                 return TRUE;
2802             } // on press 'hit', only show info
2803             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2804             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2805             SendToICS(ics_prefix);
2806             SendToICS(buf);
2807             return TRUE; // let incoming board of started game pop down the graph
2808         } else if(click == Release) { // release 'miss' is ignored
2809             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2810             if(moving == 2) { // right up-click
2811                 nrOfSeekAds = 0; // refresh graph
2812                 soughtPending = TRUE;
2813                 SendToICS(ics_prefix);
2814                 SendToICS("sought\n"); // should this be "sought all"?
2815             }
2816             return TRUE;
2817         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2818         // press miss or release hit 'pop down' seek graph
2819         seekGraphUp = FALSE;
2820         DrawPosition(TRUE, NULL);
2821     }
2822     return TRUE;
2823 }
2824
2825 void
2826 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2827 {
2828 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2829 #define STARTED_NONE 0
2830 #define STARTED_MOVES 1
2831 #define STARTED_BOARD 2
2832 #define STARTED_OBSERVE 3
2833 #define STARTED_HOLDINGS 4
2834 #define STARTED_CHATTER 5
2835 #define STARTED_COMMENT 6
2836 #define STARTED_MOVES_NOHIDE 7
2837
2838     static int started = STARTED_NONE;
2839     static char parse[20000];
2840     static int parse_pos = 0;
2841     static char buf[BUF_SIZE + 1];
2842     static int firstTime = TRUE, intfSet = FALSE;
2843     static ColorClass prevColor = ColorNormal;
2844     static int savingComment = FALSE;
2845     static int cmatch = 0; // continuation sequence match
2846     char *bp;
2847     char str[MSG_SIZ];
2848     int i, oldi;
2849     int buf_len;
2850     int next_out;
2851     int tkind;
2852     int backup;    /* [DM] For zippy color lines */
2853     char *p;
2854     char talker[MSG_SIZ]; // [HGM] chat
2855     int channel, collective=0;
2856
2857     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2858
2859     if (appData.debugMode) {
2860       if (!error) {
2861         fprintf(debugFP, "<ICS: ");
2862         show_bytes(debugFP, data, count);
2863         fprintf(debugFP, "\n");
2864       }
2865     }
2866
2867     if (appData.debugMode) { int f = forwardMostMove;
2868         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2869                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2870                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2871     }
2872     if (count > 0) {
2873         /* If last read ended with a partial line that we couldn't parse,
2874            prepend it to the new read and try again. */
2875         if (leftover_len > 0) {
2876             for (i=0; i<leftover_len; i++)
2877               buf[i] = buf[leftover_start + i];
2878         }
2879
2880     /* copy new characters into the buffer */
2881     bp = buf + leftover_len;
2882     buf_len=leftover_len;
2883     for (i=0; i<count; i++)
2884     {
2885         // ignore these
2886         if (data[i] == '\r')
2887             continue;
2888
2889         // join lines split by ICS?
2890         if (!appData.noJoin)
2891         {
2892             /*
2893                 Joining just consists of finding matches against the
2894                 continuation sequence, and discarding that sequence
2895                 if found instead of copying it.  So, until a match
2896                 fails, there's nothing to do since it might be the
2897                 complete sequence, and thus, something we don't want
2898                 copied.
2899             */
2900             if (data[i] == cont_seq[cmatch])
2901             {
2902                 cmatch++;
2903                 if (cmatch == strlen(cont_seq))
2904                 {
2905                     cmatch = 0; // complete match.  just reset the counter
2906
2907                     /*
2908                         it's possible for the ICS to not include the space
2909                         at the end of the last word, making our [correct]
2910                         join operation fuse two separate words.  the server
2911                         does this when the space occurs at the width setting.
2912                     */
2913                     if (!buf_len || buf[buf_len-1] != ' ')
2914                     {
2915                         *bp++ = ' ';
2916                         buf_len++;
2917                     }
2918                 }
2919                 continue;
2920             }
2921             else if (cmatch)
2922             {
2923                 /*
2924                     match failed, so we have to copy what matched before
2925                     falling through and copying this character.  In reality,
2926                     this will only ever be just the newline character, but
2927                     it doesn't hurt to be precise.
2928                 */
2929                 strncpy(bp, cont_seq, cmatch);
2930                 bp += cmatch;
2931                 buf_len += cmatch;
2932                 cmatch = 0;
2933             }
2934         }
2935
2936         // copy this char
2937         *bp++ = data[i];
2938         buf_len++;
2939     }
2940
2941         buf[buf_len] = NULLCHAR;
2942 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2943         next_out = 0;
2944         leftover_start = 0;
2945
2946         i = 0;
2947         while (i < buf_len) {
2948             /* Deal with part of the TELNET option negotiation
2949                protocol.  We refuse to do anything beyond the
2950                defaults, except that we allow the WILL ECHO option,
2951                which ICS uses to turn off password echoing when we are
2952                directly connected to it.  We reject this option
2953                if localLineEditing mode is on (always on in xboard)
2954                and we are talking to port 23, which might be a real
2955                telnet server that will try to keep WILL ECHO on permanently.
2956              */
2957             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2958                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2959                 unsigned char option;
2960                 oldi = i;
2961                 switch ((unsigned char) buf[++i]) {
2962                   case TN_WILL:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<WILL ");
2965                     switch (option = (unsigned char) buf[++i]) {
2966                       case TN_ECHO:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "ECHO ");
2969                         /* Reply only if this is a change, according
2970                            to the protocol rules. */
2971                         if (remoteEchoOption) break;
2972                         if (appData.localLineEditing &&
2973                             atoi(appData.icsPort) == TN_PORT) {
2974                             TelnetRequest(TN_DONT, TN_ECHO);
2975                         } else {
2976                             EchoOff();
2977                             TelnetRequest(TN_DO, TN_ECHO);
2978                             remoteEchoOption = TRUE;
2979                         }
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", option);
2984                         /* Whatever this is, we don't want it. */
2985                         TelnetRequest(TN_DONT, option);
2986                         break;
2987                     }
2988                     break;
2989                   case TN_WONT:
2990                     if (appData.debugMode)
2991                       fprintf(debugFP, "\n<WONT ");
2992                     switch (option = (unsigned char) buf[++i]) {
2993                       case TN_ECHO:
2994                         if (appData.debugMode)
2995                           fprintf(debugFP, "ECHO ");
2996                         /* Reply only if this is a change, according
2997                            to the protocol rules. */
2998                         if (!remoteEchoOption) break;
2999                         EchoOn();
3000                         TelnetRequest(TN_DONT, TN_ECHO);
3001                         remoteEchoOption = FALSE;
3002                         break;
3003                       default:
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", (unsigned char) option);
3006                         /* Whatever this is, it must already be turned
3007                            off, because we never agree to turn on
3008                            anything non-default, so according to the
3009                            protocol rules, we don't reply. */
3010                         break;
3011                     }
3012                     break;
3013                   case TN_DO:
3014                     if (appData.debugMode)
3015                       fprintf(debugFP, "\n<DO ");
3016                     switch (option = (unsigned char) buf[++i]) {
3017                       default:
3018                         /* Whatever this is, we refuse to do it. */
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         TelnetRequest(TN_WONT, option);
3022                         break;
3023                     }
3024                     break;
3025                   case TN_DONT:
3026                     if (appData.debugMode)
3027                       fprintf(debugFP, "\n<DONT ");
3028                     switch (option = (unsigned char) buf[++i]) {
3029                       default:
3030                         if (appData.debugMode)
3031                           fprintf(debugFP, "%d ", option);
3032                         /* Whatever this is, we are already not doing
3033                            it, because we never agree to do anything
3034                            non-default, so according to the protocol
3035                            rules, we don't reply. */
3036                         break;
3037                     }
3038                     break;
3039                   case TN_IAC:
3040                     if (appData.debugMode)
3041                       fprintf(debugFP, "\n<IAC ");
3042                     /* Doubled IAC; pass it through */
3043                     i--;
3044                     break;
3045                   default:
3046                     if (appData.debugMode)
3047                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3048                     /* Drop all other telnet commands on the floor */
3049                     break;
3050                 }
3051                 if (oldi > next_out)
3052                   SendToPlayer(&buf[next_out], oldi - next_out);
3053                 if (++i > next_out)
3054                   next_out = i;
3055                 continue;
3056             }
3057
3058             /* OK, this at least will *usually* work */
3059             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3060                 loggedOn = TRUE;
3061             }
3062
3063             if (loggedOn && !intfSet) {
3064                 if (ics_type == ICS_ICC) {
3065                   snprintf(str, MSG_SIZ,
3066                           "/set-quietly interface %s\n/set-quietly style 12\n",
3067                           programVersion);
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3070                 } else if (ics_type == ICS_CHESSNET) {
3071                   snprintf(str, MSG_SIZ, "/style 12\n");
3072                 } else {
3073                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3074                   strcat(str, programVersion);
3075                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3076                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3078 #ifdef WIN32
3079                   strcat(str, "$iset nohighlight 1\n");
3080 #endif
3081                   strcat(str, "$iset lock 1\n$style 12\n");
3082                 }
3083                 SendToICS(str);
3084                 NotifyFrontendLogin();
3085                 intfSet = TRUE;
3086             }
3087
3088             if (started == STARTED_COMMENT) {
3089                 /* Accumulate characters in comment */
3090                 parse[parse_pos++] = buf[i];
3091                 if (buf[i] == '\n') {
3092                     parse[parse_pos] = NULLCHAR;
3093                     if(chattingPartner>=0) {
3094                         char mess[MSG_SIZ];
3095                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3096                         OutputChatMessage(chattingPartner, mess);
3097                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3098                             int p;
3099                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3100                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3101                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3102                                 OutputChatMessage(p, mess);
3103                                 break;
3104                             }
3105                         }
3106                         chattingPartner = -1;
3107                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3108                         collective = 0;
3109                     } else
3110                     if(!suppressKibitz) // [HGM] kibitz
3111                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3112                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3113                         int nrDigit = 0, nrAlph = 0, j;
3114                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3115                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3116                         parse[parse_pos] = NULLCHAR;
3117                         // try to be smart: if it does not look like search info, it should go to
3118                         // ICS interaction window after all, not to engine-output window.
3119                         for(j=0; j<parse_pos; j++) { // count letters and digits
3120                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3121                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3122                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3123                         }
3124                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3125                             int depth=0; float score;
3126                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3127                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3128                                 pvInfoList[forwardMostMove-1].depth = depth;
3129                                 pvInfoList[forwardMostMove-1].score = 100*score;
3130                             }
3131                             OutputKibitz(suppressKibitz, parse);
3132                         } else {
3133                             char tmp[MSG_SIZ];
3134                             if(gameMode == IcsObserving) // restore original ICS messages
3135                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3136                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3137                             else
3138                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3140                             SendToPlayer(tmp, strlen(tmp));
3141                         }
3142                         next_out = i+1; // [HGM] suppress printing in ICS window
3143                     }
3144                     started = STARTED_NONE;
3145                 } else {
3146                     /* Don't match patterns against characters in comment */
3147                     i++;
3148                     continue;
3149                 }
3150             }
3151             if (started == STARTED_CHATTER) {
3152                 if (buf[i] != '\n') {
3153                     /* Don't match patterns against characters in chatter */
3154                     i++;
3155                     continue;
3156                 }
3157                 started = STARTED_NONE;
3158                 if(suppressKibitz) next_out = i+1;
3159             }
3160
3161             /* Kludge to deal with rcmd protocol */
3162             if (firstTime && looking_at(buf, &i, "\001*")) {
3163                 DisplayFatalError(&buf[1], 0, 1);
3164                 continue;
3165             } else {
3166                 firstTime = FALSE;
3167             }
3168
3169             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3170                 ics_type = ICS_ICC;
3171                 ics_prefix = "/";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3177                 ics_type = ICS_FICS;
3178                 ics_prefix = "$";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3184                 ics_type = ICS_CHESSNET;
3185                 ics_prefix = "/";
3186                 if (appData.debugMode)
3187                   fprintf(debugFP, "ics_type %d\n", ics_type);
3188                 continue;
3189             }
3190
3191             if (!loggedOn &&
3192                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3193                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3194                  looking_at(buf, &i, "will be \"*\""))) {
3195               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3196               continue;
3197             }
3198
3199             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3200               char buf[MSG_SIZ];
3201               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3202               DisplayIcsInteractionTitle(buf);
3203               have_set_title = TRUE;
3204             }
3205
3206             /* skip finger notes */
3207             if (started == STARTED_NONE &&
3208                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3209                  (buf[i] == '1' && buf[i+1] == '0')) &&
3210                 buf[i+2] == ':' && buf[i+3] == ' ') {
3211               started = STARTED_CHATTER;
3212               i += 3;
3213               continue;
3214             }
3215
3216             oldi = i;
3217             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3218             if(appData.seekGraph) {
3219                 if(soughtPending && MatchSoughtLine(buf+i)) {
3220                     i = strstr(buf+i, "rated") - buf;
3221                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                     next_out = leftover_start = i;
3223                     started = STARTED_CHATTER;
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3228                         && looking_at(buf, &i, "* ads displayed")) {
3229                     soughtPending = FALSE;
3230                     seekGraphUp = TRUE;
3231                     DrawSeekGraph();
3232                     continue;
3233                 }
3234                 if(appData.autoRefresh) {
3235                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3236                         int s = (ics_type == ICS_ICC); // ICC format differs
3237                         if(seekGraphUp)
3238                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3239                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3240                         looking_at(buf, &i, "*% "); // eat prompt
3241                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i; // suppress
3244                         continue;
3245                     }
3246                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3247                         char *p = star_match[0];
3248                         while(*p) {
3249                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3250                             while(*p && *p++ != ' '); // next
3251                         }
3252                         looking_at(buf, &i, "*% "); // eat prompt
3253                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254                         next_out = i;
3255                         continue;
3256                     }
3257                 }
3258             }
3259
3260             /* skip formula vars */
3261             if (started == STARTED_NONE &&
3262                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3263               started = STARTED_CHATTER;
3264               i += 3;
3265               continue;
3266             }
3267
3268             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3269             if (appData.autoKibitz && started == STARTED_NONE &&
3270                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3271                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3272                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3273                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3274                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3275                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3276                         suppressKibitz = TRUE;
3277                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278                         next_out = i;
3279                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3280                                 && (gameMode == IcsPlayingWhite)) ||
3281                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3282                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3283                             started = STARTED_CHATTER; // own kibitz we simply discard
3284                         else {
3285                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3286                             parse_pos = 0; parse[0] = NULLCHAR;
3287                             savingComment = TRUE;
3288                             suppressKibitz = gameMode != IcsObserving ? 2 :
3289                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3290                         }
3291                         continue;
3292                 } else
3293                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3294                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3295                          && atoi(star_match[0])) {
3296                     // suppress the acknowledgements of our own autoKibitz
3297                     char *p;
3298                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3300                     SendToPlayer(star_match[0], strlen(star_match[0]));
3301                     if(looking_at(buf, &i, "*% ")) // eat prompt
3302                         suppressKibitz = FALSE;
3303                     next_out = i;
3304                     continue;
3305                 }
3306             } // [HGM] kibitz: end of patch
3307
3308             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3309
3310             // [HGM] chat: intercept tells by users for which we have an open chat window
3311             channel = -1;
3312             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3313                                            looking_at(buf, &i, "* whispers:") ||
3314                                            looking_at(buf, &i, "* kibitzes:") ||
3315                                            looking_at(buf, &i, "* shouts:") ||
3316                                            looking_at(buf, &i, "* c-shouts:") ||
3317                                            looking_at(buf, &i, "--> * ") ||
3318                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3322                 int p;
3323                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3324                 chattingPartner = -1; collective = 0;
3325
3326                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3327                 for(p=0; p<MAX_CHAT; p++) {
3328                     collective = 1;
3329                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3330                     talker[0] = '['; strcat(talker, "] ");
3331                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3332                     chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("kibitzes", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3344                 for(p=0; p<MAX_CHAT; p++) {
3345                     collective = 1;
3346                     if(!strcmp("whispers", chatPartner[p])) {
3347                         talker[0] = '['; strcat(talker, "] ");
3348                         chattingPartner = p; break;
3349                     }
3350                 } else
3351                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3352                   if(buf[i-8] == '-' && buf[i-3] == 't')
3353                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3354                     collective = 1;
3355                     if(!strcmp("c-shouts", chatPartner[p])) {
3356                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3357                         chattingPartner = p; break;
3358                     }
3359                   }
3360                   if(chattingPartner < 0)
3361                   for(p=0; p<MAX_CHAT; p++) {
3362                     collective = 1;
3363                     if(!strcmp("shouts", chatPartner[p])) {
3364                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3365                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3366                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3367                         chattingPartner = p; break;
3368                     }
3369                   }
3370                 }
3371                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3372                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3373                     talker[0] = 0;
3374                     Colorize(ColorTell, FALSE);
3375                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3376                     collective |= 2;
3377                     chattingPartner = p; break;
3378                 }
3379                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3380                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3381                     started = STARTED_COMMENT;
3382                     parse_pos = 0; parse[0] = NULLCHAR;
3383                     savingComment = 3 + chattingPartner; // counts as TRUE
3384                     if(collective == 3) i = oldi; else {
3385                         suppressKibitz = TRUE;
3386                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3387                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3388                         continue;
3389                     }
3390                 }
3391             } // [HGM] chat: end of patch
3392
3393           backup = i;
3394             if (appData.zippyTalk || appData.zippyPlay) {
3395                 /* [DM] Backup address for color zippy lines */
3396 #if ZIPPY
3397                if (loggedOn == TRUE)
3398                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3399                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3400                        ;
3401 #endif
3402             } // [DM] 'else { ' deleted
3403                 if (
3404                     /* Regular tells and says */
3405                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3406                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3407                     looking_at(buf, &i, "* says: ") ||
3408                     /* Don't color "message" or "messages" output */
3409                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3410                     looking_at(buf, &i, "*. * at *:*: ") ||
3411                     looking_at(buf, &i, "--* (*:*): ") ||
3412                     /* Message notifications (same color as tells) */
3413                     looking_at(buf, &i, "* has left a message ") ||
3414                     looking_at(buf, &i, "* just sent you a message:\n") ||
3415                     /* Whispers and kibitzes */
3416                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3417                     looking_at(buf, &i, "* kibitzes: ") ||
3418                     /* Channel tells */
3419                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3420
3421                   if (tkind == 1 && strchr(star_match[0], ':')) {
3422                       /* Avoid "tells you:" spoofs in channels */
3423                      tkind = 3;
3424                   }
3425                   if (star_match[0][0] == NULLCHAR ||
3426                       strchr(star_match[0], ' ') ||
3427                       (tkind == 3 && strchr(star_match[1], ' '))) {
3428                     /* Reject bogus matches */
3429                     i = oldi;
3430                   } else {
3431                     if (appData.colorize) {
3432                       if (oldi > next_out) {
3433                         SendToPlayer(&buf[next_out], oldi - next_out);
3434                         next_out = oldi;
3435                       }
3436                       switch (tkind) {
3437                       case 1:
3438                         Colorize(ColorTell, FALSE);
3439                         curColor = ColorTell;
3440                         break;
3441                       case 2:
3442                         Colorize(ColorKibitz, FALSE);
3443                         curColor = ColorKibitz;
3444                         break;
3445                       case 3:
3446                         p = strrchr(star_match[1], '(');
3447                         if (p == NULL) {
3448                           p = star_match[1];
3449                         } else {
3450                           p++;
3451                         }
3452                         if (atoi(p) == 1) {
3453                           Colorize(ColorChannel1, FALSE);
3454                           curColor = ColorChannel1;
3455                         } else {
3456                           Colorize(ColorChannel, FALSE);
3457                           curColor = ColorChannel;
3458                         }
3459                         break;
3460                       case 5:
3461                         curColor = ColorNormal;
3462                         break;
3463                       }
3464                     }
3465                     if (started == STARTED_NONE && appData.autoComment &&
3466                         (gameMode == IcsObserving ||
3467                          gameMode == IcsPlayingWhite ||
3468                          gameMode == IcsPlayingBlack)) {
3469                       parse_pos = i - oldi;
3470                       memcpy(parse, &buf[oldi], parse_pos);
3471                       parse[parse_pos] = NULLCHAR;
3472                       started = STARTED_COMMENT;
3473                       savingComment = TRUE;
3474                     } else if(collective != 3) {
3475                       started = STARTED_CHATTER;
3476                       savingComment = FALSE;
3477                     }
3478                     loggedOn = TRUE;
3479                     continue;
3480                   }
3481                 }
3482
3483                 if (looking_at(buf, &i, "* s-shouts: ") ||
3484                     looking_at(buf, &i, "* c-shouts: ")) {
3485                     if (appData.colorize) {
3486                         if (oldi > next_out) {
3487                             SendToPlayer(&buf[next_out], oldi - next_out);
3488                             next_out = oldi;
3489                         }
3490                         Colorize(ColorSShout, FALSE);
3491                         curColor = ColorSShout;
3492                     }
3493                     loggedOn = TRUE;
3494                     started = STARTED_CHATTER;
3495                     continue;
3496                 }
3497
3498                 if (looking_at(buf, &i, "--->")) {
3499                     loggedOn = TRUE;
3500                     continue;
3501                 }
3502
3503                 if (looking_at(buf, &i, "* shouts: ") ||
3504                     looking_at(buf, &i, "--> ")) {
3505                     if (appData.colorize) {
3506                         if (oldi > next_out) {
3507                             SendToPlayer(&buf[next_out], oldi - next_out);
3508                             next_out = oldi;
3509                         }
3510                         Colorize(ColorShout, FALSE);
3511                         curColor = ColorShout;
3512                     }
3513                     loggedOn = TRUE;
3514                     started = STARTED_CHATTER;
3515                     continue;
3516                 }
3517
3518                 if (looking_at( buf, &i, "Challenge:")) {
3519                     if (appData.colorize) {
3520                         if (oldi > next_out) {
3521                             SendToPlayer(&buf[next_out], oldi - next_out);
3522                             next_out = oldi;
3523                         }
3524                         Colorize(ColorChallenge, FALSE);
3525                         curColor = ColorChallenge;
3526                     }
3527                     loggedOn = TRUE;
3528                     continue;
3529                 }
3530
3531                 if (looking_at(buf, &i, "* offers you") ||
3532                     looking_at(buf, &i, "* offers to be") ||
3533                     looking_at(buf, &i, "* would like to") ||
3534                     looking_at(buf, &i, "* requests to") ||
3535                     looking_at(buf, &i, "Your opponent offers") ||
3536                     looking_at(buf, &i, "Your opponent requests")) {
3537
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorRequest, FALSE);
3544                         curColor = ColorRequest;
3545                     }
3546                     continue;
3547                 }
3548
3549                 if (looking_at(buf, &i, "* (*) seeking")) {
3550                     if (appData.colorize) {
3551                         if (oldi > next_out) {
3552                             SendToPlayer(&buf[next_out], oldi - next_out);
3553                             next_out = oldi;
3554                         }
3555                         Colorize(ColorSeek, FALSE);
3556                         curColor = ColorSeek;
3557                     }
3558                     continue;
3559             }
3560
3561           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3562
3563             if (looking_at(buf, &i, "\\   ")) {
3564                 if (prevColor != ColorNormal) {
3565                     if (oldi > next_out) {
3566                         SendToPlayer(&buf[next_out], oldi - next_out);
3567                         next_out = oldi;
3568                     }
3569                     Colorize(prevColor, TRUE);
3570                     curColor = prevColor;
3571                 }
3572                 if (savingComment) {
3573                     parse_pos = i - oldi;
3574                     memcpy(parse, &buf[oldi], parse_pos);
3575                     parse[parse_pos] = NULLCHAR;
3576                     started = STARTED_COMMENT;
3577                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3578                         chattingPartner = savingComment - 3; // kludge to remember the box
3579                 } else {
3580                     started = STARTED_CHATTER;
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i, "Black Strength :") ||
3586                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3587                 looking_at(buf, &i, "<10>") ||
3588                 looking_at(buf, &i, "#@#")) {
3589                 /* Wrong board style */
3590                 loggedOn = TRUE;
3591                 SendToICS(ics_prefix);
3592                 SendToICS("set style 12\n");
3593                 SendToICS(ics_prefix);
3594                 SendToICS("refresh\n");
3595                 continue;
3596             }
3597
3598             if (looking_at(buf, &i, "login:")) {
3599               if (!have_sent_ICS_logon) {
3600                 if(ICSInitScript())
3601                   have_sent_ICS_logon = 1;
3602                 else // no init script was found
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3604               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3605                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3606               }
3607                 continue;
3608             }
3609
3610             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3611                 (looking_at(buf, &i, "\n<12> ") ||
3612                  looking_at(buf, &i, "<12> "))) {
3613                 loggedOn = TRUE;
3614                 if (oldi > next_out) {
3615                     SendToPlayer(&buf[next_out], oldi - next_out);
3616                 }
3617                 next_out = i;
3618                 started = STARTED_BOARD;
3619                 parse_pos = 0;
3620                 continue;
3621             }
3622
3623             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3624                 looking_at(buf, &i, "<b1> ")) {
3625                 if (oldi > next_out) {
3626                     SendToPlayer(&buf[next_out], oldi - next_out);
3627                 }
3628                 next_out = i;
3629                 started = STARTED_HOLDINGS;
3630                 parse_pos = 0;
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3635                 loggedOn = TRUE;
3636                 /* Header for a move list -- first line */
3637
3638                 switch (ics_getting_history) {
3639                   case H_FALSE:
3640                     switch (gameMode) {
3641                       case IcsIdle:
3642                       case BeginningOfGame:
3643                         /* User typed "moves" or "oldmoves" while we
3644                            were idle.  Pretend we asked for these
3645                            moves and soak them up so user can step
3646                            through them and/or save them.
3647                            */
3648                         Reset(FALSE, TRUE);
3649                         gameMode = IcsObserving;
3650                         ModeHighlight();
3651                         ics_gamenum = -1;
3652                         ics_getting_history = H_GOT_UNREQ_HEADER;
3653                         break;
3654                       case EditGame: /*?*/
3655                       case EditPosition: /*?*/
3656                         /* Should above feature work in these modes too? */
3657                         /* For now it doesn't */
3658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3659                         break;
3660                       default:
3661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3662                         break;
3663                     }
3664                     break;
3665                   case H_REQUESTED:
3666                     /* Is this the right one? */
3667                     if (gameInfo.white && gameInfo.black &&
3668                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3669                         strcmp(gameInfo.black, star_match[2]) == 0) {
3670                         /* All is well */
3671                         ics_getting_history = H_GOT_REQ_HEADER;
3672                     }
3673                     break;
3674                   case H_GOT_REQ_HEADER:
3675                   case H_GOT_UNREQ_HEADER:
3676                   case H_GOT_UNWANTED_HEADER:
3677                   case H_GETTING_MOVES:
3678                     /* Should not happen */
3679                     DisplayError(_("Error gathering move list: two headers"), 0);
3680                     ics_getting_history = H_FALSE;
3681                     break;
3682                 }
3683
3684                 /* Save player ratings into gameInfo if needed */
3685                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3686                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3687                     (gameInfo.whiteRating == -1 ||
3688                      gameInfo.blackRating == -1)) {
3689
3690                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3691                     gameInfo.blackRating = string_to_rating(star_match[3]);
3692                     if (appData.debugMode)
3693                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3694                               gameInfo.whiteRating, gameInfo.blackRating);
3695                 }
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i,
3700               "* * match, initial time: * minute*, increment: * second")) {
3701                 /* Header for a move list -- second line */
3702                 /* Initial board will follow if this is a wild game */
3703                 if (gameInfo.event != NULL) free(gameInfo.event);
3704                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3705                 gameInfo.event = StrSave(str);
3706                 /* [HGM] we switched variant. Translate boards if needed. */
3707                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3708                 continue;
3709             }
3710
3711             if (looking_at(buf, &i, "Move  ")) {
3712                 /* Beginning of a move list */
3713                 switch (ics_getting_history) {
3714                   case H_FALSE:
3715                     /* Normally should not happen */
3716                     /* Maybe user hit reset while we were parsing */
3717                     break;
3718                   case H_REQUESTED:
3719                     /* Happens if we are ignoring a move list that is not
3720                      * the one we just requested.  Common if the user
3721                      * tries to observe two games without turning off
3722                      * getMoveList */
3723                     break;
3724                   case H_GETTING_MOVES:
3725                     /* Should not happen */
3726                     DisplayError(_("Error gathering move list: nested"), 0);
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                   case H_GOT_REQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES;
3732                     parse_pos = 0;
3733                     if (oldi > next_out) {
3734                         SendToPlayer(&buf[next_out], oldi - next_out);
3735                     }
3736                     break;
3737                   case H_GOT_UNREQ_HEADER:
3738                     ics_getting_history = H_GETTING_MOVES;
3739                     started = STARTED_MOVES_NOHIDE;
3740                     parse_pos = 0;
3741                     break;
3742                   case H_GOT_UNWANTED_HEADER:
3743                     ics_getting_history = H_FALSE;
3744                     break;
3745                 }
3746                 continue;
3747             }
3748
3749             if (looking_at(buf, &i, "% ") ||
3750                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3751                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3752                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3753                     soughtPending = FALSE;
3754                     seekGraphUp = TRUE;
3755                     DrawSeekGraph();
3756                 }
3757                 if(suppressKibitz) next_out = i;
3758                 savingComment = FALSE;
3759                 suppressKibitz = 0;
3760                 switch (started) {
3761                   case STARTED_MOVES:
3762                   case STARTED_MOVES_NOHIDE:
3763                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3764                     parse[parse_pos + i - oldi] = NULLCHAR;
3765                     ParseGameHistory(parse);
3766 #if ZIPPY
3767                     if (appData.zippyPlay && first.initDone) {
3768                         FeedMovesToProgram(&first, forwardMostMove);
3769                         if (gameMode == IcsPlayingWhite) {
3770                             if (WhiteOnMove(forwardMostMove)) {
3771                                 if (first.sendTime) {
3772                                   if (first.useColors) {
3773                                     SendToProgram("black\n", &first);
3774                                   }
3775                                   SendTimeRemaining(&first, TRUE);
3776                                 }
3777                                 if (first.useColors) {
3778                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3779                                 }
3780                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3781                                 first.maybeThinking = TRUE;
3782                             } else {
3783                                 if (first.usePlayother) {
3784                                   if (first.sendTime) {
3785                                     SendTimeRemaining(&first, TRUE);
3786                                   }
3787                                   SendToProgram("playother\n", &first);
3788                                   firstMove = FALSE;
3789                                 } else {
3790                                   firstMove = TRUE;
3791                                 }
3792                             }
3793                         } else if (gameMode == IcsPlayingBlack) {
3794                             if (!WhiteOnMove(forwardMostMove)) {
3795                                 if (first.sendTime) {
3796                                   if (first.useColors) {
3797                                     SendToProgram("white\n", &first);
3798                                   }
3799                                   SendTimeRemaining(&first, FALSE);
3800                                 }
3801                                 if (first.useColors) {
3802                                   SendToProgram("black\n", &first);
3803                                 }
3804                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3805                                 first.maybeThinking = TRUE;
3806                             } else {
3807                                 if (first.usePlayother) {
3808                                   if (first.sendTime) {
3809                                     SendTimeRemaining(&first, FALSE);
3810                                   }
3811                                   SendToProgram("playother\n", &first);
3812                                   firstMove = FALSE;
3813                                 } else {
3814                                   firstMove = TRUE;
3815                                 }
3816                             }
3817                         }
3818                     }
3819 #endif
3820                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3821                         /* Moves came from oldmoves or moves command
3822                            while we weren't doing anything else.
3823                            */
3824                         currentMove = forwardMostMove;
3825                         ClearHighlights();/*!!could figure this out*/
3826                         flipView = appData.flipView;
3827                         DrawPosition(TRUE, boards[currentMove]);
3828                         DisplayBothClocks();
3829                         snprintf(str, MSG_SIZ, "%s %s %s",
3830                                 gameInfo.white, _("vs."),  gameInfo.black);
3831                         DisplayTitle(str);
3832                         gameMode = IcsIdle;
3833                     } else {
3834                         /* Moves were history of an active game */
3835                         if (gameInfo.resultDetails != NULL) {
3836                             free(gameInfo.resultDetails);
3837                             gameInfo.resultDetails = NULL;
3838                         }
3839                     }
3840                     HistorySet(parseList, backwardMostMove,
3841                                forwardMostMove, currentMove-1);
3842                     DisplayMove(currentMove - 1);
3843                     if (started == STARTED_MOVES) next_out = i;
3844                     started = STARTED_NONE;
3845                     ics_getting_history = H_FALSE;
3846                     break;
3847
3848                   case STARTED_OBSERVE:
3849                     started = STARTED_NONE;
3850                     SendToICS(ics_prefix);
3851                     SendToICS("refresh\n");
3852                     break;
3853
3854                   default:
3855                     break;
3856                 }
3857                 if(bookHit) { // [HGM] book: simulate book reply
3858                     static char bookMove[MSG_SIZ]; // a bit generous?
3859
3860                     programStats.nodes = programStats.depth = programStats.time =
3861                     programStats.score = programStats.got_only_move = 0;
3862                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3863
3864                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3865                     strcat(bookMove, bookHit);
3866                     HandleMachineMove(bookMove, &first);
3867                 }
3868                 continue;
3869             }
3870
3871             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3872                  started == STARTED_HOLDINGS ||
3873                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3874                 /* Accumulate characters in move list or board */
3875                 parse[parse_pos++] = buf[i];
3876             }
3877
3878             /* Start of game messages.  Mostly we detect start of game
3879                when the first board image arrives.  On some versions
3880                of the ICS, though, we need to do a "refresh" after starting
3881                to observe in order to get the current board right away. */
3882             if (looking_at(buf, &i, "Adding game * to observation list")) {
3883                 started = STARTED_OBSERVE;
3884                 continue;
3885             }
3886
3887             /* Handle auto-observe */
3888             if (appData.autoObserve &&
3889                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3890                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3891                 char *player;
3892                 /* Choose the player that was highlighted, if any. */
3893                 if (star_match[0][0] == '\033' ||
3894                     star_match[1][0] != '\033') {
3895                     player = star_match[0];
3896                 } else {
3897                     player = star_match[2];
3898                 }
3899                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3900                         ics_prefix, StripHighlightAndTitle(player));
3901                 SendToICS(str);
3902
3903                 /* Save ratings from notify string */
3904                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3905                 player1Rating = string_to_rating(star_match[1]);
3906                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3907                 player2Rating = string_to_rating(star_match[3]);
3908
3909                 if (appData.debugMode)
3910                   fprintf(debugFP,
3911                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3912                           player1Name, player1Rating,
3913                           player2Name, player2Rating);
3914
3915                 continue;
3916             }
3917
3918             /* Deal with automatic examine mode after a game,
3919                and with IcsObserving -> IcsExamining transition */
3920             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3921                 looking_at(buf, &i, "has made you an examiner of game *")) {
3922
3923                 int gamenum = atoi(star_match[0]);
3924                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3925                     gamenum == ics_gamenum) {
3926                     /* We were already playing or observing this game;
3927                        no need to refetch history */
3928                     gameMode = IcsExamining;
3929                     if (pausing) {
3930                         pauseExamForwardMostMove = forwardMostMove;
3931                     } else if (currentMove < forwardMostMove) {
3932                         ForwardInner(forwardMostMove);
3933                     }
3934                 } else {
3935                     /* I don't think this case really can happen */
3936                     SendToICS(ics_prefix);
3937                     SendToICS("refresh\n");
3938                 }
3939                 continue;
3940             }
3941
3942             /* Error messages */
3943 //          if (ics_user_moved) {
3944             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3945                 if (looking_at(buf, &i, "Illegal move") ||
3946                     looking_at(buf, &i, "Not a legal move") ||
3947                     looking_at(buf, &i, "Your king is in check") ||
3948                     looking_at(buf, &i, "It isn't your turn") ||
3949                     looking_at(buf, &i, "It is not your move")) {
3950                     /* Illegal move */
3951                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3952                         currentMove = forwardMostMove-1;
3953                         DisplayMove(currentMove - 1); /* before DMError */
3954                         DrawPosition(FALSE, boards[currentMove]);
3955                         SwitchClocks(forwardMostMove-1); // [HGM] race
3956                         DisplayBothClocks();
3957                     }
3958                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3959                     ics_user_moved = 0;
3960                     continue;
3961                 }
3962             }
3963
3964             if (looking_at(buf, &i, "still have time") ||
3965                 looking_at(buf, &i, "not out of time") ||
3966                 looking_at(buf, &i, "either player is out of time") ||
3967                 looking_at(buf, &i, "has timeseal; checking")) {
3968                 /* We must have called his flag a little too soon */
3969                 whiteFlag = blackFlag = FALSE;
3970                 continue;
3971             }
3972
3973             if (looking_at(buf, &i, "added * seconds to") ||
3974                 looking_at(buf, &i, "seconds were added to")) {
3975                 /* Update the clocks */
3976                 SendToICS(ics_prefix);
3977                 SendToICS("refresh\n");
3978                 continue;
3979             }
3980
3981             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3982                 ics_clock_paused = TRUE;
3983                 StopClocks();
3984                 continue;
3985             }
3986
3987             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3988                 ics_clock_paused = FALSE;
3989                 StartClocks();
3990                 continue;
3991             }
3992
3993             /* Grab player ratings from the Creating: message.
3994                Note we have to check for the special case when
3995                the ICS inserts things like [white] or [black]. */
3996             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3997                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3998                 /* star_matches:
3999                    0    player 1 name (not necessarily white)
4000                    1    player 1 rating
4001                    2    empty, white, or black (IGNORED)
4002                    3    player 2 name (not necessarily black)
4003                    4    player 2 rating
4004
4005                    The names/ratings are sorted out when the game
4006                    actually starts (below).
4007                 */
4008                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4009                 player1Rating = string_to_rating(star_match[1]);
4010                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4011                 player2Rating = string_to_rating(star_match[4]);
4012
4013                 if (appData.debugMode)
4014                   fprintf(debugFP,
4015                           "Ratings from 'Creating:' %s %d, %s %d\n",
4016                           player1Name, player1Rating,
4017                           player2Name, player2Rating);
4018
4019                 continue;
4020             }
4021
4022             /* Improved generic start/end-of-game messages */
4023             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4024                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4025                 /* If tkind == 0: */
4026                 /* star_match[0] is the game number */
4027                 /*           [1] is the white player's name */
4028                 /*           [2] is the black player's name */
4029                 /* For end-of-game: */
4030                 /*           [3] is the reason for the game end */
4031                 /*           [4] is a PGN end game-token, preceded by " " */
4032                 /* For start-of-game: */
4033                 /*           [3] begins with "Creating" or "Continuing" */
4034                 /*           [4] is " *" or empty (don't care). */
4035                 int gamenum = atoi(star_match[0]);
4036                 char *whitename, *blackname, *why, *endtoken;
4037                 ChessMove endtype = EndOfFile;
4038
4039                 if (tkind == 0) {
4040                   whitename = star_match[1];
4041                   blackname = star_match[2];
4042                   why = star_match[3];
4043                   endtoken = star_match[4];
4044                 } else {
4045                   whitename = star_match[1];
4046                   blackname = star_match[3];
4047                   why = star_match[5];
4048                   endtoken = star_match[6];
4049                 }
4050
4051                 /* Game start messages */
4052                 if (strncmp(why, "Creating ", 9) == 0 ||
4053                     strncmp(why, "Continuing ", 11) == 0) {
4054                     gs_gamenum = gamenum;
4055                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4056                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4057                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4058 #if ZIPPY
4059                     if (appData.zippyPlay) {
4060                         ZippyGameStart(whitename, blackname);
4061                     }
4062 #endif /*ZIPPY*/
4063                     partnerBoardValid = FALSE; // [HGM] bughouse
4064                     continue;
4065                 }
4066
4067                 /* Game end messages */
4068                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4069                     ics_gamenum != gamenum) {
4070                     continue;
4071                 }
4072                 while (endtoken[0] == ' ') endtoken++;
4073                 switch (endtoken[0]) {
4074                   case '*':
4075                   default:
4076                     endtype = GameUnfinished;
4077                     break;
4078                   case '0':
4079                     endtype = BlackWins;
4080                     break;
4081                   case '1':
4082                     if (endtoken[1] == '/')
4083                       endtype = GameIsDrawn;
4084                     else
4085                       endtype = WhiteWins;
4086                     break;
4087                 }
4088                 GameEnds(endtype, why, GE_ICS);
4089 #if ZIPPY
4090                 if (appData.zippyPlay && first.initDone) {
4091                     ZippyGameEnd(endtype, why);
4092                     if (first.pr == NoProc) {
4093                       /* Start the next process early so that we'll
4094                          be ready for the next challenge */
4095                       StartChessProgram(&first);
4096                     }
4097                     /* Send "new" early, in case this command takes
4098                        a long time to finish, so that we'll be ready
4099                        for the next challenge. */
4100                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4101                     Reset(TRUE, TRUE);
4102                 }
4103 #endif /*ZIPPY*/
4104                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4105                 continue;
4106             }
4107
4108             if (looking_at(buf, &i, "Removing game * from observation") ||
4109                 looking_at(buf, &i, "no longer observing game *") ||
4110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4111                 if (gameMode == IcsObserving &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       /* icsEngineAnalyze */
4115                       if (appData.icsEngineAnalyze) {
4116                             ExitAnalyzeMode();
4117                             ModeHighlight();
4118                       }
4119                       StopClocks();
4120                       gameMode = IcsIdle;
4121                       ics_gamenum = -1;
4122                       ics_user_moved = FALSE;
4123                   }
4124                 continue;
4125             }
4126
4127             if (looking_at(buf, &i, "no longer examining game *")) {
4128                 if (gameMode == IcsExamining &&
4129                     atoi(star_match[0]) == ics_gamenum)
4130                   {
4131                       gameMode = IcsIdle;
4132                       ics_gamenum = -1;
4133                       ics_user_moved = FALSE;
4134                   }
4135                 continue;
4136             }
4137
4138             /* Advance leftover_start past any newlines we find,
4139                so only partial lines can get reparsed */
4140             if (looking_at(buf, &i, "\n")) {
4141                 prevColor = curColor;
4142                 if (curColor != ColorNormal) {
4143                     if (oldi > next_out) {
4144                         SendToPlayer(&buf[next_out], oldi - next_out);
4145                         next_out = oldi;
4146                     }
4147                     Colorize(ColorNormal, FALSE);
4148                     curColor = ColorNormal;
4149                 }
4150                 if (started == STARTED_BOARD) {
4151                     started = STARTED_NONE;
4152                     parse[parse_pos] = NULLCHAR;
4153                     ParseBoard12(parse);
4154                     ics_user_moved = 0;
4155
4156                     /* Send premove here */
4157                     if (appData.premove) {
4158                       char str[MSG_SIZ];
4159                       if (currentMove == 0 &&
4160                           gameMode == IcsPlayingWhite &&
4161                           appData.premoveWhite) {
4162                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4163                         if (appData.debugMode)
4164                           fprintf(debugFP, "Sending premove:\n");
4165                         SendToICS(str);
4166                       } else if (currentMove == 1 &&
4167                                  gameMode == IcsPlayingBlack &&
4168                                  appData.premoveBlack) {
4169                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4170                         if (appData.debugMode)
4171                           fprintf(debugFP, "Sending premove:\n");
4172                         SendToICS(str);
4173                       } else if (gotPremove) {
4174                         int oldFMM = forwardMostMove;
4175                         gotPremove = 0;
4176                         ClearPremoveHighlights();
4177                         if (appData.debugMode)
4178                           fprintf(debugFP, "Sending premove:\n");
4179                           UserMoveEvent(premoveFromX, premoveFromY,
4180                                         premoveToX, premoveToY,
4181                                         premovePromoChar);
4182                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4183                           if(moveList[oldFMM-1][1] != '@')
4184                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4185                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                           else // (drop)
4187                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4188                         }
4189                       }
4190                     }
4191
4192                     /* Usually suppress following prompt */
4193                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4194                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4195                         if (looking_at(buf, &i, "*% ")) {
4196                             savingComment = FALSE;
4197                             suppressKibitz = 0;
4198                         }
4199                     }
4200                     next_out = i;
4201                 } else if (started == STARTED_HOLDINGS) {
4202                     int gamenum;
4203                     char new_piece[MSG_SIZ];
4204                     started = STARTED_NONE;
4205                     parse[parse_pos] = NULLCHAR;
4206                     if (appData.debugMode)
4207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4208                                                         parse, currentMove);
4209                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4210                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4211                         if (gameInfo.variant == VariantNormal) {
4212                           /* [HGM] We seem to switch variant during a game!
4213                            * Presumably no holdings were displayed, so we have
4214                            * to move the position two files to the right to
4215                            * create room for them!
4216                            */
4217                           VariantClass newVariant;
4218                           switch(gameInfo.boardWidth) { // base guess on board width
4219                                 case 9:  newVariant = VariantShogi; break;
4220                                 case 10: newVariant = VariantGreat; break;
4221                                 default: newVariant = VariantCrazyhouse; break;
4222                           }
4223                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224                           /* Get a move list just to see the header, which
4225                              will tell us whether this is really bug or zh */
4226                           if (ics_getting_history == H_FALSE) {
4227                             ics_getting_history = H_REQUESTED;
4228                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229                             SendToICS(str);
4230                           }
4231                         }
4232                         new_piece[0] = NULLCHAR;
4233                         sscanf(parse, "game %d white [%s black [%s <- %s",
4234                                &gamenum, white_holding, black_holding,
4235                                new_piece);
4236                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4237                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4238                         /* [HGM] copy holdings to board holdings area */
4239                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4240                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4241                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4242 #if ZIPPY
4243                         if (appData.zippyPlay && first.initDone) {
4244                             ZippyHoldings(white_holding, black_holding,
4245                                           new_piece);
4246                         }
4247 #endif /*ZIPPY*/
4248                         if (tinyLayout || smallLayout) {
4249                             char wh[16], bh[16];
4250                             PackHolding(wh, white_holding);
4251                             PackHolding(bh, black_holding);
4252                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4253                                     gameInfo.white, gameInfo.black);
4254                         } else {
4255                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4256                                     gameInfo.white, white_holding, _("vs."),
4257                                     gameInfo.black, black_holding);
4258                         }
4259                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4260                         DrawPosition(FALSE, boards[currentMove]);
4261                         DisplayTitle(str);
4262                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4263                         sscanf(parse, "game %d white [%s black [%s <- %s",
4264                                &gamenum, white_holding, black_holding,
4265                                new_piece);
4266                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4267                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4268                         /* [HGM] copy holdings to partner-board holdings area */
4269                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4270                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4271                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4272                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4274                       }
4275                     }
4276                     /* Suppress following prompt */
4277                     if (looking_at(buf, &i, "*% ")) {
4278                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4279                         savingComment = FALSE;
4280                         suppressKibitz = 0;
4281                     }
4282                     next_out = i;
4283                 }
4284                 continue;
4285             }
4286
4287             i++;                /* skip unparsed character and loop back */
4288         }
4289
4290         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4291 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4292 //          SendToPlayer(&buf[next_out], i - next_out);
4293             started != STARTED_HOLDINGS && leftover_start > next_out) {
4294             SendToPlayer(&buf[next_out], leftover_start - next_out);
4295             next_out = i;
4296         }
4297
4298         leftover_len = buf_len - leftover_start;
4299         /* if buffer ends with something we couldn't parse,
4300            reparse it after appending the next read */
4301
4302     } else if (count == 0) {
4303         RemoveInputSource(isr);
4304         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4305     } else {
4306         DisplayFatalError(_("Error reading from ICS"), error, 1);
4307     }
4308 }
4309
4310
4311 /* Board style 12 looks like this:
4312
4313    <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
4314
4315  * The "<12> " is stripped before it gets to this routine.  The two
4316  * trailing 0's (flip state and clock ticking) are later addition, and
4317  * some chess servers may not have them, or may have only the first.
4318  * Additional trailing fields may be added in the future.
4319  */
4320
4321 #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"
4322
4323 #define RELATION_OBSERVING_PLAYED    0
4324 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4325 #define RELATION_PLAYING_MYMOVE      1
4326 #define RELATION_PLAYING_NOTMYMOVE  -1
4327 #define RELATION_EXAMINING           2
4328 #define RELATION_ISOLATED_BOARD     -3
4329 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4330
4331 void
4332 ParseBoard12 (char *string)
4333 {
4334 #if ZIPPY
4335     int i, takeback;
4336     char *bookHit = NULL; // [HGM] book
4337 #endif
4338     GameMode newGameMode;
4339     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4340     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4341     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4342     char to_play, board_chars[200];
4343     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4344     char black[32], white[32];
4345     Board board;
4346     int prevMove = currentMove;
4347     int ticking = 2;
4348     ChessMove moveType;
4349     int fromX, fromY, toX, toY;
4350     char promoChar;
4351     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4352     Boolean weird = FALSE, reqFlag = FALSE;
4353
4354     fromX = fromY = toX = toY = -1;
4355
4356     newGame = FALSE;
4357
4358     if (appData.debugMode)
4359       fprintf(debugFP, "Parsing board: %s\n", string);
4360
4361     move_str[0] = NULLCHAR;
4362     elapsed_time[0] = NULLCHAR;
4363     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4364         int  i = 0, j;
4365         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4366             if(string[i] == ' ') { ranks++; files = 0; }
4367             else files++;
4368             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4369             i++;
4370         }
4371         for(j = 0; j <i; j++) board_chars[j] = string[j];
4372         board_chars[i] = '\0';
4373         string += i + 1;
4374     }
4375     n = sscanf(string, PATTERN, &to_play, &double_push,
4376                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4377                &gamenum, white, black, &relation, &basetime, &increment,
4378                &white_stren, &black_stren, &white_time, &black_time,
4379                &moveNum, str, elapsed_time, move_str, &ics_flip,
4380                &ticking);
4381
4382     if (n < 21) {
4383         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4384         DisplayError(str, 0);
4385         return;
4386     }
4387
4388     /* Convert the move number to internal form */
4389     moveNum = (moveNum - 1) * 2;
4390     if (to_play == 'B') moveNum++;
4391     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4392       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4393                         0, 1);
4394       return;
4395     }
4396
4397     switch (relation) {
4398       case RELATION_OBSERVING_PLAYED:
4399       case RELATION_OBSERVING_STATIC:
4400         if (gamenum == -1) {
4401             /* Old ICC buglet */
4402             relation = RELATION_OBSERVING_STATIC;
4403         }
4404         newGameMode = IcsObserving;
4405         break;
4406       case RELATION_PLAYING_MYMOVE:
4407       case RELATION_PLAYING_NOTMYMOVE:
4408         newGameMode =
4409           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4410             IcsPlayingWhite : IcsPlayingBlack;
4411         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4412         break;
4413       case RELATION_EXAMINING:
4414         newGameMode = IcsExamining;
4415         break;
4416       case RELATION_ISOLATED_BOARD:
4417       default:
4418         /* Just display this board.  If user was doing something else,
4419            we will forget about it until the next board comes. */
4420         newGameMode = IcsIdle;
4421         break;
4422       case RELATION_STARTING_POSITION:
4423         newGameMode = gameMode;
4424         break;
4425     }
4426
4427     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4428         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4429          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4430       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4431       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4432       static int lastBgGame = -1;
4433       char *toSqr;
4434       for (k = 0; k < ranks; k++) {
4435         for (j = 0; j < files; j++)
4436           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437         if(gameInfo.holdingsWidth > 1) {
4438              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440         }
4441       }
4442       CopyBoard(partnerBoard, board);
4443       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4444         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4445         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4446       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4447       if(toSqr = strchr(str, '-')) {
4448         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4449         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4451       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4452       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4453       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4454       if(twoBoards) {
4455           DisplayWhiteClock(white_time*fac, to_play == 'W');
4456           DisplayBlackClock(black_time*fac, to_play != 'W');
4457           activePartner = to_play;
4458           if(gamenum != lastBgGame) {
4459               char buf[MSG_SIZ];
4460               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4461               DisplayTitle(buf);
4462           }
4463           lastBgGame = gamenum;
4464           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4465                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4466       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4467                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4468       if(!twoBoards) DisplayMessage(partnerStatus, "");
4469         partnerBoardValid = TRUE;
4470       return;
4471     }
4472
4473     if(appData.dualBoard && appData.bgObserve) {
4474         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4475             SendToICS(ics_prefix), SendToICS("pobserve\n");
4476         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4477             char buf[MSG_SIZ];
4478             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4479             SendToICS(buf);
4480         }
4481     }
4482
4483     /* Modify behavior for initial board display on move listing
4484        of wild games.
4485        */
4486     switch (ics_getting_history) {
4487       case H_FALSE:
4488       case H_REQUESTED:
4489         break;
4490       case H_GOT_REQ_HEADER:
4491       case H_GOT_UNREQ_HEADER:
4492         /* This is the initial position of the current game */
4493         gamenum = ics_gamenum;
4494         moveNum = 0;            /* old ICS bug workaround */
4495         if (to_play == 'B') {
4496           startedFromSetupPosition = TRUE;
4497           blackPlaysFirst = TRUE;
4498           moveNum = 1;
4499           if (forwardMostMove == 0) forwardMostMove = 1;
4500           if (backwardMostMove == 0) backwardMostMove = 1;
4501           if (currentMove == 0) currentMove = 1;
4502         }
4503         newGameMode = gameMode;
4504         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4505         break;
4506       case H_GOT_UNWANTED_HEADER:
4507         /* This is an initial board that we don't want */
4508         return;
4509       case H_GETTING_MOVES:
4510         /* Should not happen */
4511         DisplayError(_("Error gathering move list: extra board"), 0);
4512         ics_getting_history = H_FALSE;
4513         return;
4514     }
4515
4516    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4517                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4518                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4519      /* [HGM] We seem to have switched variant unexpectedly
4520       * Try to guess new variant from board size
4521       */
4522           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4523           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4524           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4525           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4526           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4527           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4528           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4529           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4530           /* Get a move list just to see the header, which
4531              will tell us whether this is really bug or zh */
4532           if (ics_getting_history == H_FALSE) {
4533             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4534             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4535             SendToICS(str);
4536           }
4537     }
4538
4539     /* Take action if this is the first board of a new game, or of a
4540        different game than is currently being displayed.  */
4541     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4542         relation == RELATION_ISOLATED_BOARD) {
4543
4544         /* Forget the old game and get the history (if any) of the new one */
4545         if (gameMode != BeginningOfGame) {
4546           Reset(TRUE, TRUE);
4547         }
4548         newGame = TRUE;
4549         if (appData.autoRaiseBoard) BoardToTop();
4550         prevMove = -3;
4551         if (gamenum == -1) {
4552             newGameMode = IcsIdle;
4553         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4554                    appData.getMoveList && !reqFlag) {
4555             /* Need to get game history */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560
4561         /* Initially flip the board to have black on the bottom if playing
4562            black or if the ICS flip flag is set, but let the user change
4563            it with the Flip View button. */
4564         flipView = appData.autoFlipView ?
4565           (newGameMode == IcsPlayingBlack) || ics_flip :
4566           appData.flipView;
4567
4568         /* Done with values from previous mode; copy in new ones */
4569         gameMode = newGameMode;
4570         ModeHighlight();
4571         ics_gamenum = gamenum;
4572         if (gamenum == gs_gamenum) {
4573             int klen = strlen(gs_kind);
4574             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4575             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4576             gameInfo.event = StrSave(str);
4577         } else {
4578             gameInfo.event = StrSave("ICS game");
4579         }
4580         gameInfo.site = StrSave(appData.icsHost);
4581         gameInfo.date = PGNDate();
4582         gameInfo.round = StrSave("-");
4583         gameInfo.white = StrSave(white);
4584         gameInfo.black = StrSave(black);
4585         timeControl = basetime * 60 * 1000;
4586         timeControl_2 = 0;
4587         timeIncrement = increment * 1000;
4588         movesPerSession = 0;
4589         gameInfo.timeControl = TimeControlTagValue();
4590         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4591   if (appData.debugMode) {
4592     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4593     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4594     setbuf(debugFP, NULL);
4595   }
4596
4597         gameInfo.outOfBook = NULL;
4598
4599         /* Do we have the ratings? */
4600         if (strcmp(player1Name, white) == 0 &&
4601             strcmp(player2Name, black) == 0) {
4602             if (appData.debugMode)
4603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4604                       player1Rating, player2Rating);
4605             gameInfo.whiteRating = player1Rating;
4606             gameInfo.blackRating = player2Rating;
4607         } else if (strcmp(player2Name, white) == 0 &&
4608                    strcmp(player1Name, black) == 0) {
4609             if (appData.debugMode)
4610               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4611                       player2Rating, player1Rating);
4612             gameInfo.whiteRating = player2Rating;
4613             gameInfo.blackRating = player1Rating;
4614         }
4615         player1Name[0] = player2Name[0] = NULLCHAR;
4616
4617         /* Silence shouts if requested */
4618         if (appData.quietPlay &&
4619             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4620             SendToICS(ics_prefix);
4621             SendToICS("set shout 0\n");
4622         }
4623     }
4624
4625     /* Deal with midgame name changes */
4626     if (!newGame) {
4627         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4628             if (gameInfo.white) free(gameInfo.white);
4629             gameInfo.white = StrSave(white);
4630         }
4631         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4632             if (gameInfo.black) free(gameInfo.black);
4633             gameInfo.black = StrSave(black);
4634         }
4635     }
4636
4637     /* Throw away game result if anything actually changes in examine mode */
4638     if (gameMode == IcsExamining && !newGame) {
4639         gameInfo.result = GameUnfinished;
4640         if (gameInfo.resultDetails != NULL) {
4641             free(gameInfo.resultDetails);
4642             gameInfo.resultDetails = NULL;
4643         }
4644     }
4645
4646     /* In pausing && IcsExamining mode, we ignore boards coming
4647        in if they are in a different variation than we are. */
4648     if (pauseExamInvalid) return;
4649     if (pausing && gameMode == IcsExamining) {
4650         if (moveNum <= pauseExamForwardMostMove) {
4651             pauseExamInvalid = TRUE;
4652             forwardMostMove = pauseExamForwardMostMove;
4653             return;
4654         }
4655     }
4656
4657   if (appData.debugMode) {
4658     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4659   }
4660     /* Parse the board */
4661     for (k = 0; k < ranks; k++) {
4662       for (j = 0; j < files; j++)
4663         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4664       if(gameInfo.holdingsWidth > 1) {
4665            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4666            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4667       }
4668     }
4669     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4670       board[5][BOARD_RGHT+1] = WhiteAngel;
4671       board[6][BOARD_RGHT+1] = WhiteMarshall;
4672       board[1][0] = BlackMarshall;
4673       board[2][0] = BlackAngel;
4674       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4675     }
4676     CopyBoard(boards[moveNum], board);
4677     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4678     if (moveNum == 0) {
4679         startedFromSetupPosition =
4680           !CompareBoards(board, initialPosition);
4681         if(startedFromSetupPosition)
4682             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4683     }
4684
4685     /* [HGM] Set castling rights. Take the outermost Rooks,
4686        to make it also work for FRC opening positions. Note that board12
4687        is really defective for later FRC positions, as it has no way to
4688        indicate which Rook can castle if they are on the same side of King.
4689        For the initial position we grant rights to the outermost Rooks,
4690        and remember thos rights, and we then copy them on positions
4691        later in an FRC game. This means WB might not recognize castlings with
4692        Rooks that have moved back to their original position as illegal,
4693        but in ICS mode that is not its job anyway.
4694     */
4695     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4696     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4697
4698         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4699             if(board[0][i] == WhiteRook) j = i;
4700         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4702             if(board[0][i] == WhiteRook) j = i;
4703         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710
4711         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4712         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4715         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4716             if(board[BOARD_HEIGHT-1][k] == bKing)
4717                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4718         if(gameInfo.variant == VariantTwoKings) {
4719             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4720             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4721             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4722         }
4723     } else { int r;
4724         r = boards[moveNum][CASTLING][0] = initialRights[0];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4726         r = boards[moveNum][CASTLING][1] = initialRights[1];
4727         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4728         r = boards[moveNum][CASTLING][3] = initialRights[3];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4730         r = boards[moveNum][CASTLING][4] = initialRights[4];
4731         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4732         /* wildcastle kludge: always assume King has rights */
4733         r = boards[moveNum][CASTLING][2] = initialRights[2];
4734         r = boards[moveNum][CASTLING][5] = initialRights[5];
4735     }
4736     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4737     boards[moveNum][EP_STATUS] = EP_NONE;
4738     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4739     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4740     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4741
4742
4743     if (ics_getting_history == H_GOT_REQ_HEADER ||
4744         ics_getting_history == H_GOT_UNREQ_HEADER) {
4745         /* This was an initial position from a move list, not
4746            the current position */
4747         return;
4748     }
4749
4750     /* Update currentMove and known move number limits */
4751     newMove = newGame || moveNum > forwardMostMove;
4752
4753     if (newGame) {
4754         forwardMostMove = backwardMostMove = currentMove = moveNum;
4755         if (gameMode == IcsExamining && moveNum == 0) {
4756           /* Workaround for ICS limitation: we are not told the wild
4757              type when starting to examine a game.  But if we ask for
4758              the move list, the move list header will tell us */
4759             ics_getting_history = H_REQUESTED;
4760             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4761             SendToICS(str);
4762         }
4763     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4764                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4765 #if ZIPPY
4766         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4767         /* [HGM] applied this also to an engine that is silently watching        */
4768         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4769             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4770             gameInfo.variant == currentlyInitializedVariant) {
4771           takeback = forwardMostMove - moveNum;
4772           for (i = 0; i < takeback; i++) {
4773             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4774             SendToProgram("undo\n", &first);
4775           }
4776         }
4777 #endif
4778
4779         forwardMostMove = moveNum;
4780         if (!pausing || currentMove > forwardMostMove)
4781           currentMove = forwardMostMove;
4782     } else {
4783         /* New part of history that is not contiguous with old part */
4784         if (pausing && gameMode == IcsExamining) {
4785             pauseExamInvalid = TRUE;
4786             forwardMostMove = pauseExamForwardMostMove;
4787             return;
4788         }
4789         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4790 #if ZIPPY
4791             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4792                 // [HGM] when we will receive the move list we now request, it will be
4793                 // fed to the engine from the first move on. So if the engine is not
4794                 // in the initial position now, bring it there.
4795                 InitChessProgram(&first, 0);
4796             }
4797 #endif
4798             ics_getting_history = H_REQUESTED;
4799             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4800             SendToICS(str);
4801         }
4802         forwardMostMove = backwardMostMove = currentMove = moveNum;
4803     }
4804
4805     /* Update the clocks */
4806     if (strchr(elapsed_time, '.')) {
4807       /* Time is in ms */
4808       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4809       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4810     } else {
4811       /* Time is in seconds */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4814     }
4815
4816
4817 #if ZIPPY
4818     if (appData.zippyPlay && newGame &&
4819         gameMode != IcsObserving && gameMode != IcsIdle &&
4820         gameMode != IcsExamining)
4821       ZippyFirstBoard(moveNum, basetime, increment);
4822 #endif
4823
4824     /* Put the move on the move list, first converting
4825        to canonical algebraic form. */
4826     if (moveNum > 0) {
4827   if (appData.debugMode) {
4828     int f = forwardMostMove;
4829     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4830             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4831             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4832     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4833     fprintf(debugFP, "moveNum = %d\n", moveNum);
4834     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4835     setbuf(debugFP, NULL);
4836   }
4837         if (moveNum <= backwardMostMove) {
4838             /* We don't know what the board looked like before
4839                this move.  Punt. */
4840           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4841             strcat(parseList[moveNum - 1], " ");
4842             strcat(parseList[moveNum - 1], elapsed_time);
4843             moveList[moveNum - 1][0] = NULLCHAR;
4844         } else if (strcmp(move_str, "none") == 0) {
4845             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4846             /* Again, we don't know what the board looked like;
4847                this is really the start of the game. */
4848             parseList[moveNum - 1][0] = NULLCHAR;
4849             moveList[moveNum - 1][0] = NULLCHAR;
4850             backwardMostMove = moveNum;
4851             startedFromSetupPosition = TRUE;
4852             fromX = fromY = toX = toY = -1;
4853         } else {
4854           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4855           //                 So we parse the long-algebraic move string in stead of the SAN move
4856           int valid; char buf[MSG_SIZ], *prom;
4857
4858           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4859                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4860           // str looks something like "Q/a1-a2"; kill the slash
4861           if(str[1] == '/')
4862             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4863           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4864           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4865                 strcat(buf, prom); // long move lacks promo specification!
4866           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4867                 if(appData.debugMode)
4868                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4869                 safeStrCpy(move_str, buf, MSG_SIZ);
4870           }
4871           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar)
4873                || ParseOneMove(buf, moveNum - 1, &moveType,
4874                                 &fromX, &fromY, &toX, &toY, &promoChar);
4875           // end of long SAN patch
4876           if (valid) {
4877             (void) CoordsToAlgebraic(boards[moveNum - 1],
4878                                      PosFlags(moveNum - 1),
4879                                      fromY, fromX, toY, toX, promoChar,
4880                                      parseList[moveNum-1]);
4881             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4882               case MT_NONE:
4883               case MT_STALEMATE:
4884               default:
4885                 break;
4886               case MT_CHECK:
4887                 if(!IS_SHOGI(gameInfo.variant))
4888                     strcat(parseList[moveNum - 1], "+");
4889                 break;
4890               case MT_CHECKMATE:
4891               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4892                 strcat(parseList[moveNum - 1], "#");
4893                 break;
4894             }
4895             strcat(parseList[moveNum - 1], " ");
4896             strcat(parseList[moveNum - 1], elapsed_time);
4897             /* currentMoveString is set as a side-effect of ParseOneMove */
4898             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4899             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4900             strcat(moveList[moveNum - 1], "\n");
4901
4902             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4903                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4904               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4905                 ChessSquare old, new = boards[moveNum][k][j];
4906                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4907                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4908                   if(old == new) continue;
4909                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4910                   else if(new == WhiteWazir || new == BlackWazir) {
4911                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4912                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4913                       else boards[moveNum][k][j] = old; // preserve type of Gold
4914                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4915                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4916               }
4917           } else {
4918             /* Move from ICS was illegal!?  Punt. */
4919             if (appData.debugMode) {
4920               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4921               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4922             }
4923             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4924             strcat(parseList[moveNum - 1], " ");
4925             strcat(parseList[moveNum - 1], elapsed_time);
4926             moveList[moveNum - 1][0] = NULLCHAR;
4927             fromX = fromY = toX = toY = -1;
4928           }
4929         }
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4932     setbuf(debugFP, NULL);
4933   }
4934
4935 #if ZIPPY
4936         /* Send move to chess program (BEFORE animating it). */
4937         if (appData.zippyPlay && !newGame && newMove &&
4938            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4939
4940             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4941                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4942                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4943                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4944                             move_str);
4945                     DisplayError(str, 0);
4946                 } else {
4947                     if (first.sendTime) {
4948                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4949                     }
4950                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4951                     if (firstMove && !bookHit) {
4952                         firstMove = FALSE;
4953                         if (first.useColors) {
4954                           SendToProgram(gameMode == IcsPlayingWhite ?
4955                                         "white\ngo\n" :
4956                                         "black\ngo\n", &first);
4957                         } else {
4958                           SendToProgram("go\n", &first);
4959                         }
4960                         first.maybeThinking = TRUE;
4961                     }
4962                 }
4963             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4964               if (moveList[moveNum - 1][0] == NULLCHAR) {
4965                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4966                 DisplayError(str, 0);
4967               } else {
4968                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4969                 SendMoveToProgram(moveNum - 1, &first);
4970               }
4971             }
4972         }
4973 #endif
4974     }
4975
4976     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4977         /* If move comes from a remote source, animate it.  If it
4978            isn't remote, it will have already been animated. */
4979         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4980             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4981         }
4982         if (!pausing && appData.highlightLastMove) {
4983             SetHighlights(fromX, fromY, toX, toY);
4984         }
4985     }
4986
4987     /* Start the clocks */
4988     whiteFlag = blackFlag = FALSE;
4989     appData.clockMode = !(basetime == 0 && increment == 0);
4990     if (ticking == 0) {
4991       ics_clock_paused = TRUE;
4992       StopClocks();
4993     } else if (ticking == 1) {
4994       ics_clock_paused = FALSE;
4995     }
4996     if (gameMode == IcsIdle ||
4997         relation == RELATION_OBSERVING_STATIC ||
4998         relation == RELATION_EXAMINING ||
4999         ics_clock_paused)
5000       DisplayBothClocks();
5001     else
5002       StartClocks();
5003
5004     /* Display opponents and material strengths */
5005     if (gameInfo.variant != VariantBughouse &&
5006         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5007         if (tinyLayout || smallLayout) {
5008             if(gameInfo.variant == VariantNormal)
5009               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5011                     basetime, increment);
5012             else
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment, (int) gameInfo.variant);
5016         } else {
5017             if(gameInfo.variant == VariantNormal)
5018               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5019                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5020                     basetime, increment);
5021             else
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment, VariantName(gameInfo.variant));
5025         }
5026         DisplayTitle(str);
5027   if (appData.debugMode) {
5028     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5029   }
5030     }
5031
5032
5033     /* Display the board */
5034     if (!pausing && !appData.noGUI) {
5035
5036       if (appData.premove)
5037           if (!gotPremove ||
5038              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5039              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5040               ClearPremoveHighlights();
5041
5042       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5043         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5044       DrawPosition(j, boards[currentMove]);
5045
5046       DisplayMove(moveNum - 1);
5047       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5048             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5049               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5050         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5051       }
5052     }
5053
5054     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5055 #if ZIPPY
5056     if(bookHit) { // [HGM] book: simulate book reply
5057         static char bookMove[MSG_SIZ]; // a bit generous?
5058
5059         programStats.nodes = programStats.depth = programStats.time =
5060         programStats.score = programStats.got_only_move = 0;
5061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5062
5063         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5064         strcat(bookMove, bookHit);
5065         HandleMachineMove(bookMove, &first);
5066     }
5067 #endif
5068 }
5069
5070 void
5071 GetMoveListEvent ()
5072 {
5073     char buf[MSG_SIZ];
5074     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5075         ics_getting_history = H_REQUESTED;
5076         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5077         SendToICS(buf);
5078     }
5079 }
5080
5081 void
5082 SendToBoth (char *msg)
5083 {   // to make it easy to keep two engines in step in dual analysis
5084     SendToProgram(msg, &first);
5085     if(second.analyzing) SendToProgram(msg, &second);
5086 }
5087
5088 void
5089 AnalysisPeriodicEvent (int force)
5090 {
5091     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5092          && !force) || !appData.periodicUpdates)
5093       return;
5094
5095     /* Send . command to Crafty to collect stats */
5096     SendToBoth(".\n");
5097
5098     /* Don't send another until we get a response (this makes
5099        us stop sending to old Crafty's which don't understand
5100        the "." command (sending illegal cmds resets node count & time,
5101        which looks bad)) */
5102     programStats.ok_to_send = 0;
5103 }
5104
5105 void
5106 ics_update_width (int new_width)
5107 {
5108         ics_printf("set width %d\n", new_width);
5109 }
5110
5111 void
5112 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5117         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5118             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5119             SendToProgram(buf, cps);
5120             return;
5121         }
5122         // null move in variant where engine does not understand it (for analysis purposes)
5123         SendBoard(cps, moveNum + 1); // send position after move in stead.
5124         return;
5125     }
5126     if (cps->useUsermove) {
5127       SendToProgram("usermove ", cps);
5128     }
5129     if (cps->useSAN) {
5130       char *space;
5131       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5132         int len = space - parseList[moveNum];
5133         memcpy(buf, parseList[moveNum], len);
5134         buf[len++] = '\n';
5135         buf[len] = NULLCHAR;
5136       } else {
5137         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5138       }
5139       SendToProgram(buf, cps);
5140     } else {
5141       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5142         AlphaRank(moveList[moveNum], 4);
5143         SendToProgram(moveList[moveNum], cps);
5144         AlphaRank(moveList[moveNum], 4); // and back
5145       } else
5146       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5147        * the engine. It would be nice to have a better way to identify castle
5148        * moves here. */
5149       if(appData.fischerCastling && cps->useOOCastle) {
5150         int fromX = moveList[moveNum][0] - AAA;
5151         int fromY = moveList[moveNum][1] - ONE;
5152         int toX = moveList[moveNum][2] - AAA;
5153         int toY = moveList[moveNum][3] - ONE;
5154         if((boards[moveNum][fromY][fromX] == WhiteKing
5155             && boards[moveNum][toY][toX] == WhiteRook)
5156            || (boards[moveNum][fromY][fromX] == BlackKing
5157                && boards[moveNum][toY][toX] == BlackRook)) {
5158           if(toX > fromX) SendToProgram("O-O\n", cps);
5159           else SendToProgram("O-O-O\n", cps);
5160         }
5161         else SendToProgram(moveList[moveNum], cps);
5162       } else
5163       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5164         char *m = moveList[moveNum];
5165         static char c[2];
5166         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5167         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
5168           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5169                                                m[2], m[3] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5172         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5173           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5174           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
5175                                                m[7], m[8] - '0',
5176                                                m[7], m[8] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[5], m[6] - '0',
5179                                                m[2], m[3] - '0', c);
5180         } else
5181           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5182                                                m[5], m[6] - '0',
5183                                                m[5], m[6] - '0',
5184                                                m[2], m[3] - '0', c);
5185           SendToProgram(buf, cps);
5186       } else
5187       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5188         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5189           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5190           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5191                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192         } else
5193           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5194                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5195         SendToProgram(buf, cps);
5196       }
5197       else SendToProgram(moveList[moveNum], cps);
5198       /* End of additions by Tord */
5199     }
5200
5201     /* [HGM] setting up the opening has brought engine in force mode! */
5202     /*       Send 'go' if we are in a mode where machine should play. */
5203     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5204         (gameMode == TwoMachinesPlay   ||
5205 #if ZIPPY
5206          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5207 #endif
5208          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5209         SendToProgram("go\n", cps);
5210   if (appData.debugMode) {
5211     fprintf(debugFP, "(extra)\n");
5212   }
5213     }
5214     setboardSpoiledMachineBlack = 0;
5215 }
5216
5217 void
5218 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5219 {
5220     char user_move[MSG_SIZ];
5221     char suffix[4];
5222
5223     if(gameInfo.variant == VariantSChess && promoChar) {
5224         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5225         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5226     } else suffix[0] = NULLCHAR;
5227
5228     switch (moveType) {
5229       default:
5230         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5231                 (int)moveType, fromX, fromY, toX, toY);
5232         DisplayError(user_move + strlen("say "), 0);
5233         break;
5234       case WhiteKingSideCastle:
5235       case BlackKingSideCastle:
5236       case WhiteQueenSideCastleWild:
5237       case BlackQueenSideCastleWild:
5238       /* PUSH Fabien */
5239       case WhiteHSideCastleFR:
5240       case BlackHSideCastleFR:
5241       /* POP Fabien */
5242         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5243         break;
5244       case WhiteQueenSideCastle:
5245       case BlackQueenSideCastle:
5246       case WhiteKingSideCastleWild:
5247       case BlackKingSideCastleWild:
5248       /* PUSH Fabien */
5249       case WhiteASideCastleFR:
5250       case BlackASideCastleFR:
5251       /* POP Fabien */
5252         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5253         break;
5254       case WhiteNonPromotion:
5255       case BlackNonPromotion:
5256         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5257         break;
5258       case WhitePromotion:
5259       case BlackPromotion:
5260         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5261            gameInfo.variant == VariantMakruk)
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 PieceToChar(WhiteFerz));
5265         else if(gameInfo.variant == VariantGreat)
5266           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteMan));
5269         else
5270           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 promoChar);
5273         break;
5274       case WhiteDrop:
5275       case BlackDrop:
5276       drop:
5277         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5278                  ToUpper(PieceToChar((ChessSquare) fromX)),
5279                  AAA + toX, ONE + toY);
5280         break;
5281       case IllegalMove:  /* could be a variant we don't quite understand */
5282         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5283       case NormalMove:
5284       case WhiteCapturesEnPassant:
5285       case BlackCapturesEnPassant:
5286         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5287                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5288         break;
5289     }
5290     SendToICS(user_move);
5291     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5292         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5293 }
5294
5295 void
5296 UploadGameEvent ()
5297 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5298     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5299     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5300     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5301       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5302       return;
5303     }
5304     if(gameMode != IcsExamining) { // is this ever not the case?
5305         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5306
5307         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5308           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5309         } else { // on FICS we must first go to general examine mode
5310           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5311         }
5312         if(gameInfo.variant != VariantNormal) {
5313             // try figure out wild number, as xboard names are not always valid on ICS
5314             for(i=1; i<=36; i++) {
5315               snprintf(buf, MSG_SIZ, "wild/%d", i);
5316                 if(StringToVariant(buf) == gameInfo.variant) break;
5317             }
5318             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5319             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5320             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5321         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5322         SendToICS(ics_prefix);
5323         SendToICS(buf);
5324         if(startedFromSetupPosition || backwardMostMove != 0) {
5325           fen = PositionToFEN(backwardMostMove, NULL, 1);
5326           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5327             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5328             SendToICS(buf);
5329           } else { // FICS: everything has to set by separate bsetup commands
5330             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5331             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5332             SendToICS(buf);
5333             if(!WhiteOnMove(backwardMostMove)) {
5334                 SendToICS("bsetup tomove black\n");
5335             }
5336             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5337             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5338             SendToICS(buf);
5339             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5340             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5341             SendToICS(buf);
5342             i = boards[backwardMostMove][EP_STATUS];
5343             if(i >= 0) { // set e.p.
5344               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5345                 SendToICS(buf);
5346             }
5347             bsetup++;
5348           }
5349         }
5350       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5351             SendToICS("bsetup done\n"); // switch to normal examining.
5352     }
5353     for(i = backwardMostMove; i<last; i++) {
5354         char buf[20];
5355         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5356         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5357             int len = strlen(moveList[i]);
5358             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5359             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5360         }
5361         SendToICS(buf);
5362     }
5363     SendToICS(ics_prefix);
5364     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5365 }
5366
5367 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5368 int legNr = 1;
5369
5370 void
5371 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5372 {
5373     if (rf == DROP_RANK) {
5374       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5375       sprintf(move, "%c@%c%c\n",
5376                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5377     } else {
5378         if (promoChar == 'x' || promoChar == NULLCHAR) {
5379           sprintf(move, "%c%c%c%c\n",
5380                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5381           if(killX >= 0 && killY >= 0) {
5382             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5383             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5384           }
5385         } else {
5386             sprintf(move, "%c%c%c%c%c\n",
5387                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5388           if(killX >= 0 && killY >= 0) {
5389             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5390             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5391           }
5392         }
5393     }
5394 }
5395
5396 void
5397 ProcessICSInitScript (FILE *f)
5398 {
5399     char buf[MSG_SIZ];
5400
5401     while (fgets(buf, MSG_SIZ, f)) {
5402         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5403     }
5404
5405     fclose(f);
5406 }
5407
5408
5409 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5410 int dragging;
5411 static ClickType lastClickType;
5412
5413 int
5414 PieceInString (char *s, ChessSquare piece)
5415 {
5416   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5417   while((p = strchr(s, ID))) {
5418     if(!suffix || p[1] == suffix) return TRUE;
5419     s = p;
5420   }
5421   return FALSE;
5422 }
5423
5424 int
5425 Partner (ChessSquare *p)
5426 { // change piece into promotion partner if one shogi-promotes to the other
5427   ChessSquare partner = promoPartner[*p];
5428   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5429   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5430   *p = partner;
5431   return 1;
5432 }
5433
5434 void
5435 Sweep (int step)
5436 {
5437     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5438     static int toggleFlag;
5439     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5440     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5441     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5442     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5443     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5444     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5445     do {
5446         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5447         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5448         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5449         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5450         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5451         if(!step) step = -1;
5452     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5453             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5454             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5455             promoSweep == pawn ||
5456             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5457             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5458     if(toX >= 0) {
5459         int victim = boards[currentMove][toY][toX];
5460         boards[currentMove][toY][toX] = promoSweep;
5461         DrawPosition(FALSE, boards[currentMove]);
5462         boards[currentMove][toY][toX] = victim;
5463     } else
5464     ChangeDragPiece(promoSweep);
5465 }
5466
5467 int
5468 PromoScroll (int x, int y)
5469 {
5470   int step = 0;
5471
5472   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5473   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5474   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5475   if(!step) return FALSE;
5476   lastX = x; lastY = y;
5477   if((promoSweep < BlackPawn) == flipView) step = -step;
5478   if(step > 0) selectFlag = 1;
5479   if(!selectFlag) Sweep(step);
5480   return FALSE;
5481 }
5482
5483 void
5484 NextPiece (int step)
5485 {
5486     ChessSquare piece = boards[currentMove][toY][toX];
5487     do {
5488         pieceSweep -= step;
5489         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5490         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5491         if(!step) step = -1;
5492     } while(PieceToChar(pieceSweep) == '.');
5493     boards[currentMove][toY][toX] = pieceSweep;
5494     DrawPosition(FALSE, boards[currentMove]);
5495     boards[currentMove][toY][toX] = piece;
5496 }
5497 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5498 void
5499 AlphaRank (char *move, int n)
5500 {
5501 //    char *p = move, c; int x, y;
5502
5503     if (appData.debugMode) {
5504         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5505     }
5506
5507     if(move[1]=='*' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         move[1] = '@';
5511         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5512         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5513     } else
5514     if(move[0]>='0' && move[0]<='9' &&
5515        move[1]>='a' && move[1]<='x' &&
5516        move[2]>='0' && move[2]<='9' &&
5517        move[3]>='a' && move[3]<='x'    ) {
5518         /* input move, Shogi -> normal */
5519         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5520         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5521         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5523     } else
5524     if(move[1]=='@' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527         move[1] = '*';
5528         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5529         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5530     } else
5531     if(
5532        move[0]>='a' && move[0]<='x' &&
5533        move[3]>='0' && move[3]<='9' &&
5534        move[2]>='a' && move[2]<='x'    ) {
5535          /* output move, normal -> Shogi */
5536         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5537         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5538         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5540         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5541     }
5542     if (appData.debugMode) {
5543         fprintf(debugFP, "   out = '%s'\n", move);
5544     }
5545 }
5546
5547 char yy_textstr[8000];
5548
5549 /* Parser for moves from gnuchess, ICS, or user typein box */
5550 Boolean
5551 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5552 {
5553     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5554
5555     switch (*moveType) {
5556       case WhitePromotion:
5557       case BlackPromotion:
5558       case WhiteNonPromotion:
5559       case BlackNonPromotion:
5560       case NormalMove:
5561       case FirstLeg:
5562       case WhiteCapturesEnPassant:
5563       case BlackCapturesEnPassant:
5564       case WhiteKingSideCastle:
5565       case WhiteQueenSideCastle:
5566       case BlackKingSideCastle:
5567       case BlackQueenSideCastle:
5568       case WhiteKingSideCastleWild:
5569       case WhiteQueenSideCastleWild:
5570       case BlackKingSideCastleWild:
5571       case BlackQueenSideCastleWild:
5572       /* Code added by Tord: */
5573       case WhiteHSideCastleFR:
5574       case WhiteASideCastleFR:
5575       case BlackHSideCastleFR:
5576       case BlackASideCastleFR:
5577       /* End of code added by Tord */
5578       case IllegalMove:         /* bug or odd chess variant */
5579         if(currentMoveString[1] == '@') { // illegal drop
5580           *fromX = WhiteOnMove(moveNum) ?
5581             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5582             (int) CharToPiece(ToLower(currentMoveString[0]));
5583           goto drop;
5584         }
5585         *fromX = currentMoveString[0] - AAA;
5586         *fromY = currentMoveString[1] - ONE;
5587         *toX = currentMoveString[2] - AAA;
5588         *toY = currentMoveString[3] - ONE;
5589         *promoChar = currentMoveString[4];
5590         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5591         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5592             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5593     if (appData.debugMode) {
5594         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5595     }
5596             *fromX = *fromY = *toX = *toY = 0;
5597             return FALSE;
5598         }
5599         if (appData.testLegality) {
5600           return (*moveType != IllegalMove);
5601         } else {
5602           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5603                          // [HGM] lion: if this is a double move we are less critical
5604                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5605         }
5606
5607       case WhiteDrop:
5608       case BlackDrop:
5609         *fromX = *moveType == WhiteDrop ?
5610           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611           (int) CharToPiece(ToLower(currentMoveString[0]));
5612       drop:
5613         *fromY = DROP_RANK;
5614         *toX = currentMoveString[2] - AAA;
5615         *toY = currentMoveString[3] - ONE;
5616         *promoChar = NULLCHAR;
5617         return TRUE;
5618
5619       case AmbiguousMove:
5620       case ImpossibleMove:
5621       case EndOfFile:
5622       case ElapsedTime:
5623       case Comment:
5624       case PGNTag:
5625       case NAG:
5626       case WhiteWins:
5627       case BlackWins:
5628       case GameIsDrawn:
5629       default:
5630     if (appData.debugMode) {
5631         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5632     }
5633         /* bug? */
5634         *fromX = *fromY = *toX = *toY = 0;
5635         *promoChar = NULLCHAR;
5636         return FALSE;
5637     }
5638 }
5639
5640 Boolean pushed = FALSE;
5641 char *lastParseAttempt;
5642
5643 void
5644 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5645 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5646   int fromX, fromY, toX, toY; char promoChar;
5647   ChessMove moveType;
5648   Boolean valid;
5649   int nr = 0;
5650
5651   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5652   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5653     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5654     pushed = TRUE;
5655   }
5656   endPV = forwardMostMove;
5657   do {
5658     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5659     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5660     lastParseAttempt = pv;
5661     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5662     if(!valid && nr == 0 &&
5663        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5664         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5665         // Hande case where played move is different from leading PV move
5666         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5667         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5668         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5669         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5670           endPV += 2; // if position different, keep this
5671           moveList[endPV-1][0] = fromX + AAA;
5672           moveList[endPV-1][1] = fromY + ONE;
5673           moveList[endPV-1][2] = toX + AAA;
5674           moveList[endPV-1][3] = toY + ONE;
5675           parseList[endPV-1][0] = NULLCHAR;
5676           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5677         }
5678       }
5679     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5680     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5681     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5682     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5683         valid++; // allow comments in PV
5684         continue;
5685     }
5686     nr++;
5687     if(endPV+1 > framePtr) break; // no space, truncate
5688     if(!valid) break;
5689     endPV++;
5690     CopyBoard(boards[endPV], boards[endPV-1]);
5691     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5692     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5693     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5694     CoordsToAlgebraic(boards[endPV - 1],
5695                              PosFlags(endPV - 1),
5696                              fromY, fromX, toY, toX, promoChar,
5697                              parseList[endPV - 1]);
5698   } while(valid);
5699   if(atEnd == 2) return; // used hidden, for PV conversion
5700   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5701   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5702   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5703                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5704   DrawPosition(TRUE, boards[currentMove]);
5705 }
5706
5707 int
5708 MultiPV (ChessProgramState *cps, int kind)
5709 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5710         int i;
5711         for(i=0; i<cps->nrOptions; i++) {
5712             char *s = cps->option[i].name;
5713             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5714             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5715                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5716         }
5717         return -1;
5718 }
5719
5720 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5721 static int multi, pv_margin;
5722 static ChessProgramState *activeCps;
5723
5724 Boolean
5725 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5726 {
5727         int startPV, lineStart, origIndex = index;
5728         char *p, buf2[MSG_SIZ];
5729         ChessProgramState *cps = (pane ? &second : &first);
5730
5731         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5732         lastX = x; lastY = y;
5733         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5734         lineStart = startPV = index;
5735         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5736         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5737         index = startPV;
5738         do{ while(buf[index] && buf[index] != '\n') index++;
5739         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5740         buf[index] = 0;
5741         if(lineStart == 0 && gameMode == AnalyzeMode) {
5742             int n = 0;
5743             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5744             if(n == 0) { // click not on "fewer" or "more"
5745                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5746                     pv_margin = cps->option[multi].value;
5747                     activeCps = cps; // non-null signals margin adjustment
5748                 }
5749             } else if((multi = MultiPV(cps, 1)) >= 0) {
5750                 n += cps->option[multi].value; if(n < 1) n = 1;
5751                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5752                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5753                 cps->option[multi].value = n;
5754                 *start = *end = 0;
5755                 return FALSE;
5756             }
5757         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5758                 ExcludeClick(origIndex - lineStart);
5759                 return FALSE;
5760         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5761                 Collapse(origIndex - lineStart);
5762                 return FALSE;
5763         }
5764         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5765         *start = startPV; *end = index-1;
5766         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5767         return TRUE;
5768 }
5769
5770 char *
5771 PvToSAN (char *pv)
5772 {
5773         static char buf[10*MSG_SIZ];
5774         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5775         *buf = NULLCHAR;
5776         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5777         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5778         for(i = forwardMostMove; i<endPV; i++){
5779             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5780             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5781             k += strlen(buf+k);
5782         }
5783         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5784         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5785         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5786         endPV = savedEnd;
5787         return buf;
5788 }
5789
5790 Boolean
5791 LoadPV (int x, int y)
5792 { // called on right mouse click to load PV
5793   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5794   lastX = x; lastY = y;
5795   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5796   extendGame = FALSE;
5797   return TRUE;
5798 }
5799
5800 void
5801 UnLoadPV ()
5802 {
5803   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5804   if(activeCps) {
5805     if(pv_margin != activeCps->option[multi].value) {
5806       char buf[MSG_SIZ];
5807       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5808       SendToProgram(buf, activeCps);
5809       activeCps->option[multi].value = pv_margin;
5810     }
5811     activeCps = NULL;
5812     return;
5813   }
5814   if(endPV < 0) return;
5815   if(appData.autoCopyPV) CopyFENToClipboard();
5816   endPV = -1;
5817   if(extendGame && currentMove > forwardMostMove) {
5818         Boolean saveAnimate = appData.animate;
5819         if(pushed) {
5820             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5821                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5822             } else storedGames--; // abandon shelved tail of original game
5823         }
5824         pushed = FALSE;
5825         forwardMostMove = currentMove;
5826         currentMove = oldFMM;
5827         appData.animate = FALSE;
5828         ToNrEvent(forwardMostMove);
5829         appData.animate = saveAnimate;
5830   }
5831   currentMove = forwardMostMove;
5832   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5833   ClearPremoveHighlights();
5834   DrawPosition(TRUE, boards[currentMove]);
5835 }
5836
5837 void
5838 MovePV (int x, int y, int h)
5839 { // step through PV based on mouse coordinates (called on mouse move)
5840   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5841
5842   if(activeCps) { // adjusting engine's multi-pv margin
5843     if(x > lastX) pv_margin++; else
5844     if(x < lastX) pv_margin -= (pv_margin > 0);
5845     if(x != lastX) {
5846       char buf[MSG_SIZ];
5847       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5848       DisplayMessage(buf, "");
5849     }
5850     lastX = x;
5851     return;
5852   }
5853   // we must somehow check if right button is still down (might be released off board!)
5854   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5855   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5856   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5857   if(!step) return;
5858   lastX = x; lastY = y;
5859
5860   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5861   if(endPV < 0) return;
5862   if(y < margin) step = 1; else
5863   if(y > h - margin) step = -1;
5864   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5865   currentMove += step;
5866   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5867   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5868                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5869   DrawPosition(FALSE, boards[currentMove]);
5870 }
5871
5872
5873 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5874 // All positions will have equal probability, but the current method will not provide a unique
5875 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5876 #define DARK 1
5877 #define LITE 2
5878 #define ANY 3
5879
5880 int squaresLeft[4];
5881 int piecesLeft[(int)BlackPawn];
5882 int seed, nrOfShuffles;
5883
5884 void
5885 GetPositionNumber ()
5886 {       // sets global variable seed
5887         int i;
5888
5889         seed = appData.defaultFrcPosition;
5890         if(seed < 0) { // randomize based on time for negative FRC position numbers
5891                 for(i=0; i<50; i++) seed += random();
5892                 seed = random() ^ random() >> 8 ^ random() << 8;
5893                 if(seed<0) seed = -seed;
5894         }
5895 }
5896
5897 int
5898 put (Board board, int pieceType, int rank, int n, int shade)
5899 // put the piece on the (n-1)-th empty squares of the given shade
5900 {
5901         int i;
5902
5903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5904                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5905                         board[rank][i] = (ChessSquare) pieceType;
5906                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5907                         squaresLeft[ANY]--;
5908                         piecesLeft[pieceType]--;
5909                         return i;
5910                 }
5911         }
5912         return -1;
5913 }
5914
5915
5916 void
5917 AddOnePiece (Board board, int pieceType, int rank, int shade)
5918 // calculate where the next piece goes, (any empty square), and put it there
5919 {
5920         int i;
5921
5922         i = seed % squaresLeft[shade];
5923         nrOfShuffles *= squaresLeft[shade];
5924         seed /= squaresLeft[shade];
5925         put(board, pieceType, rank, i, shade);
5926 }
5927
5928 void
5929 AddTwoPieces (Board board, int pieceType, int rank)
5930 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5931 {
5932         int i, n=squaresLeft[ANY], j=n-1, k;
5933
5934         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5935         i = seed % k;  // pick one
5936         nrOfShuffles *= k;
5937         seed /= k;
5938         while(i >= j) i -= j--;
5939         j = n - 1 - j; i += j;
5940         put(board, pieceType, rank, j, ANY);
5941         put(board, pieceType, rank, i, ANY);
5942 }
5943
5944 void
5945 SetUpShuffle (Board board, int number)
5946 {
5947         int i, p, first=1;
5948
5949         GetPositionNumber(); nrOfShuffles = 1;
5950
5951         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5952         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5953         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5954
5955         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5956
5957         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5958             p = (int) board[0][i];
5959             if(p < (int) BlackPawn) piecesLeft[p] ++;
5960             board[0][i] = EmptySquare;
5961         }
5962
5963         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5964             // shuffles restricted to allow normal castling put KRR first
5965             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5966                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5968                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5970                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5971             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5972                 put(board, WhiteRook, 0, 0, ANY);
5973             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5974         }
5975
5976         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5977             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5978             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5979                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5980                 while(piecesLeft[p] >= 2) {
5981                     AddOnePiece(board, p, 0, LITE);
5982                     AddOnePiece(board, p, 0, DARK);
5983                 }
5984                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5985             }
5986
5987         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5988             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5989             // but we leave King and Rooks for last, to possibly obey FRC restriction
5990             if(p == (int)WhiteRook) continue;
5991             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5992             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5993         }
5994
5995         // now everything is placed, except perhaps King (Unicorn) and Rooks
5996
5997         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5998             // Last King gets castling rights
5999             while(piecesLeft[(int)WhiteUnicorn]) {
6000                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6001                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6002             }
6003
6004             while(piecesLeft[(int)WhiteKing]) {
6005                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6006                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6007             }
6008
6009
6010         } else {
6011             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6012             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6013         }
6014
6015         // Only Rooks can be left; simply place them all
6016         while(piecesLeft[(int)WhiteRook]) {
6017                 i = put(board, WhiteRook, 0, 0, ANY);
6018                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6019                         if(first) {
6020                                 first=0;
6021                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6022                         }
6023                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6024                 }
6025         }
6026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6027             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6028         }
6029
6030         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6031 }
6032
6033 int
6034 ptclen (const char *s, char *escapes)
6035 {
6036     int n = 0;
6037     if(!*escapes) return strlen(s);
6038     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6039     return n;
6040 }
6041
6042 int
6043 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6044 /* [HGM] moved here from winboard.c because of its general usefulness */
6045 /*       Basically a safe strcpy that uses the last character as King */
6046 {
6047     int result = FALSE; int NrPieces;
6048     unsigned char partner[EmptySquare];
6049
6050     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6051                     && NrPieces >= 12 && !(NrPieces&1)) {
6052         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6053
6054         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6055         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6056             char *p, c=0;
6057             if(map[j] == '/') offs = WhitePBishop - i, j++;
6058             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6059             table[i+offs] = map[j++];
6060             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6061             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6062             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6063         }
6064         table[(int) WhiteKing]  = map[j++];
6065         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6066             char *p, c=0;
6067             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6068             i = WHITE_TO_BLACK ii;
6069             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6070             table[i+offs] = map[j++];
6071             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6072             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6073             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6074         }
6075         table[(int) BlackKing]  = map[j++];
6076
6077
6078         if(*escapes) { // set up promotion pairing
6079             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6080             // pieceToChar entirely filled, so we can look up specified partners
6081             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6082                 int c = table[i];
6083                 if(c == '^' || c == '-') { // has specified partner
6084                     int p;
6085                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6086                     if(c == '^') table[i] = '+';
6087                     if(p < EmptySquare) {
6088                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6089                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6090                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6091                     }
6092                 } else if(c == '*') {
6093                     table[i] = partner[i];
6094                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6095                 }
6096             }
6097         }
6098
6099         result = TRUE;
6100     }
6101
6102     return result;
6103 }
6104
6105 int
6106 SetCharTable (unsigned char *table, const char * map)
6107 {
6108     return SetCharTableEsc(table, map, "");
6109 }
6110
6111 void
6112 Prelude (Board board)
6113 {       // [HGM] superchess: random selection of exo-pieces
6114         int i, j, k; ChessSquare p;
6115         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6116
6117         GetPositionNumber(); // use FRC position number
6118
6119         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6120             SetCharTable(pieceToChar, appData.pieceToCharTable);
6121             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6122                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6123         }
6124
6125         j = seed%4;                 seed /= 4;
6126         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%3 + (seed%3 >= j); seed /= 3;
6130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%3;                 seed /= 3;
6134         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6136         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6137         j = seed%2 + (seed%2 >= j); seed /= 2;
6138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6140         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6141         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6142         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6143         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6144         put(board, exoPieces[0],    0, 0, ANY);
6145         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6146 }
6147
6148 void
6149 InitPosition (int redraw)
6150 {
6151     ChessSquare (* pieces)[BOARD_FILES];
6152     int i, j, pawnRow=1, pieceRows=1, overrule,
6153     oldx = gameInfo.boardWidth,
6154     oldy = gameInfo.boardHeight,
6155     oldh = gameInfo.holdingsWidth;
6156     static int oldv;
6157
6158     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6159
6160     /* [AS] Initialize pv info list [HGM] and game status */
6161     {
6162         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6163             pvInfoList[i].depth = 0;
6164             boards[i][EP_STATUS] = EP_NONE;
6165             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6166         }
6167
6168         initialRulePlies = 0; /* 50-move counter start */
6169
6170         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6171         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6172     }
6173
6174
6175     /* [HGM] logic here is completely changed. In stead of full positions */
6176     /* the initialized data only consist of the two backranks. The switch */
6177     /* selects which one we will use, which is than copied to the Board   */
6178     /* initialPosition, which for the rest is initialized by Pawns and    */
6179     /* empty squares. This initial position is then copied to boards[0],  */
6180     /* possibly after shuffling, so that it remains available.            */
6181
6182     gameInfo.holdingsWidth = 0; /* default board sizes */
6183     gameInfo.boardWidth    = 8;
6184     gameInfo.boardHeight   = 8;
6185     gameInfo.holdingsSize  = 0;
6186     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6187     for(i=0; i<BOARD_FILES-6; i++)
6188       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6189     initialPosition[EP_STATUS] = EP_NONE;
6190     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6191     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6192     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6193          SetCharTable(pieceNickName, appData.pieceNickNames);
6194     else SetCharTable(pieceNickName, "............");
6195     pieces = FIDEArray;
6196
6197     switch (gameInfo.variant) {
6198     case VariantFischeRandom:
6199       shuffleOpenings = TRUE;
6200       appData.fischerCastling = TRUE;
6201     default:
6202       break;
6203     case VariantShatranj:
6204       pieces = ShatranjArray;
6205       nrCastlingRights = 0;
6206       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6207       break;
6208     case VariantMakruk:
6209       pieces = makrukArray;
6210       nrCastlingRights = 0;
6211       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6212       break;
6213     case VariantASEAN:
6214       pieces = aseanArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6217       break;
6218     case VariantTwoKings:
6219       pieces = twoKingsArray;
6220       break;
6221     case VariantGrand:
6222       pieces = GrandArray;
6223       nrCastlingRights = 0;
6224       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225       gameInfo.boardWidth = 10;
6226       gameInfo.boardHeight = 10;
6227       gameInfo.holdingsSize = 7;
6228       break;
6229     case VariantCapaRandom:
6230       shuffleOpenings = TRUE;
6231       appData.fischerCastling = TRUE;
6232     case VariantCapablanca:
6233       pieces = CapablancaArray;
6234       gameInfo.boardWidth = 10;
6235       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6236       break;
6237     case VariantGothic:
6238       pieces = GothicArray;
6239       gameInfo.boardWidth = 10;
6240       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6241       break;
6242     case VariantSChess:
6243       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6244       gameInfo.holdingsSize = 7;
6245       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6246       break;
6247     case VariantJanus:
6248       pieces = JanusArray;
6249       gameInfo.boardWidth = 10;
6250       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6251       nrCastlingRights = 6;
6252         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6253         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6254         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6255         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6256         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6257         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6258       break;
6259     case VariantFalcon:
6260       pieces = FalconArray;
6261       gameInfo.boardWidth = 10;
6262       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6263       break;
6264     case VariantXiangqi:
6265       pieces = XiangqiArray;
6266       gameInfo.boardWidth  = 9;
6267       gameInfo.boardHeight = 10;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6270       break;
6271     case VariantShogi:
6272       pieces = ShogiArray;
6273       gameInfo.boardWidth  = 9;
6274       gameInfo.boardHeight = 9;
6275       gameInfo.holdingsSize = 7;
6276       nrCastlingRights = 0;
6277       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6278       break;
6279     case VariantChu:
6280       pieces = ChuArray; pieceRows = 3;
6281       gameInfo.boardWidth  = 12;
6282       gameInfo.boardHeight = 12;
6283       nrCastlingRights = 0;
6284 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6285   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6286       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"
6287                                    "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);
6288       break;
6289     case VariantCourier:
6290       pieces = CourierArray;
6291       gameInfo.boardWidth  = 12;
6292       nrCastlingRights = 0;
6293       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6294       break;
6295     case VariantKnightmate:
6296       pieces = KnightmateArray;
6297       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6298       break;
6299     case VariantSpartan:
6300       pieces = SpartanArray;
6301       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6302       break;
6303     case VariantLion:
6304       pieces = lionArray;
6305       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6306       break;
6307     case VariantChuChess:
6308       pieces = ChuChessArray;
6309       gameInfo.boardWidth = 10;
6310       gameInfo.boardHeight = 10;
6311       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6312       break;
6313     case VariantFairy:
6314       pieces = fairyArray;
6315       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6316       break;
6317     case VariantGreat:
6318       pieces = GreatArray;
6319       gameInfo.boardWidth = 10;
6320       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6321       gameInfo.holdingsSize = 8;
6322       break;
6323     case VariantSuper:
6324       pieces = FIDEArray;
6325       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6326       gameInfo.holdingsSize = 8;
6327       startedFromSetupPosition = TRUE;
6328       break;
6329     case VariantCrazyhouse:
6330     case VariantBughouse:
6331       pieces = FIDEArray;
6332       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6333       gameInfo.holdingsSize = 5;
6334       break;
6335     case VariantWildCastle:
6336       pieces = FIDEArray;
6337       /* !!?shuffle with kings guaranteed to be on d or e file */
6338       shuffleOpenings = 1;
6339       break;
6340     case VariantNoCastle:
6341       pieces = FIDEArray;
6342       nrCastlingRights = 0;
6343       /* !!?unconstrained back-rank shuffle */
6344       shuffleOpenings = 1;
6345       break;
6346     }
6347
6348     overrule = 0;
6349     if(appData.NrFiles >= 0) {
6350         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6351         gameInfo.boardWidth = appData.NrFiles;
6352     }
6353     if(appData.NrRanks >= 0) {
6354         gameInfo.boardHeight = appData.NrRanks;
6355     }
6356     if(appData.holdingsSize >= 0) {
6357         i = appData.holdingsSize;
6358         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6359         gameInfo.holdingsSize = i;
6360     }
6361     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6362     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6363         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6364
6365     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6366     if(pawnRow < 1) pawnRow = 1;
6367     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6368        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6369     if(gameInfo.variant == VariantChu) pawnRow = 3;
6370
6371     /* User pieceToChar list overrules defaults */
6372     if(appData.pieceToCharTable != NULL)
6373         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6374
6375     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6376
6377         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6378             s = (ChessSquare) 0; /* account holding counts in guard band */
6379         for( i=0; i<BOARD_HEIGHT; i++ )
6380             initialPosition[i][j] = s;
6381
6382         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6383         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6384         initialPosition[pawnRow][j] = WhitePawn;
6385         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6386         if(gameInfo.variant == VariantXiangqi) {
6387             if(j&1) {
6388                 initialPosition[pawnRow][j] =
6389                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6390                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6391                    initialPosition[2][j] = WhiteCannon;
6392                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6393                 }
6394             }
6395         }
6396         if(gameInfo.variant == VariantChu) {
6397              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6398                initialPosition[pawnRow+1][j] = WhiteCobra,
6399                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6400              for(i=1; i<pieceRows; i++) {
6401                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6402                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6403              }
6404         }
6405         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6406             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6407                initialPosition[0][j] = WhiteRook;
6408                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6409             }
6410         }
6411         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6412     }
6413     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6414     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6415
6416             j=BOARD_LEFT+1;
6417             initialPosition[1][j] = WhiteBishop;
6418             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6419             j=BOARD_RGHT-2;
6420             initialPosition[1][j] = WhiteRook;
6421             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6422     }
6423
6424     if( nrCastlingRights == -1) {
6425         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6426         /*       This sets default castling rights from none to normal corners   */
6427         /* Variants with other castling rights must set them themselves above    */
6428         nrCastlingRights = 6;
6429
6430         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6431         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6432         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6433         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6434         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6435         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6436      }
6437
6438      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6439      if(gameInfo.variant == VariantGreat) { // promotion commoners
6440         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6441         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6442         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6443         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6444      }
6445      if( gameInfo.variant == VariantSChess ) {
6446       initialPosition[1][0] = BlackMarshall;
6447       initialPosition[2][0] = BlackAngel;
6448       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6449       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6450       initialPosition[1][1] = initialPosition[2][1] =
6451       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6452      }
6453   if (appData.debugMode) {
6454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6455   }
6456     if(shuffleOpenings) {
6457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6458         startedFromSetupPosition = TRUE;
6459     }
6460     if(startedFromPositionFile) {
6461       /* [HGM] loadPos: use PositionFile for every new game */
6462       CopyBoard(initialPosition, filePosition);
6463       for(i=0; i<nrCastlingRights; i++)
6464           initialRights[i] = filePosition[CASTLING][i];
6465       startedFromSetupPosition = TRUE;
6466     }
6467     if(*appData.men) LoadPieceDesc(appData.men);
6468
6469     CopyBoard(boards[0], initialPosition);
6470
6471     if(oldx != gameInfo.boardWidth ||
6472        oldy != gameInfo.boardHeight ||
6473        oldv != gameInfo.variant ||
6474        oldh != gameInfo.holdingsWidth
6475                                          )
6476             InitDrawingSizes(-2 ,0);
6477
6478     oldv = gameInfo.variant;
6479     if (redraw)
6480       DrawPosition(TRUE, boards[currentMove]);
6481 }
6482
6483 void
6484 SendBoard (ChessProgramState *cps, int moveNum)
6485 {
6486     char message[MSG_SIZ];
6487
6488     if (cps->useSetboard) {
6489       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6490       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6491       SendToProgram(message, cps);
6492       free(fen);
6493
6494     } else {
6495       ChessSquare *bp;
6496       int i, j, left=0, right=BOARD_WIDTH;
6497       /* Kludge to set black to move, avoiding the troublesome and now
6498        * deprecated "black" command.
6499        */
6500       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6501         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6502
6503       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6504
6505       SendToProgram("edit\n", cps);
6506       SendToProgram("#\n", cps);
6507       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6508         bp = &boards[moveNum][i][left];
6509         for (j = left; j < right; j++, bp++) {
6510           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6511           if ((int) *bp < (int) BlackPawn) {
6512             if(j == BOARD_RGHT+1)
6513                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6514             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6515             if(message[0] == '+' || message[0] == '~') {
6516               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6517                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6518                         AAA + j, ONE + i - '0');
6519             }
6520             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6521                 message[1] = BOARD_RGHT   - 1 - j + '1';
6522                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6523             }
6524             SendToProgram(message, cps);
6525           }
6526         }
6527       }
6528
6529       SendToProgram("c\n", cps);
6530       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6531         bp = &boards[moveNum][i][left];
6532         for (j = left; j < right; j++, bp++) {
6533           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6534           if (((int) *bp != (int) EmptySquare)
6535               && ((int) *bp >= (int) BlackPawn)) {
6536             if(j == BOARD_LEFT-2)
6537                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6538             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6539                     AAA + j, ONE + i - '0');
6540             if(message[0] == '+' || message[0] == '~') {
6541               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6542                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6543                         AAA + j, ONE + i - '0');
6544             }
6545             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6546                 message[1] = BOARD_RGHT   - 1 - j + '1';
6547                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6548             }
6549             SendToProgram(message, cps);
6550           }
6551         }
6552       }
6553
6554       SendToProgram(".\n", cps);
6555     }
6556     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6557 }
6558
6559 char exclusionHeader[MSG_SIZ];
6560 int exCnt, excludePtr;
6561 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6562 static Exclusion excluTab[200];
6563 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6564
6565 static void
6566 WriteMap (int s)
6567 {
6568     int j;
6569     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6570     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6571 }
6572
6573 static void
6574 ClearMap ()
6575 {
6576     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6577     excludePtr = 24; exCnt = 0;
6578     WriteMap(0);
6579 }
6580
6581 static void
6582 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6583 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6584     char buf[2*MOVE_LEN], *p;
6585     Exclusion *e = excluTab;
6586     int i;
6587     for(i=0; i<exCnt; i++)
6588         if(e[i].ff == fromX && e[i].fr == fromY &&
6589            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6590     if(i == exCnt) { // was not in exclude list; add it
6591         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6592         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6593             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6594             return; // abort
6595         }
6596         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6597         excludePtr++; e[i].mark = excludePtr++;
6598         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6599         exCnt++;
6600     }
6601     exclusionHeader[e[i].mark] = state;
6602 }
6603
6604 static int
6605 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6606 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6607     char buf[MSG_SIZ];
6608     int j, k;
6609     ChessMove moveType;
6610     if((signed char)promoChar == -1) { // kludge to indicate best move
6611         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6612             return 1; // if unparsable, abort
6613     }
6614     // update exclusion map (resolving toggle by consulting existing state)
6615     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6616     j = k%8; k >>= 3;
6617     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6618     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6619          excludeMap[k] |=   1<<j;
6620     else excludeMap[k] &= ~(1<<j);
6621     // update header
6622     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6623     // inform engine
6624     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6625     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6626     SendToBoth(buf);
6627     return (state == '+');
6628 }
6629
6630 static void
6631 ExcludeClick (int index)
6632 {
6633     int i, j;
6634     Exclusion *e = excluTab;
6635     if(index < 25) { // none, best or tail clicked
6636         if(index < 13) { // none: include all
6637             WriteMap(0); // clear map
6638             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6639             SendToBoth("include all\n"); // and inform engine
6640         } else if(index > 18) { // tail
6641             if(exclusionHeader[19] == '-') { // tail was excluded
6642                 SendToBoth("include all\n");
6643                 WriteMap(0); // clear map completely
6644                 // now re-exclude selected moves
6645                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6646                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6647             } else { // tail was included or in mixed state
6648                 SendToBoth("exclude all\n");
6649                 WriteMap(0xFF); // fill map completely
6650                 // now re-include selected moves
6651                 j = 0; // count them
6652                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6653                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6654                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6655             }
6656         } else { // best
6657             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6658         }
6659     } else {
6660         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6661             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6662             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6663             break;
6664         }
6665     }
6666 }
6667
6668 ChessSquare
6669 DefaultPromoChoice (int white)
6670 {
6671     ChessSquare result;
6672     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6673        gameInfo.variant == VariantMakruk)
6674         result = WhiteFerz; // no choice
6675     else if(gameInfo.variant == VariantASEAN)
6676         result = WhiteRook; // no choice
6677     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6678         result= WhiteKing; // in Suicide Q is the last thing we want
6679     else if(gameInfo.variant == VariantSpartan)
6680         result = white ? WhiteQueen : WhiteAngel;
6681     else result = WhiteQueen;
6682     if(!white) result = WHITE_TO_BLACK result;
6683     return result;
6684 }
6685
6686 static int autoQueen; // [HGM] oneclick
6687
6688 int
6689 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6690 {
6691     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6692     /* [HGM] add Shogi promotions */
6693     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6694     ChessSquare piece, partner;
6695     ChessMove moveType;
6696     Boolean premove;
6697
6698     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6699     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6700
6701     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6702       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6703         return FALSE;
6704
6705     if(legal[toY][toX] == 4) return FALSE;
6706
6707     piece = boards[currentMove][fromY][fromX];
6708     if(gameInfo.variant == VariantChu) {
6709         promotionZoneSize = BOARD_HEIGHT/3;
6710         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6711     } else if(gameInfo.variant == VariantShogi) {
6712         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6713         highestPromotingPiece = (int)WhiteAlfil;
6714     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6715         promotionZoneSize = 3;
6716     }
6717
6718     // Treat Lance as Pawn when it is not representing Amazon or Lance
6719     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6720         if(piece == WhiteLance) piece = WhitePawn; else
6721         if(piece == BlackLance) piece = BlackPawn;
6722     }
6723
6724     // next weed out all moves that do not touch the promotion zone at all
6725     if((int)piece >= BlackPawn) {
6726         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6727              return FALSE;
6728         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6729         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6730     } else {
6731         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6732            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6733         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6734              return FALSE;
6735     }
6736
6737     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6738
6739     // weed out mandatory Shogi promotions
6740     if(gameInfo.variant == VariantShogi) {
6741         if(piece >= BlackPawn) {
6742             if(toY == 0 && piece == BlackPawn ||
6743                toY == 0 && piece == BlackQueen ||
6744                toY <= 1 && piece == BlackKnight) {
6745                 *promoChoice = '+';
6746                 return FALSE;
6747             }
6748         } else {
6749             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6750                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6751                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6752                 *promoChoice = '+';
6753                 return FALSE;
6754             }
6755         }
6756     }
6757
6758     // weed out obviously illegal Pawn moves
6759     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6760         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6761         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6762         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6763         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6764         // note we are not allowed to test for valid (non-)capture, due to premove
6765     }
6766
6767     // we either have a choice what to promote to, or (in Shogi) whether to promote
6768     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6769        gameInfo.variant == VariantMakruk) {
6770         ChessSquare p=BlackFerz;  // no choice
6771         while(p < EmptySquare) {  //but make sure we use piece that exists
6772             *promoChoice = PieceToChar(p++);
6773             if(*promoChoice != '.') break;
6774         }
6775         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6776     }
6777     // no sense asking what we must promote to if it is going to explode...
6778     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6779         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6780         return FALSE;
6781     }
6782     // give caller the default choice even if we will not make it
6783     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6784     partner = piece; // pieces can promote if the pieceToCharTable says so
6785     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6786     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6787     if(        sweepSelect && gameInfo.variant != VariantGreat
6788                            && gameInfo.variant != VariantGrand
6789                            && gameInfo.variant != VariantSuper) return FALSE;
6790     if(autoQueen) return FALSE; // predetermined
6791
6792     // suppress promotion popup on illegal moves that are not premoves
6793     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6794               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6795     if(appData.testLegality && !premove) {
6796         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6797                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6798         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6799         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6800             return FALSE;
6801     }
6802
6803     return TRUE;
6804 }
6805
6806 int
6807 InPalace (int row, int column)
6808 {   /* [HGM] for Xiangqi */
6809     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6810          column < (BOARD_WIDTH + 4)/2 &&
6811          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6812     return FALSE;
6813 }
6814
6815 int
6816 PieceForSquare (int x, int y)
6817 {
6818   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6819      return -1;
6820   else
6821      return boards[currentMove][y][x];
6822 }
6823
6824 int
6825 OKToStartUserMove (int x, int y)
6826 {
6827     ChessSquare from_piece;
6828     int white_piece;
6829
6830     if (matchMode) return FALSE;
6831     if (gameMode == EditPosition) return TRUE;
6832
6833     if (x >= 0 && y >= 0)
6834       from_piece = boards[currentMove][y][x];
6835     else
6836       from_piece = EmptySquare;
6837
6838     if (from_piece == EmptySquare) return FALSE;
6839
6840     white_piece = (int)from_piece >= (int)WhitePawn &&
6841       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6842
6843     switch (gameMode) {
6844       case AnalyzeFile:
6845       case TwoMachinesPlay:
6846       case EndOfGame:
6847         return FALSE;
6848
6849       case IcsObserving:
6850       case IcsIdle:
6851         return FALSE;
6852
6853       case MachinePlaysWhite:
6854       case IcsPlayingBlack:
6855         if (appData.zippyPlay) return FALSE;
6856         if (white_piece) {
6857             DisplayMoveError(_("You are playing Black"));
6858             return FALSE;
6859         }
6860         break;
6861
6862       case MachinePlaysBlack:
6863       case IcsPlayingWhite:
6864         if (appData.zippyPlay) return FALSE;
6865         if (!white_piece) {
6866             DisplayMoveError(_("You are playing White"));
6867             return FALSE;
6868         }
6869         break;
6870
6871       case PlayFromGameFile:
6872             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6873       case EditGame:
6874       case AnalyzeMode:
6875         if (!white_piece && WhiteOnMove(currentMove)) {
6876             DisplayMoveError(_("It is White's turn"));
6877             return FALSE;
6878         }
6879         if (white_piece && !WhiteOnMove(currentMove)) {
6880             DisplayMoveError(_("It is Black's turn"));
6881             return FALSE;
6882         }
6883         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6884             /* Editing correspondence game history */
6885             /* Could disallow this or prompt for confirmation */
6886             cmailOldMove = -1;
6887         }
6888         break;
6889
6890       case BeginningOfGame:
6891         if (appData.icsActive) return FALSE;
6892         if (!appData.noChessProgram) {
6893             if (!white_piece) {
6894                 DisplayMoveError(_("You are playing White"));
6895                 return FALSE;
6896             }
6897         }
6898         break;
6899
6900       case Training:
6901         if (!white_piece && WhiteOnMove(currentMove)) {
6902             DisplayMoveError(_("It is White's turn"));
6903             return FALSE;
6904         }
6905         if (white_piece && !WhiteOnMove(currentMove)) {
6906             DisplayMoveError(_("It is Black's turn"));
6907             return FALSE;
6908         }
6909         break;
6910
6911       default:
6912       case IcsExamining:
6913         break;
6914     }
6915     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6916         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6917         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6918         && gameMode != AnalyzeFile && gameMode != Training) {
6919         DisplayMoveError(_("Displayed position is not current"));
6920         return FALSE;
6921     }
6922     return TRUE;
6923 }
6924
6925 Boolean
6926 OnlyMove (int *x, int *y, Boolean captures)
6927 {
6928     DisambiguateClosure cl;
6929     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6930     switch(gameMode) {
6931       case MachinePlaysBlack:
6932       case IcsPlayingWhite:
6933       case BeginningOfGame:
6934         if(!WhiteOnMove(currentMove)) return FALSE;
6935         break;
6936       case MachinePlaysWhite:
6937       case IcsPlayingBlack:
6938         if(WhiteOnMove(currentMove)) return FALSE;
6939         break;
6940       case EditGame:
6941         break;
6942       default:
6943         return FALSE;
6944     }
6945     cl.pieceIn = EmptySquare;
6946     cl.rfIn = *y;
6947     cl.ffIn = *x;
6948     cl.rtIn = -1;
6949     cl.ftIn = -1;
6950     cl.promoCharIn = NULLCHAR;
6951     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6952     if( cl.kind == NormalMove ||
6953         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6954         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6955         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6956       fromX = cl.ff;
6957       fromY = cl.rf;
6958       *x = cl.ft;
6959       *y = cl.rt;
6960       return TRUE;
6961     }
6962     if(cl.kind != ImpossibleMove) return FALSE;
6963     cl.pieceIn = EmptySquare;
6964     cl.rfIn = -1;
6965     cl.ffIn = -1;
6966     cl.rtIn = *y;
6967     cl.ftIn = *x;
6968     cl.promoCharIn = NULLCHAR;
6969     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6970     if( cl.kind == NormalMove ||
6971         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6972         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6973         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6974       fromX = cl.ff;
6975       fromY = cl.rf;
6976       *x = cl.ft;
6977       *y = cl.rt;
6978       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6979       return TRUE;
6980     }
6981     return FALSE;
6982 }
6983
6984 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6985 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6986 int lastLoadGameUseList = FALSE;
6987 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6988 ChessMove lastLoadGameStart = EndOfFile;
6989 int doubleClick;
6990 Boolean addToBookFlag;
6991 static Board rightsBoard, nullBoard;
6992
6993 void
6994 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6995 {
6996     ChessMove moveType;
6997     ChessSquare pup;
6998     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6999
7000     /* Check if the user is playing in turn.  This is complicated because we
7001        let the user "pick up" a piece before it is his turn.  So the piece he
7002        tried to pick up may have been captured by the time he puts it down!
7003        Therefore we use the color the user is supposed to be playing in this
7004        test, not the color of the piece that is currently on the starting
7005        square---except in EditGame mode, where the user is playing both
7006        sides; fortunately there the capture race can't happen.  (It can
7007        now happen in IcsExamining mode, but that's just too bad.  The user
7008        will get a somewhat confusing message in that case.)
7009        */
7010
7011     switch (gameMode) {
7012       case AnalyzeFile:
7013       case TwoMachinesPlay:
7014       case EndOfGame:
7015       case IcsObserving:
7016       case IcsIdle:
7017         /* We switched into a game mode where moves are not accepted,
7018            perhaps while the mouse button was down. */
7019         return;
7020
7021       case MachinePlaysWhite:
7022         /* User is moving for Black */
7023         if (WhiteOnMove(currentMove)) {
7024             DisplayMoveError(_("It is White's turn"));
7025             return;
7026         }
7027         break;
7028
7029       case MachinePlaysBlack:
7030         /* User is moving for White */
7031         if (!WhiteOnMove(currentMove)) {
7032             DisplayMoveError(_("It is Black's turn"));
7033             return;
7034         }
7035         break;
7036
7037       case PlayFromGameFile:
7038             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7039       case EditGame:
7040       case IcsExamining:
7041       case BeginningOfGame:
7042       case AnalyzeMode:
7043       case Training:
7044         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7045         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7046             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7047             /* User is moving for Black */
7048             if (WhiteOnMove(currentMove)) {
7049                 DisplayMoveError(_("It is White's turn"));
7050                 return;
7051             }
7052         } else {
7053             /* User is moving for White */
7054             if (!WhiteOnMove(currentMove)) {
7055                 DisplayMoveError(_("It is Black's turn"));
7056                 return;
7057             }
7058         }
7059         break;
7060
7061       case IcsPlayingBlack:
7062         /* User is moving for Black */
7063         if (WhiteOnMove(currentMove)) {
7064             if (!appData.premove) {
7065                 DisplayMoveError(_("It is White's turn"));
7066             } else if (toX >= 0 && toY >= 0) {
7067                 premoveToX = toX;
7068                 premoveToY = toY;
7069                 premoveFromX = fromX;
7070                 premoveFromY = fromY;
7071                 premovePromoChar = promoChar;
7072                 gotPremove = 1;
7073                 if (appData.debugMode)
7074                     fprintf(debugFP, "Got premove: fromX %d,"
7075                             "fromY %d, toX %d, toY %d\n",
7076                             fromX, fromY, toX, toY);
7077             }
7078             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7079             return;
7080         }
7081         break;
7082
7083       case IcsPlayingWhite:
7084         /* User is moving for White */
7085         if (!WhiteOnMove(currentMove)) {
7086             if (!appData.premove) {
7087                 DisplayMoveError(_("It is Black's turn"));
7088             } else if (toX >= 0 && toY >= 0) {
7089                 premoveToX = toX;
7090                 premoveToY = toY;
7091                 premoveFromX = fromX;
7092                 premoveFromY = fromY;
7093                 premovePromoChar = promoChar;
7094                 gotPremove = 1;
7095                 if (appData.debugMode)
7096                     fprintf(debugFP, "Got premove: fromX %d,"
7097                             "fromY %d, toX %d, toY %d\n",
7098                             fromX, fromY, toX, toY);
7099             }
7100             DrawPosition(TRUE, boards[currentMove]);
7101             return;
7102         }
7103         break;
7104
7105       default:
7106         break;
7107
7108       case EditPosition:
7109         /* EditPosition, empty square, or different color piece;
7110            click-click move is possible */
7111         if (toX == -2 || toY == -2) {
7112             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7113             DrawPosition(FALSE, boards[currentMove]);
7114             return;
7115         } else if (toX >= 0 && toY >= 0) {
7116             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7117                 ChessSquare p = boards[0][rf][ff];
7118                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7119                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7120                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7121                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7122                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7123                     gatingPiece = p;
7124                 }
7125             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7126             boards[0][toY][toX] = boards[0][fromY][fromX];
7127             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7128                 if(boards[0][fromY][0] != EmptySquare) {
7129                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7130                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7131                 }
7132             } else
7133             if(fromX == BOARD_RGHT+1) {
7134                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7135                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7136                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7137                 }
7138             } else
7139             boards[0][fromY][fromX] = gatingPiece;
7140             ClearHighlights();
7141             DrawPosition(FALSE, boards[currentMove]);
7142             return;
7143         }
7144         return;
7145     }
7146
7147     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7148     pup = boards[currentMove][toY][toX];
7149
7150     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7151     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7152          if( pup != EmptySquare ) return;
7153          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7154            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7155                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7156            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7157            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7158            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7159            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7160          fromY = DROP_RANK;
7161     }
7162
7163     /* [HGM] always test for legality, to get promotion info */
7164     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7165                                          fromY, fromX, toY, toX, promoChar);
7166
7167     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7168
7169     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7170
7171     /* [HGM] but possibly ignore an IllegalMove result */
7172     if (appData.testLegality) {
7173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7174             DisplayMoveError(_("Illegal move"));
7175             return;
7176         }
7177     }
7178
7179     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7180         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7181              ClearPremoveHighlights(); // was included
7182         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7183         DrawPosition(FALSE, NULL);
7184         return;
7185     }
7186
7187     if(addToBookFlag) { // adding moves to book
7188         char buf[MSG_SIZ], move[MSG_SIZ];
7189         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7190         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7191                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7192         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7193         AddBookMove(buf);
7194         addToBookFlag = FALSE;
7195         ClearHighlights();
7196         return;
7197     }
7198
7199     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7200 }
7201
7202 /* Common tail of UserMoveEvent and DropMenuEvent */
7203 int
7204 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7205 {
7206     char *bookHit = 0;
7207
7208     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7209         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7210         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7211         if(WhiteOnMove(currentMove)) {
7212             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7213         } else {
7214             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7215         }
7216     }
7217
7218     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7219        move type in caller when we know the move is a legal promotion */
7220     if(moveType == NormalMove && promoChar)
7221         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7222
7223     /* [HGM] <popupFix> The following if has been moved here from
7224        UserMoveEvent(). Because it seemed to belong here (why not allow
7225        piece drops in training games?), and because it can only be
7226        performed after it is known to what we promote. */
7227     if (gameMode == Training) {
7228       /* compare the move played on the board to the next move in the
7229        * game. If they match, display the move and the opponent's response.
7230        * If they don't match, display an error message.
7231        */
7232       int saveAnimate;
7233       Board testBoard;
7234       CopyBoard(testBoard, boards[currentMove]);
7235       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7236
7237       if (CompareBoards(testBoard, boards[currentMove+1])) {
7238         ForwardInner(currentMove+1);
7239
7240         /* Autoplay the opponent's response.
7241          * if appData.animate was TRUE when Training mode was entered,
7242          * the response will be animated.
7243          */
7244         saveAnimate = appData.animate;
7245         appData.animate = animateTraining;
7246         ForwardInner(currentMove+1);
7247         appData.animate = saveAnimate;
7248
7249         /* check for the end of the game */
7250         if (currentMove >= forwardMostMove) {
7251           gameMode = PlayFromGameFile;
7252           ModeHighlight();
7253           SetTrainingModeOff();
7254           DisplayInformation(_("End of game"));
7255         }
7256       } else {
7257         DisplayError(_("Incorrect move"), 0);
7258       }
7259       return 1;
7260     }
7261
7262   /* Ok, now we know that the move is good, so we can kill
7263      the previous line in Analysis Mode */
7264   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7265                                 && currentMove < forwardMostMove) {
7266     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7267     else forwardMostMove = currentMove;
7268   }
7269
7270   ClearMap();
7271
7272   /* If we need the chess program but it's dead, restart it */
7273   ResurrectChessProgram();
7274
7275   /* A user move restarts a paused game*/
7276   if (pausing)
7277     PauseEvent();
7278
7279   thinkOutput[0] = NULLCHAR;
7280
7281   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7282
7283   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7284     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7285     return 1;
7286   }
7287
7288   if (gameMode == BeginningOfGame) {
7289     if (appData.noChessProgram) {
7290       gameMode = EditGame;
7291       SetGameInfo();
7292     } else {
7293       char buf[MSG_SIZ];
7294       gameMode = MachinePlaysBlack;
7295       StartClocks();
7296       SetGameInfo();
7297       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7298       DisplayTitle(buf);
7299       if (first.sendName) {
7300         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7301         SendToProgram(buf, &first);
7302       }
7303       StartClocks();
7304     }
7305     ModeHighlight();
7306   }
7307
7308   /* Relay move to ICS or chess engine */
7309   if (appData.icsActive) {
7310     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7311         gameMode == IcsExamining) {
7312       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7313         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7314         SendToICS("draw ");
7315         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7316       }
7317       // also send plain move, in case ICS does not understand atomic claims
7318       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7319       ics_user_moved = 1;
7320     }
7321   } else {
7322     if (first.sendTime && (gameMode == BeginningOfGame ||
7323                            gameMode == MachinePlaysWhite ||
7324                            gameMode == MachinePlaysBlack)) {
7325       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7326     }
7327     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7328          // [HGM] book: if program might be playing, let it use book
7329         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7330         first.maybeThinking = TRUE;
7331     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7332         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7333         SendBoard(&first, currentMove+1);
7334         if(second.analyzing) {
7335             if(!second.useSetboard) SendToProgram("undo\n", &second);
7336             SendBoard(&second, currentMove+1);
7337         }
7338     } else {
7339         SendMoveToProgram(forwardMostMove-1, &first);
7340         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7341     }
7342     if (currentMove == cmailOldMove + 1) {
7343       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7344     }
7345   }
7346
7347   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7348
7349   switch (gameMode) {
7350   case EditGame:
7351     if(appData.testLegality)
7352     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7353     case MT_NONE:
7354     case MT_CHECK:
7355       break;
7356     case MT_CHECKMATE:
7357     case MT_STAINMATE:
7358       if (WhiteOnMove(currentMove)) {
7359         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7360       } else {
7361         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7362       }
7363       break;
7364     case MT_STALEMATE:
7365       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7366       break;
7367     }
7368     break;
7369
7370   case MachinePlaysBlack:
7371   case MachinePlaysWhite:
7372     /* disable certain menu options while machine is thinking */
7373     SetMachineThinkingEnables();
7374     break;
7375
7376   default:
7377     break;
7378   }
7379
7380   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7381   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7382
7383   if(bookHit) { // [HGM] book: simulate book reply
7384         static char bookMove[MSG_SIZ]; // a bit generous?
7385
7386         programStats.nodes = programStats.depth = programStats.time =
7387         programStats.score = programStats.got_only_move = 0;
7388         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7389
7390         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7391         strcat(bookMove, bookHit);
7392         HandleMachineMove(bookMove, &first);
7393   }
7394   return 1;
7395 }
7396
7397 void
7398 MarkByFEN(char *fen)
7399 {
7400         int r, f;
7401         if(!appData.markers || !appData.highlightDragging) return;
7402         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7403         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7404         while(*fen) {
7405             int s = 0;
7406             marker[r][f] = 0;
7407             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7408             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7409             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7410             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7411             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7412             if(*fen == 'T') marker[r][f++] = 0; else
7413             if(*fen == 'Y') marker[r][f++] = 1; else
7414             if(*fen == 'G') marker[r][f++] = 3; else
7415             if(*fen == 'B') marker[r][f++] = 4; else
7416             if(*fen == 'C') marker[r][f++] = 5; else
7417             if(*fen == 'M') marker[r][f++] = 6; else
7418             if(*fen == 'W') marker[r][f++] = 7; else
7419             if(*fen == 'D') marker[r][f++] = 8; else
7420             if(*fen == 'R') marker[r][f++] = 2; else {
7421                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7422               f += s; fen -= s>0;
7423             }
7424             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7425             if(r < 0) break;
7426             fen++;
7427         }
7428         DrawPosition(TRUE, NULL);
7429 }
7430
7431 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7432
7433 void
7434 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7435 {
7436     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7437     Markers *m = (Markers *) closure;
7438     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7439                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7440         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7441                          || kind == WhiteCapturesEnPassant
7442                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7443     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7444 }
7445
7446 static int hoverSavedValid;
7447
7448 void
7449 MarkTargetSquares (int clear)
7450 {
7451   int x, y, sum=0;
7452   if(clear) { // no reason to ever suppress clearing
7453     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7454     hoverSavedValid = 0;
7455     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7456   } else {
7457     int capt = 0;
7458     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7459        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7460     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7461     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7462       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7463       if(capt)
7464       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7465     }
7466   }
7467   DrawPosition(FALSE, NULL);
7468 }
7469
7470 int
7471 Explode (Board board, int fromX, int fromY, int toX, int toY)
7472 {
7473     if(gameInfo.variant == VariantAtomic &&
7474        (board[toY][toX] != EmptySquare ||                     // capture?
7475         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7476                          board[fromY][fromX] == BlackPawn   )
7477       )) {
7478         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7479         return TRUE;
7480     }
7481     return FALSE;
7482 }
7483
7484 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7485
7486 int
7487 CanPromote (ChessSquare piece, int y)
7488 {
7489         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7490         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7491         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7492         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7493            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7494           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7495            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7496         return (piece == BlackPawn && y <= zone ||
7497                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7498                 piece == BlackLance && y <= zone ||
7499                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7500 }
7501
7502 void
7503 HoverEvent (int xPix, int yPix, int x, int y)
7504 {
7505         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7506         int r, f;
7507         if(!first.highlight) return;
7508         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7509         if(x == oldX && y == oldY) return; // only do something if we enter new square
7510         oldFromX = fromX; oldFromY = fromY;
7511         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7512           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7513             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7514           hoverSavedValid = 1;
7515         } else if(oldX != x || oldY != y) {
7516           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7517           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7518           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7519             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7520           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7521             char buf[MSG_SIZ];
7522             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7523             SendToProgram(buf, &first);
7524           }
7525           oldX = x; oldY = y;
7526 //        SetHighlights(fromX, fromY, x, y);
7527         }
7528 }
7529
7530 void ReportClick(char *action, int x, int y)
7531 {
7532         char buf[MSG_SIZ]; // Inform engine of what user does
7533         int r, f;
7534         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7535           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7536             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7537         if(!first.highlight || gameMode == EditPosition) return;
7538         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7539         SendToProgram(buf, &first);
7540 }
7541
7542 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7543 Boolean deferChoice;
7544
7545 void
7546 LeftClick (ClickType clickType, int xPix, int yPix)
7547 {
7548     int x, y;
7549     static Boolean saveAnimate;
7550     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7551     char promoChoice = NULLCHAR;
7552     ChessSquare piece;
7553     static TimeMark lastClickTime, prevClickTime;
7554
7555     if(flashing) return;
7556
7557   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7558     x = EventToSquare(xPix, BOARD_WIDTH);
7559     y = EventToSquare(yPix, BOARD_HEIGHT);
7560     if (!flipView && y >= 0) {
7561         y = BOARD_HEIGHT - 1 - y;
7562     }
7563     if (flipView && x >= 0) {
7564         x = BOARD_WIDTH - 1 - x;
7565     }
7566
7567     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7568         static int dummy;
7569         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7570         right = TRUE;
7571         return;
7572     }
7573
7574     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7575
7576     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7577
7578     if (clickType == Press) ErrorPopDown();
7579     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7580
7581     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7582         defaultPromoChoice = promoSweep;
7583         promoSweep = EmptySquare;   // terminate sweep
7584         promoDefaultAltered = TRUE;
7585         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7586     }
7587
7588     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7589         if(clickType == Release) return; // ignore upclick of click-click destination
7590         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7591         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7592         if(gameInfo.holdingsWidth &&
7593                 (WhiteOnMove(currentMove)
7594                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7595                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7596             // click in right holdings, for determining promotion piece
7597             ChessSquare p = boards[currentMove][y][x];
7598             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7599             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7600             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7601                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7602                 fromX = fromY = -1;
7603                 return;
7604             }
7605         }
7606         DrawPosition(FALSE, boards[currentMove]);
7607         return;
7608     }
7609
7610     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7611     if(clickType == Press
7612             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7613               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7614               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7615         return;
7616
7617     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7618         // could be static click on premove from-square: abort premove
7619         gotPremove = 0;
7620         ClearPremoveHighlights();
7621     }
7622
7623     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7624         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7625
7626     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7627         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7628                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7629         defaultPromoChoice = DefaultPromoChoice(side);
7630     }
7631
7632     autoQueen = appData.alwaysPromoteToQueen;
7633
7634     if (fromX == -1) {
7635       int originalY = y;
7636       gatingPiece = EmptySquare;
7637       if (clickType != Press) {
7638         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7639             DragPieceEnd(xPix, yPix); dragging = 0;
7640             DrawPosition(FALSE, NULL);
7641         }
7642         return;
7643       }
7644       doubleClick = FALSE;
7645       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7646         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7647       }
7648       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7649       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7650          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7651          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7652             /* First square */
7653             if (OKToStartUserMove(fromX, fromY)) {
7654                 second = 0;
7655                 ReportClick("lift", x, y);
7656                 MarkTargetSquares(0);
7657                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7658                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7659                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7660                     promoSweep = defaultPromoChoice;
7661                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7662                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7663                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7664                 }
7665                 if (appData.highlightDragging) {
7666                     SetHighlights(fromX, fromY, -1, -1);
7667                 } else {
7668                     ClearHighlights();
7669                 }
7670             } else fromX = fromY = -1;
7671             return;
7672         }
7673     }
7674
7675     /* fromX != -1 */
7676     if (clickType == Press && gameMode != EditPosition) {
7677         ChessSquare fromP;
7678         ChessSquare toP;
7679         int frc;
7680
7681         // ignore off-board to clicks
7682         if(y < 0 || x < 0) return;
7683
7684         /* Check if clicking again on the same color piece */
7685         fromP = boards[currentMove][fromY][fromX];
7686         toP = boards[currentMove][y][x];
7687         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7688         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7689             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7690            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7691              WhitePawn <= toP && toP <= WhiteKing &&
7692              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7693              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7694             (BlackPawn <= fromP && fromP <= BlackKing &&
7695              BlackPawn <= toP && toP <= BlackKing &&
7696              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7697              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7698             /* Clicked again on same color piece -- changed his mind */
7699             second = (x == fromX && y == fromY);
7700             killX = killY = kill2X = kill2Y = -1;
7701             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7702                 second = FALSE; // first double-click rather than scond click
7703                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7704             }
7705             promoDefaultAltered = FALSE;
7706            if(!second) MarkTargetSquares(1);
7707            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7708             if (appData.highlightDragging) {
7709                 SetHighlights(x, y, -1, -1);
7710             } else {
7711                 ClearHighlights();
7712             }
7713             if (OKToStartUserMove(x, y)) {
7714                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7715                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7716                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7717                  gatingPiece = boards[currentMove][fromY][fromX];
7718                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7719                 fromX = x;
7720                 fromY = y; dragging = 1;
7721                 if(!second) ReportClick("lift", x, y);
7722                 MarkTargetSquares(0);
7723                 DragPieceBegin(xPix, yPix, FALSE);
7724                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7725                     promoSweep = defaultPromoChoice;
7726                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7727                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7728                 }
7729             }
7730            }
7731            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7732            second = FALSE;
7733         }
7734         // ignore clicks on holdings
7735         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7736     }
7737
7738     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7739         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7740         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7741         return;
7742     }
7743
7744     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7745         DragPieceEnd(xPix, yPix); dragging = 0;
7746         if(clearFlag) {
7747             // a deferred attempt to click-click move an empty square on top of a piece
7748             boards[currentMove][y][x] = EmptySquare;
7749             ClearHighlights();
7750             DrawPosition(FALSE, boards[currentMove]);
7751             fromX = fromY = -1; clearFlag = 0;
7752             return;
7753         }
7754         if (appData.animateDragging) {
7755             /* Undo animation damage if any */
7756             DrawPosition(FALSE, NULL);
7757         }
7758         if (second) {
7759             /* Second up/down in same square; just abort move */
7760             second = 0;
7761             fromX = fromY = -1;
7762             gatingPiece = EmptySquare;
7763             ClearHighlights();
7764             gotPremove = 0;
7765             ClearPremoveHighlights();
7766             MarkTargetSquares(-1);
7767             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7768         } else {
7769             /* First upclick in same square; start click-click mode */
7770             SetHighlights(x, y, -1, -1);
7771         }
7772         return;
7773     }
7774
7775     clearFlag = 0;
7776
7777     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7778        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7779         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7780         DisplayMessage(_("only marked squares are legal"),"");
7781         DrawPosition(TRUE, NULL);
7782         return; // ignore to-click
7783     }
7784
7785     /* we now have a different from- and (possibly off-board) to-square */
7786     /* Completed move */
7787     if(!sweepSelecting) {
7788         toX = x;
7789         toY = y;
7790     }
7791
7792     piece = boards[currentMove][fromY][fromX];
7793
7794     saveAnimate = appData.animate;
7795     if (clickType == Press) {
7796         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7797         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7798             // must be Edit Position mode with empty-square selected
7799             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7800             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7801             return;
7802         }
7803         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7804             return;
7805         }
7806         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7807             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7808         } else
7809         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7810         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7811           if(appData.sweepSelect) {
7812             promoSweep = defaultPromoChoice;
7813             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7814             selectFlag = 0; lastX = xPix; lastY = yPix;
7815             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7816             saveFlash = appData.flashCount; appData.flashCount = 0;
7817             Sweep(0); // Pawn that is going to promote: preview promotion piece
7818             sweepSelecting = 1;
7819             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7820             MarkTargetSquares(1);
7821           }
7822           return; // promo popup appears on up-click
7823         }
7824         /* Finish clickclick move */
7825         if (appData.animate || appData.highlightLastMove) {
7826             SetHighlights(fromX, fromY, toX, toY);
7827         } else {
7828             ClearHighlights();
7829         }
7830         MarkTargetSquares(1);
7831     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7832         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7833         *promoRestrict = 0; appData.flashCount = saveFlash;
7834         if (appData.animate || appData.highlightLastMove) {
7835             SetHighlights(fromX, fromY, toX, toY);
7836         } else {
7837             ClearHighlights();
7838         }
7839         MarkTargetSquares(1);
7840     } else {
7841 #if 0
7842 // [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
7843         /* Finish drag move */
7844         if (appData.highlightLastMove) {
7845             SetHighlights(fromX, fromY, toX, toY);
7846         } else {
7847             ClearHighlights();
7848         }
7849 #endif
7850         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7851           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7852         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7853         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7854           dragging *= 2;            // flag button-less dragging if we are dragging
7855           MarkTargetSquares(1);
7856           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7857           else {
7858             kill2X = killX; kill2Y = killY;
7859             killX = x; killY = y;     // remember this square as intermediate
7860             ReportClick("put", x, y); // and inform engine
7861             ReportClick("lift", x, y);
7862             MarkTargetSquares(0);
7863             return;
7864           }
7865         }
7866         DragPieceEnd(xPix, yPix); dragging = 0;
7867         /* Don't animate move and drag both */
7868         appData.animate = FALSE;
7869         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7870     }
7871
7872     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7873     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7874         ChessSquare piece = boards[currentMove][fromY][fromX];
7875         if(gameMode == EditPosition && piece != EmptySquare &&
7876            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7877             int n;
7878
7879             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7880                 n = PieceToNumber(piece - (int)BlackPawn);
7881                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7882                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7883                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7884             } else
7885             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7886                 n = PieceToNumber(piece);
7887                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7888                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7889                 boards[currentMove][n][BOARD_WIDTH-2]++;
7890             }
7891             boards[currentMove][fromY][fromX] = EmptySquare;
7892         }
7893         ClearHighlights();
7894         fromX = fromY = -1;
7895         MarkTargetSquares(1);
7896         DrawPosition(TRUE, boards[currentMove]);
7897         return;
7898     }
7899
7900     // off-board moves should not be highlighted
7901     if(x < 0 || y < 0) ClearHighlights();
7902     else ReportClick("put", x, y);
7903
7904     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7905  }
7906
7907     if(legal[toY][toX] == 2) { // highlight-induced promotion
7908         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7909         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7910     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7911       if(!*promoRestrict) {           // but has not done that yet
7912         deferChoice = TRUE;           // set up retry for when it does
7913         return;                       // and wait for that
7914       }
7915       promoChoice = ToLower(*promoRestrict); // force engine's choice
7916       deferChoice = FALSE;
7917     }
7918
7919     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7920         SetHighlights(fromX, fromY, toX, toY);
7921         MarkTargetSquares(1);
7922         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7923             // [HGM] super: promotion to captured piece selected from holdings
7924             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7925             promotionChoice = TRUE;
7926             // kludge follows to temporarily execute move on display, without promoting yet
7927             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7928             boards[currentMove][toY][toX] = p;
7929             DrawPosition(FALSE, boards[currentMove]);
7930             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7931             boards[currentMove][toY][toX] = q;
7932             DisplayMessage("Click in holdings to choose piece", "");
7933             return;
7934         }
7935         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7936         PromotionPopUp(promoChoice);
7937     } else {
7938         int oldMove = currentMove;
7939         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7940         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7941         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7942         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7943         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7944            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7945             DrawPosition(TRUE, boards[currentMove]);
7946         fromX = fromY = -1;
7947         flashing = 0;
7948     }
7949     appData.animate = saveAnimate;
7950     if (appData.animate || appData.animateDragging) {
7951         /* Undo animation damage if needed */
7952 //      DrawPosition(FALSE, NULL);
7953     }
7954 }
7955
7956 int
7957 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7958 {   // front-end-free part taken out of PieceMenuPopup
7959     int whichMenu; int xSqr, ySqr;
7960
7961     if(seekGraphUp) { // [HGM] seekgraph
7962         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7963         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7964         return -2;
7965     }
7966
7967     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7968          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7969         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7970         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7971         if(action == Press)   {
7972             originalFlip = flipView;
7973             flipView = !flipView; // temporarily flip board to see game from partners perspective
7974             DrawPosition(TRUE, partnerBoard);
7975             DisplayMessage(partnerStatus, "");
7976             partnerUp = TRUE;
7977         } else if(action == Release) {
7978             flipView = originalFlip;
7979             DrawPosition(TRUE, boards[currentMove]);
7980             partnerUp = FALSE;
7981         }
7982         return -2;
7983     }
7984
7985     xSqr = EventToSquare(x, BOARD_WIDTH);
7986     ySqr = EventToSquare(y, BOARD_HEIGHT);
7987     if (action == Release) {
7988         if(pieceSweep != EmptySquare) {
7989             EditPositionMenuEvent(pieceSweep, toX, toY);
7990             pieceSweep = EmptySquare;
7991         } else UnLoadPV(); // [HGM] pv
7992     }
7993     if (action != Press) return -2; // return code to be ignored
7994     switch (gameMode) {
7995       case IcsExamining:
7996         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7997       case EditPosition:
7998         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7999         if (xSqr < 0 || ySqr < 0) return -1;
8000         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8001         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8002         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8003         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8004         NextPiece(0);
8005         return 2; // grab
8006       case IcsObserving:
8007         if(!appData.icsEngineAnalyze) return -1;
8008       case IcsPlayingWhite:
8009       case IcsPlayingBlack:
8010         if(!appData.zippyPlay) goto noZip;
8011       case AnalyzeMode:
8012       case AnalyzeFile:
8013       case MachinePlaysWhite:
8014       case MachinePlaysBlack:
8015       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8016         if (!appData.dropMenu) {
8017           LoadPV(x, y);
8018           return 2; // flag front-end to grab mouse events
8019         }
8020         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8021            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8022       case EditGame:
8023       noZip:
8024         if (xSqr < 0 || ySqr < 0) return -1;
8025         if (!appData.dropMenu || appData.testLegality &&
8026             gameInfo.variant != VariantBughouse &&
8027             gameInfo.variant != VariantCrazyhouse) return -1;
8028         whichMenu = 1; // drop menu
8029         break;
8030       default:
8031         return -1;
8032     }
8033
8034     if (((*fromX = xSqr) < 0) ||
8035         ((*fromY = ySqr) < 0)) {
8036         *fromX = *fromY = -1;
8037         return -1;
8038     }
8039     if (flipView)
8040       *fromX = BOARD_WIDTH - 1 - *fromX;
8041     else
8042       *fromY = BOARD_HEIGHT - 1 - *fromY;
8043
8044     return whichMenu;
8045 }
8046
8047 void
8048 Wheel (int dir, int x, int y)
8049 {
8050     if(gameMode == EditPosition) {
8051         int xSqr = EventToSquare(x, BOARD_WIDTH);
8052         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8053         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8054         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8055         do {
8056             boards[currentMove][ySqr][xSqr] += dir;
8057             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8058             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8059         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8060         DrawPosition(FALSE, boards[currentMove]);
8061     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8062 }
8063
8064 void
8065 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8066 {
8067 //    char * hint = lastHint;
8068     FrontEndProgramStats stats;
8069
8070     stats.which = cps == &first ? 0 : 1;
8071     stats.depth = cpstats->depth;
8072     stats.nodes = cpstats->nodes;
8073     stats.score = cpstats->score;
8074     stats.time = cpstats->time;
8075     stats.pv = cpstats->movelist;
8076     stats.hint = lastHint;
8077     stats.an_move_index = 0;
8078     stats.an_move_count = 0;
8079
8080     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8081         stats.hint = cpstats->move_name;
8082         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8083         stats.an_move_count = cpstats->nr_moves;
8084     }
8085
8086     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
8087
8088     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8089         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8090
8091     SetProgramStats( &stats );
8092 }
8093
8094 void
8095 ClearEngineOutputPane (int which)
8096 {
8097     static FrontEndProgramStats dummyStats;
8098     dummyStats.which = which;
8099     dummyStats.pv = "#";
8100     SetProgramStats( &dummyStats );
8101 }
8102
8103 #define MAXPLAYERS 500
8104
8105 char *
8106 TourneyStandings (int display)
8107 {
8108     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8109     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8110     char result, *p, *names[MAXPLAYERS];
8111
8112     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8113         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8114     names[0] = p = strdup(appData.participants);
8115     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8116
8117     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8118
8119     while(result = appData.results[nr]) {
8120         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8121         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8122         wScore = bScore = 0;
8123         switch(result) {
8124           case '+': wScore = 2; break;
8125           case '-': bScore = 2; break;
8126           case '=': wScore = bScore = 1; break;
8127           case ' ':
8128           case '*': return strdup("busy"); // tourney not finished
8129         }
8130         score[w] += wScore;
8131         score[b] += bScore;
8132         games[w]++;
8133         games[b]++;
8134         nr++;
8135     }
8136     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8137     for(w=0; w<nPlayers; w++) {
8138         bScore = -1;
8139         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8140         ranking[w] = b; points[w] = bScore; score[b] = -2;
8141     }
8142     p = malloc(nPlayers*34+1);
8143     for(w=0; w<nPlayers && w<display; w++)
8144         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8145     free(names[0]);
8146     return p;
8147 }
8148
8149 void
8150 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8151 {       // count all piece types
8152         int p, f, r;
8153         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8154         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8155         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8156                 p = board[r][f];
8157                 pCnt[p]++;
8158                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8159                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8160                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8161                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8162                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8163                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8164         }
8165 }
8166
8167 int
8168 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8169 {
8170         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8171         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8172
8173         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8174         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8175         if(myPawns == 2 && nMine == 3) // KPP
8176             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8177         if(myPawns == 1 && nMine == 2) // KP
8178             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8179         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8180             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8181         if(myPawns) return FALSE;
8182         if(pCnt[WhiteRook+side])
8183             return pCnt[BlackRook-side] ||
8184                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8185                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8186                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8187         if(pCnt[WhiteCannon+side]) {
8188             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8189             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8190         }
8191         if(pCnt[WhiteKnight+side])
8192             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8193         return FALSE;
8194 }
8195
8196 int
8197 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8198 {
8199         VariantClass v = gameInfo.variant;
8200
8201         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8202         if(v == VariantShatranj) return TRUE; // always winnable through baring
8203         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8204         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8205
8206         if(v == VariantXiangqi) {
8207                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8208
8209                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8210                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8211                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8212                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8213                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8214                 if(stale) // we have at least one last-rank P plus perhaps C
8215                     return majors // KPKX
8216                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8217                 else // KCA*E*
8218                     return pCnt[WhiteFerz+side] // KCAK
8219                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8220                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8221                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8222
8223         } else if(v == VariantKnightmate) {
8224                 if(nMine == 1) return FALSE;
8225                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8226         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8227                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8228
8229                 if(nMine == 1) return FALSE; // bare King
8230                 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
8231                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8232                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8233                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8234                 if(pCnt[WhiteKnight+side])
8235                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8236                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8237                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8238                 if(nBishops)
8239                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8240                 if(pCnt[WhiteAlfil+side])
8241                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8242                 if(pCnt[WhiteWazir+side])
8243                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8244         }
8245
8246         return TRUE;
8247 }
8248
8249 int
8250 CompareWithRights (Board b1, Board b2)
8251 {
8252     int rights = 0;
8253     if(!CompareBoards(b1, b2)) return FALSE;
8254     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8255     /* compare castling rights */
8256     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8257            rights++; /* King lost rights, while rook still had them */
8258     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8259         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8260            rights++; /* but at least one rook lost them */
8261     }
8262     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8263            rights++;
8264     if( b1[CASTLING][5] != NoRights ) {
8265         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8266            rights++;
8267     }
8268     return rights == 0;
8269 }
8270
8271 int
8272 Adjudicate (ChessProgramState *cps)
8273 {       // [HGM] some adjudications useful with buggy engines
8274         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8275         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8276         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8277         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8278         int k, drop, count = 0; static int bare = 1;
8279         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8280         Boolean canAdjudicate = !appData.icsActive;
8281
8282         // most tests only when we understand the game, i.e. legality-checking on
8283             if( appData.testLegality )
8284             {   /* [HGM] Some more adjudications for obstinate engines */
8285                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8286                 static int moveCount = 6;
8287                 ChessMove result;
8288                 char *reason = NULL;
8289
8290                 /* Count what is on board. */
8291                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8292
8293                 /* Some material-based adjudications that have to be made before stalemate test */
8294                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8295                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8296                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8297                      if(canAdjudicate && appData.checkMates) {
8298                          if(engineOpponent)
8299                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8300                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8301                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8302                          return 1;
8303                      }
8304                 }
8305
8306                 /* Bare King in Shatranj (loses) or Losers (wins) */
8307                 if( nrW == 1 || nrB == 1) {
8308                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8309                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8310                      if(canAdjudicate && appData.checkMates) {
8311                          if(engineOpponent)
8312                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8313                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8314                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8315                          return 1;
8316                      }
8317                   } else
8318                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8319                   {    /* bare King */
8320                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8321                         if(canAdjudicate && appData.checkMates) {
8322                             /* but only adjudicate if adjudication enabled */
8323                             if(engineOpponent)
8324                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8325                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8326                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8327                             return 1;
8328                         }
8329                   }
8330                 } else bare = 1;
8331
8332
8333             // don't wait for engine to announce game end if we can judge ourselves
8334             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8335               case MT_CHECK:
8336                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8337                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8338                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8339                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8340                             checkCnt++;
8341                         if(checkCnt >= 2) {
8342                             reason = "Xboard adjudication: 3rd check";
8343                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8344                             break;
8345                         }
8346                     }
8347                 }
8348               case MT_NONE:
8349               default:
8350                 break;
8351               case MT_STEALMATE:
8352               case MT_STALEMATE:
8353               case MT_STAINMATE:
8354                 reason = "Xboard adjudication: Stalemate";
8355                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8356                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8357                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8358                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8359                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8360                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8361                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8362                                                                         EP_CHECKMATE : EP_WINS);
8363                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8364                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8365                 }
8366                 break;
8367               case MT_CHECKMATE:
8368                 reason = "Xboard adjudication: Checkmate";
8369                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8370                 if(gameInfo.variant == VariantShogi) {
8371                     if(forwardMostMove > backwardMostMove
8372                        && moveList[forwardMostMove-1][1] == '@'
8373                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8374                         reason = "XBoard adjudication: pawn-drop mate";
8375                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8376                     }
8377                 }
8378                 break;
8379             }
8380
8381                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8382                     case EP_STALEMATE:
8383                         result = GameIsDrawn; break;
8384                     case EP_CHECKMATE:
8385                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8386                     case EP_WINS:
8387                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8388                     default:
8389                         result = EndOfFile;
8390                 }
8391                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8392                     if(engineOpponent)
8393                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8394                     GameEnds( result, reason, GE_XBOARD );
8395                     return 1;
8396                 }
8397
8398                 /* Next absolutely insufficient mating material. */
8399                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8400                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8401                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8402
8403                      /* always flag draws, for judging claims */
8404                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8405
8406                      if(canAdjudicate && appData.materialDraws) {
8407                          /* but only adjudicate them if adjudication enabled */
8408                          if(engineOpponent) {
8409                            SendToProgram("force\n", engineOpponent); // suppress reply
8410                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8411                          }
8412                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8413                          return 1;
8414                      }
8415                 }
8416
8417                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8418                 if(gameInfo.variant == VariantXiangqi ?
8419                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8420                  : nrW + nrB == 4 &&
8421                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8422                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8423                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8424                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8425                    ) ) {
8426                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8427                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8428                           if(engineOpponent) {
8429                             SendToProgram("force\n", engineOpponent); // suppress reply
8430                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8431                           }
8432                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8433                           return 1;
8434                      }
8435                 } else moveCount = 6;
8436             }
8437
8438         // Repetition draws and 50-move rule can be applied independently of legality testing
8439
8440                 /* Check for rep-draws */
8441                 count = 0;
8442                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8443                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8444                 for(k = forwardMostMove-2;
8445                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8446                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8447                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8448                     k-=2)
8449                 {   int rights=0;
8450                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8451                         /* compare castling rights */
8452                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8453                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8454                                 rights++; /* King lost rights, while rook still had them */
8455                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8456                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8457                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8458                                    rights++; /* but at least one rook lost them */
8459                         }
8460                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8461                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8462                                 rights++;
8463                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8464                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8465                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8466                                    rights++;
8467                         }
8468                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8469                             && appData.drawRepeats > 1) {
8470                              /* adjudicate after user-specified nr of repeats */
8471                              int result = GameIsDrawn;
8472                              char *details = "XBoard adjudication: repetition draw";
8473                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8474                                 // [HGM] xiangqi: check for forbidden perpetuals
8475                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8476                                 for(m=forwardMostMove; m>k; m-=2) {
8477                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8478                                         ourPerpetual = 0; // the current mover did not always check
8479                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8480                                         hisPerpetual = 0; // the opponent did not always check
8481                                 }
8482                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8483                                                                         ourPerpetual, hisPerpetual);
8484                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8485                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8486                                     details = "Xboard adjudication: perpetual checking";
8487                                 } else
8488                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8489                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8490                                 } else
8491                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8492                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8493                                         result = BlackWins;
8494                                         details = "Xboard adjudication: repetition";
8495                                     }
8496                                 } else // it must be XQ
8497                                 // Now check for perpetual chases
8498                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8499                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8500                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8501                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8502                                         static char resdet[MSG_SIZ];
8503                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8504                                         details = resdet;
8505                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8506                                     } else
8507                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8508                                         break; // Abort repetition-checking loop.
8509                                 }
8510                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8511                              }
8512                              if(engineOpponent) {
8513                                SendToProgram("force\n", engineOpponent); // suppress reply
8514                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8515                              }
8516                              GameEnds( result, details, GE_XBOARD );
8517                              return 1;
8518                         }
8519                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8520                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8521                     }
8522                 }
8523
8524                 /* Now we test for 50-move draws. Determine ply count */
8525                 count = forwardMostMove;
8526                 /* look for last irreversble move */
8527                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8528                     count--;
8529                 /* if we hit starting position, add initial plies */
8530                 if( count == backwardMostMove )
8531                     count -= initialRulePlies;
8532                 count = forwardMostMove - count;
8533                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8534                         // adjust reversible move counter for checks in Xiangqi
8535                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8536                         if(i < backwardMostMove) i = backwardMostMove;
8537                         while(i <= forwardMostMove) {
8538                                 lastCheck = inCheck; // check evasion does not count
8539                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8540                                 if(inCheck || lastCheck) count--; // check does not count
8541                                 i++;
8542                         }
8543                 }
8544                 if( count >= 100)
8545                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8546                          /* this is used to judge if draw claims are legal */
8547                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8548                          if(engineOpponent) {
8549                            SendToProgram("force\n", engineOpponent); // suppress reply
8550                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8551                          }
8552                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8553                          return 1;
8554                 }
8555
8556                 /* if draw offer is pending, treat it as a draw claim
8557                  * when draw condition present, to allow engines a way to
8558                  * claim draws before making their move to avoid a race
8559                  * condition occurring after their move
8560                  */
8561                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8562                          char *p = NULL;
8563                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8564                              p = "Draw claim: 50-move rule";
8565                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8566                              p = "Draw claim: 3-fold repetition";
8567                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8568                              p = "Draw claim: insufficient mating material";
8569                          if( p != NULL && canAdjudicate) {
8570                              if(engineOpponent) {
8571                                SendToProgram("force\n", engineOpponent); // suppress reply
8572                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8573                              }
8574                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8575                              return 1;
8576                          }
8577                 }
8578
8579                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8580                     if(engineOpponent) {
8581                       SendToProgram("force\n", engineOpponent); // suppress reply
8582                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8583                     }
8584                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8585                     return 1;
8586                 }
8587         return 0;
8588 }
8589
8590 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8591 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8592 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8593
8594 static int
8595 BitbaseProbe ()
8596 {
8597     int pieces[10], squares[10], cnt=0, r, f, res;
8598     static int loaded;
8599     static PPROBE_EGBB probeBB;
8600     if(!appData.testLegality) return 10;
8601     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8602     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8603     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8604     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8605         ChessSquare piece = boards[forwardMostMove][r][f];
8606         int black = (piece >= BlackPawn);
8607         int type = piece - black*BlackPawn;
8608         if(piece == EmptySquare) continue;
8609         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8610         if(type == WhiteKing) type = WhiteQueen + 1;
8611         type = egbbCode[type];
8612         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8613         pieces[cnt] = type + black*6;
8614         if(++cnt > 5) return 11;
8615     }
8616     pieces[cnt] = squares[cnt] = 0;
8617     // probe EGBB
8618     if(loaded == 2) return 13; // loading failed before
8619     if(loaded == 0) {
8620         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8621         HMODULE lib;
8622         PLOAD_EGBB loadBB;
8623         loaded = 2; // prepare for failure
8624         if(!path) return 13; // no egbb installed
8625         strncpy(buf, path + 8, MSG_SIZ);
8626         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8627         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8628         lib = LoadLibrary(buf);
8629         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8630         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8631         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8632         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8633         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8634         loaded = 1; // success!
8635     }
8636     res = probeBB(forwardMostMove & 1, pieces, squares);
8637     return res > 0 ? 1 : res < 0 ? -1 : 0;
8638 }
8639
8640 char *
8641 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8642 {   // [HGM] book: this routine intercepts moves to simulate book replies
8643     char *bookHit = NULL;
8644
8645     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8646         char buf[MSG_SIZ];
8647         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8648         SendToProgram(buf, cps);
8649     }
8650     //first determine if the incoming move brings opponent into his book
8651     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8652         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8653     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8654     if(bookHit != NULL && !cps->bookSuspend) {
8655         // make sure opponent is not going to reply after receiving move to book position
8656         SendToProgram("force\n", cps);
8657         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8658     }
8659     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8660     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8661     // now arrange restart after book miss
8662     if(bookHit) {
8663         // after a book hit we never send 'go', and the code after the call to this routine
8664         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8665         char buf[MSG_SIZ], *move = bookHit;
8666         if(cps->useSAN) {
8667             int fromX, fromY, toX, toY;
8668             char promoChar;
8669             ChessMove moveType;
8670             move = buf + 30;
8671             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8672                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8673                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8674                                     PosFlags(forwardMostMove),
8675                                     fromY, fromX, toY, toX, promoChar, move);
8676             } else {
8677                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8678                 bookHit = NULL;
8679             }
8680         }
8681         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8682         SendToProgram(buf, cps);
8683         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8684     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8685         SendToProgram("go\n", cps);
8686         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8687     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8688         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8689             SendToProgram("go\n", cps);
8690         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8691     }
8692     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8693 }
8694
8695 int
8696 LoadError (char *errmess, ChessProgramState *cps)
8697 {   // unloads engine and switches back to -ncp mode if it was first
8698     if(cps->initDone) return FALSE;
8699     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8700     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8701     cps->pr = NoProc;
8702     if(cps == &first) {
8703         appData.noChessProgram = TRUE;
8704         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8705         gameMode = BeginningOfGame; ModeHighlight();
8706         SetNCPMode();
8707     }
8708     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8709     DisplayMessage("", ""); // erase waiting message
8710     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8711     return TRUE;
8712 }
8713
8714 char *savedMessage;
8715 ChessProgramState *savedState;
8716 void
8717 DeferredBookMove (void)
8718 {
8719         if(savedState->lastPing != savedState->lastPong)
8720                     ScheduleDelayedEvent(DeferredBookMove, 10);
8721         else
8722         HandleMachineMove(savedMessage, savedState);
8723 }
8724
8725 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8726 static ChessProgramState *stalledEngine;
8727 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8728
8729 void
8730 HandleMachineMove (char *message, ChessProgramState *cps)
8731 {
8732     static char firstLeg[20], legs;
8733     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8734     char realname[MSG_SIZ];
8735     int fromX, fromY, toX, toY;
8736     ChessMove moveType;
8737     char promoChar, roar;
8738     char *p, *pv=buf1;
8739     int oldError;
8740     char *bookHit;
8741
8742     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8743         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8744         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8745             DisplayError(_("Invalid pairing from pairing engine"), 0);
8746             return;
8747         }
8748         pairingReceived = 1;
8749         NextMatchGame();
8750         return; // Skim the pairing messages here.
8751     }
8752
8753     oldError = cps->userError; cps->userError = 0;
8754
8755 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8756     /*
8757      * Kludge to ignore BEL characters
8758      */
8759     while (*message == '\007') message++;
8760
8761     /*
8762      * [HGM] engine debug message: ignore lines starting with '#' character
8763      */
8764     if(cps->debug && *message == '#') return;
8765
8766     /*
8767      * Look for book output
8768      */
8769     if (cps == &first && bookRequested) {
8770         if (message[0] == '\t' || message[0] == ' ') {
8771             /* Part of the book output is here; append it */
8772             strcat(bookOutput, message);
8773             strcat(bookOutput, "  \n");
8774             return;
8775         } else if (bookOutput[0] != NULLCHAR) {
8776             /* All of book output has arrived; display it */
8777             char *p = bookOutput;
8778             while (*p != NULLCHAR) {
8779                 if (*p == '\t') *p = ' ';
8780                 p++;
8781             }
8782             DisplayInformation(bookOutput);
8783             bookRequested = FALSE;
8784             /* Fall through to parse the current output */
8785         }
8786     }
8787
8788     /*
8789      * Look for machine move.
8790      */
8791     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8792         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8793     {
8794         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8795             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8796             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8797             stalledEngine = cps;
8798             if(appData.ponderNextMove) { // bring opponent out of ponder
8799                 if(gameMode == TwoMachinesPlay) {
8800                     if(cps->other->pause)
8801                         PauseEngine(cps->other);
8802                     else
8803                         SendToProgram("easy\n", cps->other);
8804                 }
8805             }
8806             StopClocks();
8807             return;
8808         }
8809
8810       if(cps->usePing) {
8811
8812         /* This method is only useful on engines that support ping */
8813         if(abortEngineThink) {
8814             if (appData.debugMode) {
8815                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8816             }
8817             SendToProgram("undo\n", cps);
8818             return;
8819         }
8820
8821         if (cps->lastPing != cps->lastPong) {
8822             /* Extra move from before last new; ignore */
8823             if (appData.debugMode) {
8824                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8825             }
8826           return;
8827         }
8828
8829       } else {
8830
8831         int machineWhite = FALSE;
8832
8833         switch (gameMode) {
8834           case BeginningOfGame:
8835             /* Extra move from before last reset; ignore */
8836             if (appData.debugMode) {
8837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8838             }
8839             return;
8840
8841           case EndOfGame:
8842           case IcsIdle:
8843           default:
8844             /* Extra move after we tried to stop.  The mode test is
8845                not a reliable way of detecting this problem, but it's
8846                the best we can do on engines that don't support ping.
8847             */
8848             if (appData.debugMode) {
8849                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8850                         cps->which, gameMode);
8851             }
8852             SendToProgram("undo\n", cps);
8853             return;
8854
8855           case MachinePlaysWhite:
8856           case IcsPlayingWhite:
8857             machineWhite = TRUE;
8858             break;
8859
8860           case MachinePlaysBlack:
8861           case IcsPlayingBlack:
8862             machineWhite = FALSE;
8863             break;
8864
8865           case TwoMachinesPlay:
8866             machineWhite = (cps->twoMachinesColor[0] == 'w');
8867             break;
8868         }
8869         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8870             if (appData.debugMode) {
8871                 fprintf(debugFP,
8872                         "Ignoring move out of turn by %s, gameMode %d"
8873                         ", forwardMost %d\n",
8874                         cps->which, gameMode, forwardMostMove);
8875             }
8876             return;
8877         }
8878       }
8879
8880         if(cps->alphaRank) AlphaRank(machineMove, 4);
8881
8882         // [HGM] lion: (some very limited) support for Alien protocol
8883         killX = killY = kill2X = kill2Y = -1;
8884         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8885             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8886             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8887             return;
8888         }
8889         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8890             char *q = strchr(p+1, ',');            // second comma?
8891             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8892             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8893             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8894         }
8895         if(firstLeg[0]) { // there was a previous leg;
8896             // only support case where same piece makes two step
8897             char buf[20], *p = machineMove+1, *q = buf+1, f;
8898             safeStrCpy(buf, machineMove, 20);
8899             while(isdigit(*q)) q++; // find start of to-square
8900             safeStrCpy(machineMove, firstLeg, 20);
8901             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8902             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
8903             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)
8904             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8905             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8906             firstLeg[0] = NULLCHAR; legs = 0;
8907         }
8908
8909         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8910                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8911             /* Machine move could not be parsed; ignore it. */
8912           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8913                     machineMove, _(cps->which));
8914             DisplayMoveError(buf1);
8915             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8916                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8917             if (gameMode == TwoMachinesPlay) {
8918               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8919                        buf1, GE_XBOARD);
8920             }
8921             return;
8922         }
8923
8924         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8925         /* So we have to redo legality test with true e.p. status here,  */
8926         /* to make sure an illegal e.p. capture does not slip through,   */
8927         /* to cause a forfeit on a justified illegal-move complaint      */
8928         /* of the opponent.                                              */
8929         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8930            ChessMove moveType;
8931            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8932                              fromY, fromX, toY, toX, promoChar);
8933             if(moveType == IllegalMove) {
8934               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8935                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8936                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8937                            buf1, GE_XBOARD);
8938                 return;
8939            } else if(!appData.fischerCastling)
8940            /* [HGM] Kludge to handle engines that send FRC-style castling
8941               when they shouldn't (like TSCP-Gothic) */
8942            switch(moveType) {
8943              case WhiteASideCastleFR:
8944              case BlackASideCastleFR:
8945                toX+=2;
8946                currentMoveString[2]++;
8947                break;
8948              case WhiteHSideCastleFR:
8949              case BlackHSideCastleFR:
8950                toX--;
8951                currentMoveString[2]--;
8952                break;
8953              default: ; // nothing to do, but suppresses warning of pedantic compilers
8954            }
8955         }
8956         hintRequested = FALSE;
8957         lastHint[0] = NULLCHAR;
8958         bookRequested = FALSE;
8959         /* Program may be pondering now */
8960         cps->maybeThinking = TRUE;
8961         if (cps->sendTime == 2) cps->sendTime = 1;
8962         if (cps->offeredDraw) cps->offeredDraw--;
8963
8964         /* [AS] Save move info*/
8965         pvInfoList[ forwardMostMove ].score = programStats.score;
8966         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8967         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8968
8969         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8970
8971         /* Test suites abort the 'game' after one move */
8972         if(*appData.finger) {
8973            static FILE *f;
8974            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8975            if(!f) f = fopen(appData.finger, "w");
8976            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8977            else { DisplayFatalError("Bad output file", errno, 0); return; }
8978            free(fen);
8979            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8980         }
8981         if(appData.epd) {
8982            if(solvingTime >= 0) {
8983               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8984               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8985            } else {
8986               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8987               if(solvingTime == -2) second.matchWins++;
8988            }
8989            OutputKibitz(2, buf1);
8990            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8991         }
8992
8993         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8994         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8995             int count = 0;
8996
8997             while( count < adjudicateLossPlies ) {
8998                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8999
9000                 if( count & 1 ) {
9001                     score = -score; /* Flip score for winning side */
9002                 }
9003
9004                 if( score > appData.adjudicateLossThreshold ) {
9005                     break;
9006                 }
9007
9008                 count++;
9009             }
9010
9011             if( count >= adjudicateLossPlies ) {
9012                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9013
9014                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9015                     "Xboard adjudication",
9016                     GE_XBOARD );
9017
9018                 return;
9019             }
9020         }
9021
9022         if(Adjudicate(cps)) {
9023             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9024             return; // [HGM] adjudicate: for all automatic game ends
9025         }
9026
9027 #if ZIPPY
9028         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9029             first.initDone) {
9030           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9031                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9032                 SendToICS("draw ");
9033                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9034           }
9035           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9036           ics_user_moved = 1;
9037           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9038                 char buf[3*MSG_SIZ];
9039
9040                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9041                         programStats.score / 100.,
9042                         programStats.depth,
9043                         programStats.time / 100.,
9044                         (unsigned int)programStats.nodes,
9045                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9046                         programStats.movelist);
9047                 SendToICS(buf);
9048           }
9049         }
9050 #endif
9051
9052         /* [AS] Clear stats for next move */
9053         ClearProgramStats();
9054         thinkOutput[0] = NULLCHAR;
9055         hiddenThinkOutputState = 0;
9056
9057         bookHit = NULL;
9058         if (gameMode == TwoMachinesPlay) {
9059             /* [HGM] relaying draw offers moved to after reception of move */
9060             /* and interpreting offer as claim if it brings draw condition */
9061             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9062                 SendToProgram("draw\n", cps->other);
9063             }
9064             if (cps->other->sendTime) {
9065                 SendTimeRemaining(cps->other,
9066                                   cps->other->twoMachinesColor[0] == 'w');
9067             }
9068             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9069             if (firstMove && !bookHit) {
9070                 firstMove = FALSE;
9071                 if (cps->other->useColors) {
9072                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9073                 }
9074                 SendToProgram("go\n", cps->other);
9075             }
9076             cps->other->maybeThinking = TRUE;
9077         }
9078
9079         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9080
9081         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9082
9083         if (!pausing && appData.ringBellAfterMoves) {
9084             if(!roar) RingBell();
9085         }
9086
9087         /*
9088          * Reenable menu items that were disabled while
9089          * machine was thinking
9090          */
9091         if (gameMode != TwoMachinesPlay)
9092             SetUserThinkingEnables();
9093
9094         // [HGM] book: after book hit opponent has received move and is now in force mode
9095         // force the book reply into it, and then fake that it outputted this move by jumping
9096         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9097         if(bookHit) {
9098                 static char bookMove[MSG_SIZ]; // a bit generous?
9099
9100                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9101                 strcat(bookMove, bookHit);
9102                 message = bookMove;
9103                 cps = cps->other;
9104                 programStats.nodes = programStats.depth = programStats.time =
9105                 programStats.score = programStats.got_only_move = 0;
9106                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9107
9108                 if(cps->lastPing != cps->lastPong) {
9109                     savedMessage = message; // args for deferred call
9110                     savedState = cps;
9111                     ScheduleDelayedEvent(DeferredBookMove, 10);
9112                     return;
9113                 }
9114                 goto FakeBookMove;
9115         }
9116
9117         return;
9118     }
9119
9120     /* Set special modes for chess engines.  Later something general
9121      *  could be added here; for now there is just one kludge feature,
9122      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9123      *  when "xboard" is given as an interactive command.
9124      */
9125     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9126         cps->useSigint = FALSE;
9127         cps->useSigterm = FALSE;
9128     }
9129     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9130       ParseFeatures(message+8, cps);
9131       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9132     }
9133
9134     if (!strncmp(message, "setup ", 6) && 
9135         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9136           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9137                                         ) { // [HGM] allow first engine to define opening position
9138       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9139       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9140       *buf = NULLCHAR;
9141       if(sscanf(message, "setup (%s", buf) == 1) {
9142         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9143         ASSIGN(appData.pieceToCharTable, buf);
9144       }
9145       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9146       if(dummy >= 3) {
9147         while(message[s] && message[s++] != ' ');
9148         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9149            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9150             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9151             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9152             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9153           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9154           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9155           startedFromSetupPosition = FALSE;
9156         }
9157       }
9158       if(startedFromSetupPosition) return;
9159       ParseFEN(boards[0], &dummy, message+s, FALSE);
9160       DrawPosition(TRUE, boards[0]);
9161       CopyBoard(initialPosition, boards[0]);
9162       startedFromSetupPosition = TRUE;
9163       return;
9164     }
9165     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9166       ChessSquare piece = WhitePawn;
9167       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9168       if(*p == '+') promoted++, ID = *++p;
9169       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9170       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9171       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9172       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9173       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9174       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9175       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9176       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9177                                                && gameInfo.variant != VariantGreat
9178                                                && gameInfo.variant != VariantFairy    ) return;
9179       if(piece < EmptySquare) {
9180         pieceDefs = TRUE;
9181         ASSIGN(pieceDesc[piece], buf1);
9182         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9183       }
9184       return;
9185     }
9186     if(sscanf(message, "choice %s", promoRestrict) == 1) {
9187       if(deferChoice) {
9188         LeftClick(Press, 0, 0); // finish the click that was interrupted
9189       } else if(promoSweep != EmptySquare) {
9190         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9191         if(strlen(promoRestrict) > 1) Sweep(0);
9192       }
9193       return;
9194     }
9195     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9196      * want this, I was asked to put it in, and obliged.
9197      */
9198     if (!strncmp(message, "setboard ", 9)) {
9199         Board initial_position;
9200
9201         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9202
9203         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9204             DisplayError(_("Bad FEN received from engine"), 0);
9205             return ;
9206         } else {
9207            Reset(TRUE, FALSE);
9208            CopyBoard(boards[0], initial_position);
9209            initialRulePlies = FENrulePlies;
9210            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9211            else gameMode = MachinePlaysBlack;
9212            DrawPosition(FALSE, boards[currentMove]);
9213         }
9214         return;
9215     }
9216
9217     /*
9218      * Look for communication commands
9219      */
9220     if (!strncmp(message, "telluser ", 9)) {
9221         if(message[9] == '\\' && message[10] == '\\')
9222             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9223         PlayTellSound();
9224         DisplayNote(message + 9);
9225         return;
9226     }
9227     if (!strncmp(message, "tellusererror ", 14)) {
9228         cps->userError = 1;
9229         if(message[14] == '\\' && message[15] == '\\')
9230             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9231         PlayTellSound();
9232         DisplayError(message + 14, 0);
9233         return;
9234     }
9235     if (!strncmp(message, "tellopponent ", 13)) {
9236       if (appData.icsActive) {
9237         if (loggedOn) {
9238           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9239           SendToICS(buf1);
9240         }
9241       } else {
9242         DisplayNote(message + 13);
9243       }
9244       return;
9245     }
9246     if (!strncmp(message, "tellothers ", 11)) {
9247       if (appData.icsActive) {
9248         if (loggedOn) {
9249           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9250           SendToICS(buf1);
9251         }
9252       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9253       return;
9254     }
9255     if (!strncmp(message, "tellall ", 8)) {
9256       if (appData.icsActive) {
9257         if (loggedOn) {
9258           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9259           SendToICS(buf1);
9260         }
9261       } else {
9262         DisplayNote(message + 8);
9263       }
9264       return;
9265     }
9266     if (strncmp(message, "warning", 7) == 0) {
9267         /* Undocumented feature, use tellusererror in new code */
9268         DisplayError(message, 0);
9269         return;
9270     }
9271     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9272         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9273         strcat(realname, " query");
9274         AskQuestion(realname, buf2, buf1, cps->pr);
9275         return;
9276     }
9277     /* Commands from the engine directly to ICS.  We don't allow these to be
9278      *  sent until we are logged on. Crafty kibitzes have been known to
9279      *  interfere with the login process.
9280      */
9281     if (loggedOn) {
9282         if (!strncmp(message, "tellics ", 8)) {
9283             SendToICS(message + 8);
9284             SendToICS("\n");
9285             return;
9286         }
9287         if (!strncmp(message, "tellicsnoalias ", 15)) {
9288             SendToICS(ics_prefix);
9289             SendToICS(message + 15);
9290             SendToICS("\n");
9291             return;
9292         }
9293         /* The following are for backward compatibility only */
9294         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9295             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9296             SendToICS(ics_prefix);
9297             SendToICS(message);
9298             SendToICS("\n");
9299             return;
9300         }
9301     }
9302     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9303         if(initPing == cps->lastPong) {
9304             if(gameInfo.variant == VariantUnknown) {
9305                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9306                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9307                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9308             }
9309             initPing = -1;
9310         }
9311         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9312             abortEngineThink = FALSE;
9313             DisplayMessage("", "");
9314             ThawUI();
9315         }
9316         return;
9317     }
9318     if(!strncmp(message, "highlight ", 10)) {
9319         if(appData.testLegality && !*engineVariant && appData.markers) return;
9320         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9321         return;
9322     }
9323     if(!strncmp(message, "click ", 6)) {
9324         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9325         if(appData.testLegality || !appData.oneClick) return;
9326         sscanf(message+6, "%c%d%c", &f, &y, &c);
9327         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9328         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9329         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9330         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9331         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9332         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9333             LeftClick(Release, lastLeftX, lastLeftY);
9334         controlKey  = (c == ',');
9335         LeftClick(Press, x, y);
9336         LeftClick(Release, x, y);
9337         first.highlight = f;
9338         return;
9339     }
9340     /*
9341      * If the move is illegal, cancel it and redraw the board.
9342      * Also deal with other error cases.  Matching is rather loose
9343      * here to accommodate engines written before the spec.
9344      */
9345     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9346         strncmp(message, "Error", 5) == 0) {
9347         if (StrStr(message, "name") ||
9348             StrStr(message, "rating") || StrStr(message, "?") ||
9349             StrStr(message, "result") || StrStr(message, "board") ||
9350             StrStr(message, "bk") || StrStr(message, "computer") ||
9351             StrStr(message, "variant") || StrStr(message, "hint") ||
9352             StrStr(message, "random") || StrStr(message, "depth") ||
9353             StrStr(message, "accepted")) {
9354             return;
9355         }
9356         if (StrStr(message, "protover")) {
9357           /* Program is responding to input, so it's apparently done
9358              initializing, and this error message indicates it is
9359              protocol version 1.  So we don't need to wait any longer
9360              for it to initialize and send feature commands. */
9361           FeatureDone(cps, 1);
9362           cps->protocolVersion = 1;
9363           return;
9364         }
9365         cps->maybeThinking = FALSE;
9366
9367         if (StrStr(message, "draw")) {
9368             /* Program doesn't have "draw" command */
9369             cps->sendDrawOffers = 0;
9370             return;
9371         }
9372         if (cps->sendTime != 1 &&
9373             (StrStr(message, "time") || StrStr(message, "otim"))) {
9374           /* Program apparently doesn't have "time" or "otim" command */
9375           cps->sendTime = 0;
9376           return;
9377         }
9378         if (StrStr(message, "analyze")) {
9379             cps->analysisSupport = FALSE;
9380             cps->analyzing = FALSE;
9381 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9382             EditGameEvent(); // [HGM] try to preserve loaded game
9383             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9384             DisplayError(buf2, 0);
9385             return;
9386         }
9387         if (StrStr(message, "(no matching move)st")) {
9388           /* Special kludge for GNU Chess 4 only */
9389           cps->stKludge = TRUE;
9390           SendTimeControl(cps, movesPerSession, timeControl,
9391                           timeIncrement, appData.searchDepth,
9392                           searchTime);
9393           return;
9394         }
9395         if (StrStr(message, "(no matching move)sd")) {
9396           /* Special kludge for GNU Chess 4 only */
9397           cps->sdKludge = TRUE;
9398           SendTimeControl(cps, movesPerSession, timeControl,
9399                           timeIncrement, appData.searchDepth,
9400                           searchTime);
9401           return;
9402         }
9403         if (!StrStr(message, "llegal")) {
9404             return;
9405         }
9406         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9407             gameMode == IcsIdle) return;
9408         if (forwardMostMove <= backwardMostMove) return;
9409         if (pausing) PauseEvent();
9410       if(appData.forceIllegal) {
9411             // [HGM] illegal: machine refused move; force position after move into it
9412           SendToProgram("force\n", cps);
9413           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9414                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9415                 // when black is to move, while there might be nothing on a2 or black
9416                 // might already have the move. So send the board as if white has the move.
9417                 // But first we must change the stm of the engine, as it refused the last move
9418                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9419                 if(WhiteOnMove(forwardMostMove)) {
9420                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9421                     SendBoard(cps, forwardMostMove); // kludgeless board
9422                 } else {
9423                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9424                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9425                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9426                 }
9427           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9428             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9429                  gameMode == TwoMachinesPlay)
9430               SendToProgram("go\n", cps);
9431             return;
9432       } else
9433         if (gameMode == PlayFromGameFile) {
9434             /* Stop reading this game file */
9435             gameMode = EditGame;
9436             ModeHighlight();
9437         }
9438         /* [HGM] illegal-move claim should forfeit game when Xboard */
9439         /* only passes fully legal moves                            */
9440         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9441             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9442                                 "False illegal-move claim", GE_XBOARD );
9443             return; // do not take back move we tested as valid
9444         }
9445         currentMove = forwardMostMove-1;
9446         DisplayMove(currentMove-1); /* before DisplayMoveError */
9447         SwitchClocks(forwardMostMove-1); // [HGM] race
9448         DisplayBothClocks();
9449         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9450                 parseList[currentMove], _(cps->which));
9451         DisplayMoveError(buf1);
9452         DrawPosition(FALSE, boards[currentMove]);
9453
9454         SetUserThinkingEnables();
9455         return;
9456     }
9457     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9458         /* Program has a broken "time" command that
9459            outputs a string not ending in newline.
9460            Don't use it. */
9461         cps->sendTime = 0;
9462     }
9463     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9464         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9465             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9466     }
9467
9468     /*
9469      * If chess program startup fails, exit with an error message.
9470      * Attempts to recover here are futile. [HGM] Well, we try anyway
9471      */
9472     if ((StrStr(message, "unknown host") != NULL)
9473         || (StrStr(message, "No remote directory") != NULL)
9474         || (StrStr(message, "not found") != NULL)
9475         || (StrStr(message, "No such file") != NULL)
9476         || (StrStr(message, "can't alloc") != NULL)
9477         || (StrStr(message, "Permission denied") != NULL)) {
9478
9479         cps->maybeThinking = FALSE;
9480         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9481                 _(cps->which), cps->program, cps->host, message);
9482         RemoveInputSource(cps->isr);
9483         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9484             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9485             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9486         }
9487         return;
9488     }
9489
9490     /*
9491      * Look for hint output
9492      */
9493     if (sscanf(message, "Hint: %s", buf1) == 1) {
9494         if (cps == &first && hintRequested) {
9495             hintRequested = FALSE;
9496             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9497                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9498                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9499                                     PosFlags(forwardMostMove),
9500                                     fromY, fromX, toY, toX, promoChar, buf1);
9501                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9502                 DisplayInformation(buf2);
9503             } else {
9504                 /* Hint move could not be parsed!? */
9505               snprintf(buf2, sizeof(buf2),
9506                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9507                         buf1, _(cps->which));
9508                 DisplayError(buf2, 0);
9509             }
9510         } else {
9511           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9512         }
9513         return;
9514     }
9515
9516     /*
9517      * Ignore other messages if game is not in progress
9518      */
9519     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9520         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9521
9522     /*
9523      * look for win, lose, draw, or draw offer
9524      */
9525     if (strncmp(message, "1-0", 3) == 0) {
9526         char *p, *q, *r = "";
9527         p = strchr(message, '{');
9528         if (p) {
9529             q = strchr(p, '}');
9530             if (q) {
9531                 *q = NULLCHAR;
9532                 r = p + 1;
9533             }
9534         }
9535         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9536         return;
9537     } else if (strncmp(message, "0-1", 3) == 0) {
9538         char *p, *q, *r = "";
9539         p = strchr(message, '{');
9540         if (p) {
9541             q = strchr(p, '}');
9542             if (q) {
9543                 *q = NULLCHAR;
9544                 r = p + 1;
9545             }
9546         }
9547         /* Kludge for Arasan 4.1 bug */
9548         if (strcmp(r, "Black resigns") == 0) {
9549             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9550             return;
9551         }
9552         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9553         return;
9554     } else if (strncmp(message, "1/2", 3) == 0) {
9555         char *p, *q, *r = "";
9556         p = strchr(message, '{');
9557         if (p) {
9558             q = strchr(p, '}');
9559             if (q) {
9560                 *q = NULLCHAR;
9561                 r = p + 1;
9562             }
9563         }
9564
9565         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9566         return;
9567
9568     } else if (strncmp(message, "White resign", 12) == 0) {
9569         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9570         return;
9571     } else if (strncmp(message, "Black resign", 12) == 0) {
9572         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9573         return;
9574     } else if (strncmp(message, "White matches", 13) == 0 ||
9575                strncmp(message, "Black matches", 13) == 0   ) {
9576         /* [HGM] ignore GNUShogi noises */
9577         return;
9578     } else if (strncmp(message, "White", 5) == 0 &&
9579                message[5] != '(' &&
9580                StrStr(message, "Black") == NULL) {
9581         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9582         return;
9583     } else if (strncmp(message, "Black", 5) == 0 &&
9584                message[5] != '(') {
9585         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9586         return;
9587     } else if (strcmp(message, "resign") == 0 ||
9588                strcmp(message, "computer resigns") == 0) {
9589         switch (gameMode) {
9590           case MachinePlaysBlack:
9591           case IcsPlayingBlack:
9592             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9593             break;
9594           case MachinePlaysWhite:
9595           case IcsPlayingWhite:
9596             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9597             break;
9598           case TwoMachinesPlay:
9599             if (cps->twoMachinesColor[0] == 'w')
9600               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9601             else
9602               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9603             break;
9604           default:
9605             /* can't happen */
9606             break;
9607         }
9608         return;
9609     } else if (strncmp(message, "opponent mates", 14) == 0) {
9610         switch (gameMode) {
9611           case MachinePlaysBlack:
9612           case IcsPlayingBlack:
9613             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9614             break;
9615           case MachinePlaysWhite:
9616           case IcsPlayingWhite:
9617             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9618             break;
9619           case TwoMachinesPlay:
9620             if (cps->twoMachinesColor[0] == 'w')
9621               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9622             else
9623               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9624             break;
9625           default:
9626             /* can't happen */
9627             break;
9628         }
9629         return;
9630     } else if (strncmp(message, "computer mates", 14) == 0) {
9631         switch (gameMode) {
9632           case MachinePlaysBlack:
9633           case IcsPlayingBlack:
9634             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9635             break;
9636           case MachinePlaysWhite:
9637           case IcsPlayingWhite:
9638             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9639             break;
9640           case TwoMachinesPlay:
9641             if (cps->twoMachinesColor[0] == 'w')
9642               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9643             else
9644               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9645             break;
9646           default:
9647             /* can't happen */
9648             break;
9649         }
9650         return;
9651     } else if (strncmp(message, "checkmate", 9) == 0) {
9652         if (WhiteOnMove(forwardMostMove)) {
9653             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9654         } else {
9655             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9656         }
9657         return;
9658     } else if (strstr(message, "Draw") != NULL ||
9659                strstr(message, "game is a draw") != NULL) {
9660         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9661         return;
9662     } else if (strstr(message, "offer") != NULL &&
9663                strstr(message, "draw") != NULL) {
9664 #if ZIPPY
9665         if (appData.zippyPlay && first.initDone) {
9666             /* Relay offer to ICS */
9667             SendToICS(ics_prefix);
9668             SendToICS("draw\n");
9669         }
9670 #endif
9671         cps->offeredDraw = 2; /* valid until this engine moves twice */
9672         if (gameMode == TwoMachinesPlay) {
9673             if (cps->other->offeredDraw) {
9674                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9675             /* [HGM] in two-machine mode we delay relaying draw offer      */
9676             /* until after we also have move, to see if it is really claim */
9677             }
9678         } else if (gameMode == MachinePlaysWhite ||
9679                    gameMode == MachinePlaysBlack) {
9680           if (userOfferedDraw) {
9681             DisplayInformation(_("Machine accepts your draw offer"));
9682             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9683           } else {
9684             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9685           }
9686         }
9687     }
9688
9689
9690     /*
9691      * Look for thinking output
9692      */
9693     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9694           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9695                                 ) {
9696         int plylev, mvleft, mvtot, curscore, time;
9697         char mvname[MOVE_LEN];
9698         u64 nodes; // [DM]
9699         char plyext;
9700         int ignore = FALSE;
9701         int prefixHint = FALSE;
9702         mvname[0] = NULLCHAR;
9703
9704         switch (gameMode) {
9705           case MachinePlaysBlack:
9706           case IcsPlayingBlack:
9707             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9708             break;
9709           case MachinePlaysWhite:
9710           case IcsPlayingWhite:
9711             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9712             break;
9713           case AnalyzeMode:
9714           case AnalyzeFile:
9715             break;
9716           case IcsObserving: /* [DM] icsEngineAnalyze */
9717             if (!appData.icsEngineAnalyze) ignore = TRUE;
9718             break;
9719           case TwoMachinesPlay:
9720             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9721                 ignore = TRUE;
9722             }
9723             break;
9724           default:
9725             ignore = TRUE;
9726             break;
9727         }
9728
9729         if (!ignore) {
9730             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9731             int solved = 0;
9732             buf1[0] = NULLCHAR;
9733             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9734                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9735                 char score_buf[MSG_SIZ];
9736
9737                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9738                     nodes += u64Const(0x100000000);
9739
9740                 if (plyext != ' ' && plyext != '\t') {
9741                     time *= 100;
9742                 }
9743
9744                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9745                 if( cps->scoreIsAbsolute &&
9746                     ( gameMode == MachinePlaysBlack ||
9747                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9748                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9749                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9750                      !WhiteOnMove(currentMove)
9751                     ) )
9752                 {
9753                     curscore = -curscore;
9754                 }
9755
9756                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9757
9758                 if(*bestMove) { // rememer time best EPD move was first found
9759                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9760                     ChessMove mt; char *p = bestMove;
9761                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9762                     solved = 0;
9763                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9764                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9765                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9766                             solved = 1;
9767                             break;
9768                         }
9769                         while(*p && *p != ' ') p++;
9770                         while(*p == ' ') p++;
9771                     }
9772                     if(!solved) solvingTime = -1;
9773                 }
9774                 if(*avoidMove && !solved) {
9775                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9776                     ChessMove mt; char *p = avoidMove, solved = 1;
9777                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9778                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9779                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9780                             solved = 0; solvingTime = -2;
9781                             break;
9782                         }
9783                         while(*p && *p != ' ') p++;
9784                         while(*p == ' ') p++;
9785                     }
9786                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9787                 }
9788
9789                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9790                         char buf[MSG_SIZ];
9791                         FILE *f;
9792                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9793                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9794                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9795                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9796                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9797                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9798                                 fclose(f);
9799                         }
9800                         else
9801                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9802                           DisplayError(_("failed writing PV"), 0);
9803                 }
9804
9805                 tempStats.depth = plylev;
9806                 tempStats.nodes = nodes;
9807                 tempStats.time = time;
9808                 tempStats.score = curscore;
9809                 tempStats.got_only_move = 0;
9810
9811                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9812                         int ticklen;
9813
9814                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9815                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9816                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9817                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9818                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9819                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9820                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9821                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9822                 }
9823
9824                 /* Buffer overflow protection */
9825                 if (pv[0] != NULLCHAR) {
9826                     if (strlen(pv) >= sizeof(tempStats.movelist)
9827                         && appData.debugMode) {
9828                         fprintf(debugFP,
9829                                 "PV is too long; using the first %u bytes.\n",
9830                                 (unsigned) sizeof(tempStats.movelist) - 1);
9831                     }
9832
9833                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9834                 } else {
9835                     sprintf(tempStats.movelist, " no PV\n");
9836                 }
9837
9838                 if (tempStats.seen_stat) {
9839                     tempStats.ok_to_send = 1;
9840                 }
9841
9842                 if (strchr(tempStats.movelist, '(') != NULL) {
9843                     tempStats.line_is_book = 1;
9844                     tempStats.nr_moves = 0;
9845                     tempStats.moves_left = 0;
9846                 } else {
9847                     tempStats.line_is_book = 0;
9848                 }
9849
9850                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9851                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9852
9853                 SendProgramStatsToFrontend( cps, &tempStats );
9854
9855                 /*
9856                     [AS] Protect the thinkOutput buffer from overflow... this
9857                     is only useful if buf1 hasn't overflowed first!
9858                 */
9859                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9860                 if(curscore >= MATE_SCORE) 
9861                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9862                 else if(curscore <= -MATE_SCORE) 
9863                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9864                 else
9865                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9866                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9867                          plylev,
9868                          (gameMode == TwoMachinesPlay ?
9869                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9870                          score_buf,
9871                          prefixHint ? lastHint : "",
9872                          prefixHint ? " " : "" );
9873
9874                 if( buf1[0] != NULLCHAR ) {
9875                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9876
9877                     if( strlen(pv) > max_len ) {
9878                         if( appData.debugMode) {
9879                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9880                         }
9881                         pv[max_len+1] = '\0';
9882                     }
9883
9884                     strcat( thinkOutput, pv);
9885                 }
9886
9887                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9888                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9889                     DisplayMove(currentMove - 1);
9890                 }
9891                 return;
9892
9893             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9894                 /* crafty (9.25+) says "(only move) <move>"
9895                  * if there is only 1 legal move
9896                  */
9897                 sscanf(p, "(only move) %s", buf1);
9898                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9899                 sprintf(programStats.movelist, "%s (only move)", buf1);
9900                 programStats.depth = 1;
9901                 programStats.nr_moves = 1;
9902                 programStats.moves_left = 1;
9903                 programStats.nodes = 1;
9904                 programStats.time = 1;
9905                 programStats.got_only_move = 1;
9906
9907                 /* Not really, but we also use this member to
9908                    mean "line isn't going to change" (Crafty
9909                    isn't searching, so stats won't change) */
9910                 programStats.line_is_book = 1;
9911
9912                 SendProgramStatsToFrontend( cps, &programStats );
9913
9914                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9915                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9916                     DisplayMove(currentMove - 1);
9917                 }
9918                 return;
9919             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9920                               &time, &nodes, &plylev, &mvleft,
9921                               &mvtot, mvname) >= 5) {
9922                 /* The stat01: line is from Crafty (9.29+) in response
9923                    to the "." command */
9924                 programStats.seen_stat = 1;
9925                 cps->maybeThinking = TRUE;
9926
9927                 if (programStats.got_only_move || !appData.periodicUpdates)
9928                   return;
9929
9930                 programStats.depth = plylev;
9931                 programStats.time = time;
9932                 programStats.nodes = nodes;
9933                 programStats.moves_left = mvleft;
9934                 programStats.nr_moves = mvtot;
9935                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9936                 programStats.ok_to_send = 1;
9937                 programStats.movelist[0] = '\0';
9938
9939                 SendProgramStatsToFrontend( cps, &programStats );
9940
9941                 return;
9942
9943             } else if (strncmp(message,"++",2) == 0) {
9944                 /* Crafty 9.29+ outputs this */
9945                 programStats.got_fail = 2;
9946                 return;
9947
9948             } else if (strncmp(message,"--",2) == 0) {
9949                 /* Crafty 9.29+ outputs this */
9950                 programStats.got_fail = 1;
9951                 return;
9952
9953             } else if (thinkOutput[0] != NULLCHAR &&
9954                        strncmp(message, "    ", 4) == 0) {
9955                 unsigned message_len;
9956
9957                 p = message;
9958                 while (*p && *p == ' ') p++;
9959
9960                 message_len = strlen( p );
9961
9962                 /* [AS] Avoid buffer overflow */
9963                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9964                     strcat(thinkOutput, " ");
9965                     strcat(thinkOutput, p);
9966                 }
9967
9968                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9969                     strcat(programStats.movelist, " ");
9970                     strcat(programStats.movelist, p);
9971                 }
9972
9973                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9974                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9975                     DisplayMove(currentMove - 1);
9976                 }
9977                 return;
9978             }
9979         }
9980         else {
9981             buf1[0] = NULLCHAR;
9982
9983             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9984                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9985             {
9986                 ChessProgramStats cpstats;
9987
9988                 if (plyext != ' ' && plyext != '\t') {
9989                     time *= 100;
9990                 }
9991
9992                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9993                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9994                     curscore = -curscore;
9995                 }
9996
9997                 cpstats.depth = plylev;
9998                 cpstats.nodes = nodes;
9999                 cpstats.time = time;
10000                 cpstats.score = curscore;
10001                 cpstats.got_only_move = 0;
10002                 cpstats.movelist[0] = '\0';
10003
10004                 if (buf1[0] != NULLCHAR) {
10005                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10006                 }
10007
10008                 cpstats.ok_to_send = 0;
10009                 cpstats.line_is_book = 0;
10010                 cpstats.nr_moves = 0;
10011                 cpstats.moves_left = 0;
10012
10013                 SendProgramStatsToFrontend( cps, &cpstats );
10014             }
10015         }
10016     }
10017 }
10018
10019
10020 /* Parse a game score from the character string "game", and
10021    record it as the history of the current game.  The game
10022    score is NOT assumed to start from the standard position.
10023    The display is not updated in any way.
10024    */
10025 void
10026 ParseGameHistory (char *game)
10027 {
10028     ChessMove moveType;
10029     int fromX, fromY, toX, toY, boardIndex;
10030     char promoChar;
10031     char *p, *q;
10032     char buf[MSG_SIZ];
10033
10034     if (appData.debugMode)
10035       fprintf(debugFP, "Parsing game history: %s\n", game);
10036
10037     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10038     gameInfo.site = StrSave(appData.icsHost);
10039     gameInfo.date = PGNDate();
10040     gameInfo.round = StrSave("-");
10041
10042     /* Parse out names of players */
10043     while (*game == ' ') game++;
10044     p = buf;
10045     while (*game != ' ') *p++ = *game++;
10046     *p = NULLCHAR;
10047     gameInfo.white = StrSave(buf);
10048     while (*game == ' ') game++;
10049     p = buf;
10050     while (*game != ' ' && *game != '\n') *p++ = *game++;
10051     *p = NULLCHAR;
10052     gameInfo.black = StrSave(buf);
10053
10054     /* Parse moves */
10055     boardIndex = blackPlaysFirst ? 1 : 0;
10056     yynewstr(game);
10057     for (;;) {
10058         yyboardindex = boardIndex;
10059         moveType = (ChessMove) Myylex();
10060         switch (moveType) {
10061           case IllegalMove:             /* maybe suicide chess, etc. */
10062   if (appData.debugMode) {
10063     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10064     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10065     setbuf(debugFP, NULL);
10066   }
10067           case WhitePromotion:
10068           case BlackPromotion:
10069           case WhiteNonPromotion:
10070           case BlackNonPromotion:
10071           case NormalMove:
10072           case FirstLeg:
10073           case WhiteCapturesEnPassant:
10074           case BlackCapturesEnPassant:
10075           case WhiteKingSideCastle:
10076           case WhiteQueenSideCastle:
10077           case BlackKingSideCastle:
10078           case BlackQueenSideCastle:
10079           case WhiteKingSideCastleWild:
10080           case WhiteQueenSideCastleWild:
10081           case BlackKingSideCastleWild:
10082           case BlackQueenSideCastleWild:
10083           /* PUSH Fabien */
10084           case WhiteHSideCastleFR:
10085           case WhiteASideCastleFR:
10086           case BlackHSideCastleFR:
10087           case BlackASideCastleFR:
10088           /* POP Fabien */
10089             fromX = currentMoveString[0] - AAA;
10090             fromY = currentMoveString[1] - ONE;
10091             toX = currentMoveString[2] - AAA;
10092             toY = currentMoveString[3] - ONE;
10093             promoChar = currentMoveString[4];
10094             break;
10095           case WhiteDrop:
10096           case BlackDrop:
10097             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10098             fromX = moveType == WhiteDrop ?
10099               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10100             (int) CharToPiece(ToLower(currentMoveString[0]));
10101             fromY = DROP_RANK;
10102             toX = currentMoveString[2] - AAA;
10103             toY = currentMoveString[3] - ONE;
10104             promoChar = NULLCHAR;
10105             break;
10106           case AmbiguousMove:
10107             /* bug? */
10108             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10109   if (appData.debugMode) {
10110     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10111     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10112     setbuf(debugFP, NULL);
10113   }
10114             DisplayError(buf, 0);
10115             return;
10116           case ImpossibleMove:
10117             /* bug? */
10118             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10119   if (appData.debugMode) {
10120     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10121     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10122     setbuf(debugFP, NULL);
10123   }
10124             DisplayError(buf, 0);
10125             return;
10126           case EndOfFile:
10127             if (boardIndex < backwardMostMove) {
10128                 /* Oops, gap.  How did that happen? */
10129                 DisplayError(_("Gap in move list"), 0);
10130                 return;
10131             }
10132             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10133             if (boardIndex > forwardMostMove) {
10134                 forwardMostMove = boardIndex;
10135             }
10136             return;
10137           case ElapsedTime:
10138             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10139                 strcat(parseList[boardIndex-1], " ");
10140                 strcat(parseList[boardIndex-1], yy_text);
10141             }
10142             continue;
10143           case Comment:
10144           case PGNTag:
10145           case NAG:
10146           default:
10147             /* ignore */
10148             continue;
10149           case WhiteWins:
10150           case BlackWins:
10151           case GameIsDrawn:
10152           case GameUnfinished:
10153             if (gameMode == IcsExamining) {
10154                 if (boardIndex < backwardMostMove) {
10155                     /* Oops, gap.  How did that happen? */
10156                     return;
10157                 }
10158                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10159                 return;
10160             }
10161             gameInfo.result = moveType;
10162             p = strchr(yy_text, '{');
10163             if (p == NULL) p = strchr(yy_text, '(');
10164             if (p == NULL) {
10165                 p = yy_text;
10166                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10167             } else {
10168                 q = strchr(p, *p == '{' ? '}' : ')');
10169                 if (q != NULL) *q = NULLCHAR;
10170                 p++;
10171             }
10172             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10173             gameInfo.resultDetails = StrSave(p);
10174             continue;
10175         }
10176         if (boardIndex >= forwardMostMove &&
10177             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10178             backwardMostMove = blackPlaysFirst ? 1 : 0;
10179             return;
10180         }
10181         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10182                                  fromY, fromX, toY, toX, promoChar,
10183                                  parseList[boardIndex]);
10184         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10185         /* currentMoveString is set as a side-effect of yylex */
10186         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10187         strcat(moveList[boardIndex], "\n");
10188         boardIndex++;
10189         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10190         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10191           case MT_NONE:
10192           case MT_STALEMATE:
10193           default:
10194             break;
10195           case MT_CHECK:
10196             if(!IS_SHOGI(gameInfo.variant))
10197                 strcat(parseList[boardIndex - 1], "+");
10198             break;
10199           case MT_CHECKMATE:
10200           case MT_STAINMATE:
10201             strcat(parseList[boardIndex - 1], "#");
10202             break;
10203         }
10204     }
10205 }
10206
10207
10208 /* Apply a move to the given board  */
10209 void
10210 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10211 {
10212   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10213   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10214
10215     /* [HGM] compute & store e.p. status and castling rights for new position */
10216     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10217
10218       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10219       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10220       board[EP_STATUS] = EP_NONE;
10221       board[EP_FILE] = board[EP_RANK] = 100;
10222
10223   if (fromY == DROP_RANK) {
10224         /* must be first */
10225         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10226             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10227             return;
10228         }
10229         piece = board[toY][toX] = (ChessSquare) fromX;
10230   } else {
10231 //      ChessSquare victim;
10232       int i;
10233
10234       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10235 //           victim = board[killY][killX],
10236            killed = board[killY][killX],
10237            board[killY][killX] = EmptySquare,
10238            board[EP_STATUS] = EP_CAPTURE;
10239            if( kill2X >= 0 && kill2Y >= 0)
10240              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10241       }
10242
10243       if( board[toY][toX] != EmptySquare ) {
10244            board[EP_STATUS] = EP_CAPTURE;
10245            if( (fromX != toX || fromY != toY) && // not igui!
10246                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10247                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10248                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10249            }
10250       }
10251
10252       pawn = board[fromY][fromX];
10253       if( pawn == WhiteLance || pawn == BlackLance ) {
10254            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10255                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10256                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10257            }
10258       }
10259       if( pawn == WhitePawn ) {
10260            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10261                board[EP_STATUS] = EP_PAWN_MOVE;
10262            if( toY-fromY>=2) {
10263                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10264                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10265                         gameInfo.variant != VariantBerolina || toX < fromX)
10266                       board[EP_STATUS] = toX | berolina;
10267                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10268                         gameInfo.variant != VariantBerolina || toX > fromX)
10269                       board[EP_STATUS] = toX;
10270            }
10271       } else
10272       if( pawn == BlackPawn ) {
10273            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10274                board[EP_STATUS] = EP_PAWN_MOVE;
10275            if( toY-fromY<= -2) {
10276                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10277                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10278                         gameInfo.variant != VariantBerolina || toX < fromX)
10279                       board[EP_STATUS] = toX | berolina;
10280                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10281                         gameInfo.variant != VariantBerolina || toX > fromX)
10282                       board[EP_STATUS] = toX;
10283            }
10284        }
10285
10286        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10287        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10288        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10289        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10290
10291        for(i=0; i<nrCastlingRights; i++) {
10292            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10293               board[CASTLING][i] == toX   && castlingRank[i] == toY
10294              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10295        }
10296
10297        if(gameInfo.variant == VariantSChess) { // update virginity
10298            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10299            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10300            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10301            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10302        }
10303
10304      if (fromX == toX && fromY == toY && killX < 0) return;
10305
10306      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10307      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10308      if(gameInfo.variant == VariantKnightmate)
10309          king += (int) WhiteUnicorn - (int) WhiteKing;
10310
10311     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10312        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10313         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10314         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10315         board[EP_STATUS] = EP_NONE; // capture was fake!
10316     } else
10317     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10318         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10319         board[toY][toX] = piece;
10320         board[EP_STATUS] = EP_NONE; // capture was fake!
10321     } else
10322     /* Code added by Tord: */
10323     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10324     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10325         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10326       board[EP_STATUS] = EP_NONE; // capture was fake!
10327       board[fromY][fromX] = EmptySquare;
10328       board[toY][toX] = EmptySquare;
10329       if((toX > fromX) != (piece == WhiteRook)) {
10330         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10331       } else {
10332         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10333       }
10334     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10335                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10336       board[EP_STATUS] = EP_NONE;
10337       board[fromY][fromX] = EmptySquare;
10338       board[toY][toX] = EmptySquare;
10339       if((toX > fromX) != (piece == BlackRook)) {
10340         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10341       } else {
10342         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10343       }
10344     /* End of code added by Tord */
10345
10346     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10347         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10348         board[toY][toX] = piece;
10349     } else if (board[fromY][fromX] == king
10350         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10351         && toY == fromY && toX > fromX+1) {
10352         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10353                                                                                              ; // castle with nearest piece
10354         board[fromY][toX-1] = board[fromY][rookX];
10355         board[fromY][rookX] = EmptySquare;
10356         board[fromY][fromX] = EmptySquare;
10357         board[toY][toX] = king;
10358     } else if (board[fromY][fromX] == king
10359         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10360                && toY == fromY && toX < fromX-1) {
10361         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10362                                                                                   ; // castle with nearest piece
10363         board[fromY][toX+1] = board[fromY][rookX];
10364         board[fromY][rookX] = EmptySquare;
10365         board[fromY][fromX] = EmptySquare;
10366         board[toY][toX] = king;
10367     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10368                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10369                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10370                ) {
10371         /* white pawn promotion */
10372         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10373         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10374             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10375         board[fromY][fromX] = EmptySquare;
10376     } else if ((fromY >= BOARD_HEIGHT>>1)
10377                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10378                && (toX != fromX)
10379                && gameInfo.variant != VariantXiangqi
10380                && gameInfo.variant != VariantBerolina
10381                && (pawn == WhitePawn)
10382                && (board[toY][toX] == EmptySquare)) {
10383         board[fromY][fromX] = EmptySquare;
10384         board[toY][toX] = piece;
10385         if(toY == epRank - 128 + 1)
10386             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10387         else
10388             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10389     } else if ((fromY == BOARD_HEIGHT-4)
10390                && (toX == fromX)
10391                && gameInfo.variant == VariantBerolina
10392                && (board[fromY][fromX] == WhitePawn)
10393                && (board[toY][toX] == EmptySquare)) {
10394         board[fromY][fromX] = EmptySquare;
10395         board[toY][toX] = WhitePawn;
10396         if(oldEP & EP_BEROLIN_A) {
10397                 captured = board[fromY][fromX-1];
10398                 board[fromY][fromX-1] = EmptySquare;
10399         }else{  captured = board[fromY][fromX+1];
10400                 board[fromY][fromX+1] = EmptySquare;
10401         }
10402     } else if (board[fromY][fromX] == king
10403         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10404                && toY == fromY && toX > fromX+1) {
10405         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10406                                                                                              ;
10407         board[fromY][toX-1] = board[fromY][rookX];
10408         board[fromY][rookX] = EmptySquare;
10409         board[fromY][fromX] = EmptySquare;
10410         board[toY][toX] = king;
10411     } else if (board[fromY][fromX] == king
10412         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10413                && toY == fromY && toX < fromX-1) {
10414         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10415                                                                                 ;
10416         board[fromY][toX+1] = board[fromY][rookX];
10417         board[fromY][rookX] = EmptySquare;
10418         board[fromY][fromX] = EmptySquare;
10419         board[toY][toX] = king;
10420     } else if (fromY == 7 && fromX == 3
10421                && board[fromY][fromX] == BlackKing
10422                && toY == 7 && toX == 5) {
10423         board[fromY][fromX] = EmptySquare;
10424         board[toY][toX] = BlackKing;
10425         board[fromY][7] = EmptySquare;
10426         board[toY][4] = BlackRook;
10427     } else if (fromY == 7 && fromX == 3
10428                && board[fromY][fromX] == BlackKing
10429                && toY == 7 && toX == 1) {
10430         board[fromY][fromX] = EmptySquare;
10431         board[toY][toX] = BlackKing;
10432         board[fromY][0] = EmptySquare;
10433         board[toY][2] = BlackRook;
10434     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10435                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10436                && toY < promoRank && promoChar
10437                ) {
10438         /* black pawn promotion */
10439         board[toY][toX] = CharToPiece(ToLower(promoChar));
10440         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10441             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10442         board[fromY][fromX] = EmptySquare;
10443     } else if ((fromY < BOARD_HEIGHT>>1)
10444                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10445                && (toX != fromX)
10446                && gameInfo.variant != VariantXiangqi
10447                && gameInfo.variant != VariantBerolina
10448                && (pawn == BlackPawn)
10449                && (board[toY][toX] == EmptySquare)) {
10450         board[fromY][fromX] = EmptySquare;
10451         board[toY][toX] = piece;
10452         if(toY == epRank - 128 - 1)
10453             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10454         else
10455             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10456     } else if ((fromY == 3)
10457                && (toX == fromX)
10458                && gameInfo.variant == VariantBerolina
10459                && (board[fromY][fromX] == BlackPawn)
10460                && (board[toY][toX] == EmptySquare)) {
10461         board[fromY][fromX] = EmptySquare;
10462         board[toY][toX] = BlackPawn;
10463         if(oldEP & EP_BEROLIN_A) {
10464                 captured = board[fromY][fromX-1];
10465                 board[fromY][fromX-1] = EmptySquare;
10466         }else{  captured = board[fromY][fromX+1];
10467                 board[fromY][fromX+1] = EmptySquare;
10468         }
10469     } else {
10470         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10471         board[fromY][fromX] = EmptySquare;
10472         board[toY][toX] = piece;
10473     }
10474   }
10475
10476     if (gameInfo.holdingsWidth != 0) {
10477
10478       /* !!A lot more code needs to be written to support holdings  */
10479       /* [HGM] OK, so I have written it. Holdings are stored in the */
10480       /* penultimate board files, so they are automaticlly stored   */
10481       /* in the game history.                                       */
10482       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10483                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10484         /* Delete from holdings, by decreasing count */
10485         /* and erasing image if necessary            */
10486         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10487         if(p < (int) BlackPawn) { /* white drop */
10488              p -= (int)WhitePawn;
10489                  p = PieceToNumber((ChessSquare)p);
10490              if(p >= gameInfo.holdingsSize) p = 0;
10491              if(--board[p][BOARD_WIDTH-2] <= 0)
10492                   board[p][BOARD_WIDTH-1] = EmptySquare;
10493              if((int)board[p][BOARD_WIDTH-2] < 0)
10494                         board[p][BOARD_WIDTH-2] = 0;
10495         } else {                  /* black drop */
10496              p -= (int)BlackPawn;
10497                  p = PieceToNumber((ChessSquare)p);
10498              if(p >= gameInfo.holdingsSize) p = 0;
10499              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10500                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10501              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10502                         board[BOARD_HEIGHT-1-p][1] = 0;
10503         }
10504       }
10505       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10506           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10507         /* [HGM] holdings: Add to holdings, if holdings exist */
10508         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10509                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10510                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10511         }
10512         p = (int) captured;
10513         if (p >= (int) BlackPawn) {
10514           p -= (int)BlackPawn;
10515           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10516                   /* Restore shogi-promoted piece to its original  first */
10517                   captured = (ChessSquare) (DEMOTED(captured));
10518                   p = DEMOTED(p);
10519           }
10520           p = PieceToNumber((ChessSquare)p);
10521           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10522           board[p][BOARD_WIDTH-2]++;
10523           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10524         } else {
10525           p -= (int)WhitePawn;
10526           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10527                   captured = (ChessSquare) (DEMOTED(captured));
10528                   p = DEMOTED(p);
10529           }
10530           p = PieceToNumber((ChessSquare)p);
10531           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10532           board[BOARD_HEIGHT-1-p][1]++;
10533           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10534         }
10535       }
10536     } else if (gameInfo.variant == VariantAtomic) {
10537       if (captured != EmptySquare) {
10538         int y, x;
10539         for (y = toY-1; y <= toY+1; y++) {
10540           for (x = toX-1; x <= toX+1; x++) {
10541             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10542                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10543               board[y][x] = EmptySquare;
10544             }
10545           }
10546         }
10547         board[toY][toX] = EmptySquare;
10548       }
10549     }
10550
10551     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10552         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10553     } else
10554     if(promoChar == '+') {
10555         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10556         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10557         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10558           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10559     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10560         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10561         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10562            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10563         board[toY][toX] = newPiece;
10564     }
10565     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10566                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10567         // [HGM] superchess: take promotion piece out of holdings
10568         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10569         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10570             if(!--board[k][BOARD_WIDTH-2])
10571                 board[k][BOARD_WIDTH-1] = EmptySquare;
10572         } else {
10573             if(!--board[BOARD_HEIGHT-1-k][1])
10574                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10575         }
10576     }
10577 }
10578
10579 /* Updates forwardMostMove */
10580 void
10581 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10582 {
10583     int x = toX, y = toY;
10584     char *s = parseList[forwardMostMove];
10585     ChessSquare p = boards[forwardMostMove][toY][toX];
10586 //    forwardMostMove++; // [HGM] bare: moved downstream
10587
10588     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10589     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10590     (void) CoordsToAlgebraic(boards[forwardMostMove],
10591                              PosFlags(forwardMostMove),
10592                              fromY, fromX, y, x, (killX < 0)*promoChar,
10593                              s);
10594     if(kill2X >= 0 && kill2Y >= 0)
10595         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10596     if(killX >= 0 && killY >= 0)
10597         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10598                                            toX + AAA, toY + ONE - '0', promoChar);
10599
10600     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10601         int timeLeft; static int lastLoadFlag=0; int king, piece;
10602         piece = boards[forwardMostMove][fromY][fromX];
10603         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10604         if(gameInfo.variant == VariantKnightmate)
10605             king += (int) WhiteUnicorn - (int) WhiteKing;
10606         if(forwardMostMove == 0) {
10607             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10608                 fprintf(serverMoves, "%s;", UserName());
10609             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10610                 fprintf(serverMoves, "%s;", second.tidy);
10611             fprintf(serverMoves, "%s;", first.tidy);
10612             if(gameMode == MachinePlaysWhite)
10613                 fprintf(serverMoves, "%s;", UserName());
10614             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10615                 fprintf(serverMoves, "%s;", second.tidy);
10616         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10617         lastLoadFlag = loadFlag;
10618         // print base move
10619         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10620         // print castling suffix
10621         if( toY == fromY && piece == king ) {
10622             if(toX-fromX > 1)
10623                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10624             if(fromX-toX >1)
10625                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10626         }
10627         // e.p. suffix
10628         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10629              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10630              boards[forwardMostMove][toY][toX] == EmptySquare
10631              && fromX != toX && fromY != toY)
10632                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10633         // promotion suffix
10634         if(promoChar != NULLCHAR) {
10635             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10636                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10637                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10638             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10639         }
10640         if(!loadFlag) {
10641                 char buf[MOVE_LEN*2], *p; int len;
10642             fprintf(serverMoves, "/%d/%d",
10643                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10644             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10645             else                      timeLeft = blackTimeRemaining/1000;
10646             fprintf(serverMoves, "/%d", timeLeft);
10647                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10648                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10649                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10650                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10651             fprintf(serverMoves, "/%s", buf);
10652         }
10653         fflush(serverMoves);
10654     }
10655
10656     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10657         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10658       return;
10659     }
10660     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10661     if (commentList[forwardMostMove+1] != NULL) {
10662         free(commentList[forwardMostMove+1]);
10663         commentList[forwardMostMove+1] = NULL;
10664     }
10665     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10666     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10667     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10668     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10669     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10670     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10671     adjustedClock = FALSE;
10672     gameInfo.result = GameUnfinished;
10673     if (gameInfo.resultDetails != NULL) {
10674         free(gameInfo.resultDetails);
10675         gameInfo.resultDetails = NULL;
10676     }
10677     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10678                               moveList[forwardMostMove - 1]);
10679     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10680       case MT_NONE:
10681       case MT_STALEMATE:
10682       default:
10683         break;
10684       case MT_CHECK:
10685         if(!IS_SHOGI(gameInfo.variant))
10686             strcat(parseList[forwardMostMove - 1], "+");
10687         break;
10688       case MT_CHECKMATE:
10689       case MT_STAINMATE:
10690         strcat(parseList[forwardMostMove - 1], "#");
10691         break;
10692     }
10693 }
10694
10695 /* Updates currentMove if not pausing */
10696 void
10697 ShowMove (int fromX, int fromY, int toX, int toY)
10698 {
10699     int instant = (gameMode == PlayFromGameFile) ?
10700         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10701     if(appData.noGUI) return;
10702     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10703         if (!instant) {
10704             if (forwardMostMove == currentMove + 1) {
10705                 AnimateMove(boards[forwardMostMove - 1],
10706                             fromX, fromY, toX, toY);
10707             }
10708         }
10709         currentMove = forwardMostMove;
10710     }
10711
10712     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10713
10714     if (instant) return;
10715
10716     DisplayMove(currentMove - 1);
10717     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10718             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10719                 SetHighlights(fromX, fromY, toX, toY);
10720             }
10721     }
10722     DrawPosition(FALSE, boards[currentMove]);
10723     DisplayBothClocks();
10724     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10725 }
10726
10727 void
10728 SendEgtPath (ChessProgramState *cps)
10729 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10730         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10731
10732         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10733
10734         while(*p) {
10735             char c, *q = name+1, *r, *s;
10736
10737             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10738             while(*p && *p != ',') *q++ = *p++;
10739             *q++ = ':'; *q = 0;
10740             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10741                 strcmp(name, ",nalimov:") == 0 ) {
10742                 // take nalimov path from the menu-changeable option first, if it is defined
10743               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10744                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10745             } else
10746             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10747                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10748                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10749                 s = r = StrStr(s, ":") + 1; // beginning of path info
10750                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10751                 c = *r; *r = 0;             // temporarily null-terminate path info
10752                     *--q = 0;               // strip of trailig ':' from name
10753                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10754                 *r = c;
10755                 SendToProgram(buf,cps);     // send egtbpath command for this format
10756             }
10757             if(*p == ',') p++; // read away comma to position for next format name
10758         }
10759 }
10760
10761 static int
10762 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10763 {
10764       int width = 8, height = 8, holdings = 0;             // most common sizes
10765       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10766       // correct the deviations default for each variant
10767       if( v == VariantXiangqi ) width = 9,  height = 10;
10768       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10769       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10770       if( v == VariantCapablanca || v == VariantCapaRandom ||
10771           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10772                                 width = 10;
10773       if( v == VariantCourier ) width = 12;
10774       if( v == VariantSuper )                            holdings = 8;
10775       if( v == VariantGreat )   width = 10,              holdings = 8;
10776       if( v == VariantSChess )                           holdings = 7;
10777       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10778       if( v == VariantChuChess) width = 10, height = 10;
10779       if( v == VariantChu )     width = 12, height = 12;
10780       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10781              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10782              holdingsSize >= 0 && holdingsSize != holdings;
10783 }
10784
10785 char variantError[MSG_SIZ];
10786
10787 char *
10788 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10789 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10790       char *p, *variant = VariantName(v);
10791       static char b[MSG_SIZ];
10792       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10793            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10794                                                holdingsSize, variant); // cook up sized variant name
10795            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10796            if(StrStr(list, b) == NULL) {
10797                // specific sized variant not known, check if general sizing allowed
10798                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10799                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10800                             boardWidth, boardHeight, holdingsSize, engine);
10801                    return NULL;
10802                }
10803                /* [HGM] here we really should compare with the maximum supported board size */
10804            }
10805       } else snprintf(b, MSG_SIZ,"%s", variant);
10806       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10807       p = StrStr(list, b);
10808       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10809       if(p == NULL) {
10810           // occurs not at all in list, or only as sub-string
10811           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10812           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10813               int l = strlen(variantError);
10814               char *q;
10815               while(p != list && p[-1] != ',') p--;
10816               q = strchr(p, ',');
10817               if(q) *q = NULLCHAR;
10818               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10819               if(q) *q= ',';
10820           }
10821           return NULL;
10822       }
10823       return b;
10824 }
10825
10826 void
10827 InitChessProgram (ChessProgramState *cps, int setup)
10828 /* setup needed to setup FRC opening position */
10829 {
10830     char buf[MSG_SIZ], *b;
10831     if (appData.noChessProgram) return;
10832     hintRequested = FALSE;
10833     bookRequested = FALSE;
10834
10835     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10836     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10837     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10838     if(cps->memSize) { /* [HGM] memory */
10839       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10840         SendToProgram(buf, cps);
10841     }
10842     SendEgtPath(cps); /* [HGM] EGT */
10843     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10844       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10845         SendToProgram(buf, cps);
10846     }
10847
10848     setboardSpoiledMachineBlack = FALSE;
10849     SendToProgram(cps->initString, cps);
10850     if (gameInfo.variant != VariantNormal &&
10851         gameInfo.variant != VariantLoadable
10852         /* [HGM] also send variant if board size non-standard */
10853         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10854
10855       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10856                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10857
10858       if (b == NULL) {
10859         VariantClass v;
10860         char c, *q = cps->variants, *p = strchr(q, ',');
10861         if(p) *p = NULLCHAR;
10862         v = StringToVariant(q);
10863         DisplayError(variantError, 0);
10864         if(v != VariantUnknown && cps == &first) {
10865             int w, h, s;
10866             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10867                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10868             ASSIGN(appData.variant, q);
10869             Reset(TRUE, FALSE);
10870         }
10871         if(p) *p = ',';
10872         return;
10873       }
10874
10875       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10876       SendToProgram(buf, cps);
10877     }
10878     currentlyInitializedVariant = gameInfo.variant;
10879
10880     /* [HGM] send opening position in FRC to first engine */
10881     if(setup) {
10882           SendToProgram("force\n", cps);
10883           SendBoard(cps, 0);
10884           /* engine is now in force mode! Set flag to wake it up after first move. */
10885           setboardSpoiledMachineBlack = 1;
10886     }
10887
10888     if (cps->sendICS) {
10889       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10890       SendToProgram(buf, cps);
10891     }
10892     cps->maybeThinking = FALSE;
10893     cps->offeredDraw = 0;
10894     if (!appData.icsActive) {
10895         SendTimeControl(cps, movesPerSession, timeControl,
10896                         timeIncrement, appData.searchDepth,
10897                         searchTime);
10898     }
10899     if (appData.showThinking
10900         // [HGM] thinking: four options require thinking output to be sent
10901         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10902                                 ) {
10903         SendToProgram("post\n", cps);
10904     }
10905     SendToProgram("hard\n", cps);
10906     if (!appData.ponderNextMove) {
10907         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10908            it without being sure what state we are in first.  "hard"
10909            is not a toggle, so that one is OK.
10910          */
10911         SendToProgram("easy\n", cps);
10912     }
10913     if (cps->usePing) {
10914       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10915       SendToProgram(buf, cps);
10916     }
10917     cps->initDone = TRUE;
10918     ClearEngineOutputPane(cps == &second);
10919 }
10920
10921
10922 void
10923 ResendOptions (ChessProgramState *cps)
10924 { // send the stored value of the options
10925   int i;
10926   char buf[MSG_SIZ];
10927   Option *opt = cps->option;
10928   for(i=0; i<cps->nrOptions; i++, opt++) {
10929       switch(opt->type) {
10930         case Spin:
10931         case Slider:
10932         case CheckBox:
10933             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10934           break;
10935         case ComboBox:
10936           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10937           break;
10938         default:
10939             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10940           break;
10941         case Button:
10942         case SaveButton:
10943           continue;
10944       }
10945       SendToProgram(buf, cps);
10946   }
10947 }
10948
10949 void
10950 StartChessProgram (ChessProgramState *cps)
10951 {
10952     char buf[MSG_SIZ];
10953     int err;
10954
10955     if (appData.noChessProgram) return;
10956     cps->initDone = FALSE;
10957
10958     if (strcmp(cps->host, "localhost") == 0) {
10959         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10960     } else if (*appData.remoteShell == NULLCHAR) {
10961         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10962     } else {
10963         if (*appData.remoteUser == NULLCHAR) {
10964           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10965                     cps->program);
10966         } else {
10967           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10968                     cps->host, appData.remoteUser, cps->program);
10969         }
10970         err = StartChildProcess(buf, "", &cps->pr);
10971     }
10972
10973     if (err != 0) {
10974       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10975         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10976         if(cps != &first) return;
10977         appData.noChessProgram = TRUE;
10978         ThawUI();
10979         SetNCPMode();
10980 //      DisplayFatalError(buf, err, 1);
10981 //      cps->pr = NoProc;
10982 //      cps->isr = NULL;
10983         return;
10984     }
10985
10986     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10987     if (cps->protocolVersion > 1) {
10988       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10989       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10990         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10991         cps->comboCnt = 0;  //                and values of combo boxes
10992       }
10993       SendToProgram(buf, cps);
10994       if(cps->reload) ResendOptions(cps);
10995     } else {
10996       SendToProgram("xboard\n", cps);
10997     }
10998 }
10999
11000 void
11001 TwoMachinesEventIfReady P((void))
11002 {
11003   static int curMess = 0;
11004   if (first.lastPing != first.lastPong) {
11005     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11006     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11007     return;
11008   }
11009   if (second.lastPing != second.lastPong) {
11010     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11011     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11012     return;
11013   }
11014   DisplayMessage("", ""); curMess = 0;
11015   TwoMachinesEvent();
11016 }
11017
11018 char *
11019 MakeName (char *template)
11020 {
11021     time_t clock;
11022     struct tm *tm;
11023     static char buf[MSG_SIZ];
11024     char *p = buf;
11025     int i;
11026
11027     clock = time((time_t *)NULL);
11028     tm = localtime(&clock);
11029
11030     while(*p++ = *template++) if(p[-1] == '%') {
11031         switch(*template++) {
11032           case 0:   *p = 0; return buf;
11033           case 'Y': i = tm->tm_year+1900; break;
11034           case 'y': i = tm->tm_year-100; break;
11035           case 'M': i = tm->tm_mon+1; break;
11036           case 'd': i = tm->tm_mday; break;
11037           case 'h': i = tm->tm_hour; break;
11038           case 'm': i = tm->tm_min; break;
11039           case 's': i = tm->tm_sec; break;
11040           default:  i = 0;
11041         }
11042         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11043     }
11044     return buf;
11045 }
11046
11047 int
11048 CountPlayers (char *p)
11049 {
11050     int n = 0;
11051     while(p = strchr(p, '\n')) p++, n++; // count participants
11052     return n;
11053 }
11054
11055 FILE *
11056 WriteTourneyFile (char *results, FILE *f)
11057 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11058     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11059     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11060         // create a file with tournament description
11061         fprintf(f, "-participants {%s}\n", appData.participants);
11062         fprintf(f, "-seedBase %d\n", appData.seedBase);
11063         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11064         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11065         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11066         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11067         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11068         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11069         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11070         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11071         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11072         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11073         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11074         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11075         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11076         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11077         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11078         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11079         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11080         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11081         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11082         fprintf(f, "-smpCores %d\n", appData.smpCores);
11083         if(searchTime > 0)
11084                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11085         else {
11086                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11087                 fprintf(f, "-tc %s\n", appData.timeControl);
11088                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11089         }
11090         fprintf(f, "-results \"%s\"\n", results);
11091     }
11092     return f;
11093 }
11094
11095 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11096
11097 void
11098 Substitute (char *participants, int expunge)
11099 {
11100     int i, changed, changes=0, nPlayers=0;
11101     char *p, *q, *r, buf[MSG_SIZ];
11102     if(participants == NULL) return;
11103     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11104     r = p = participants; q = appData.participants;
11105     while(*p && *p == *q) {
11106         if(*p == '\n') r = p+1, nPlayers++;
11107         p++; q++;
11108     }
11109     if(*p) { // difference
11110         while(*p && *p++ != '\n')
11111                                  ;
11112         while(*q && *q++ != '\n')
11113                                  ;
11114       changed = nPlayers;
11115         changes = 1 + (strcmp(p, q) != 0);
11116     }
11117     if(changes == 1) { // a single engine mnemonic was changed
11118         q = r; while(*q) nPlayers += (*q++ == '\n');
11119         p = buf; while(*r && (*p = *r++) != '\n') p++;
11120         *p = NULLCHAR;
11121         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11122         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11123         if(mnemonic[i]) { // The substitute is valid
11124             FILE *f;
11125             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11126                 flock(fileno(f), LOCK_EX);
11127                 ParseArgsFromFile(f);
11128                 fseek(f, 0, SEEK_SET);
11129                 FREE(appData.participants); appData.participants = participants;
11130                 if(expunge) { // erase results of replaced engine
11131                     int len = strlen(appData.results), w, b, dummy;
11132                     for(i=0; i<len; i++) {
11133                         Pairing(i, nPlayers, &w, &b, &dummy);
11134                         if((w == changed || b == changed) && appData.results[i] == '*') {
11135                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11136                             fclose(f);
11137                             return;
11138                         }
11139                     }
11140                     for(i=0; i<len; i++) {
11141                         Pairing(i, nPlayers, &w, &b, &dummy);
11142                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11143                     }
11144                 }
11145                 WriteTourneyFile(appData.results, f);
11146                 fclose(f); // release lock
11147                 return;
11148             }
11149         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11150     }
11151     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11152     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11153     free(participants);
11154     return;
11155 }
11156
11157 int
11158 CheckPlayers (char *participants)
11159 {
11160         int i;
11161         char buf[MSG_SIZ], *p;
11162         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11163         while(p = strchr(participants, '\n')) {
11164             *p = NULLCHAR;
11165             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11166             if(!mnemonic[i]) {
11167                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11168                 *p = '\n';
11169                 DisplayError(buf, 0);
11170                 return 1;
11171             }
11172             *p = '\n';
11173             participants = p + 1;
11174         }
11175         return 0;
11176 }
11177
11178 int
11179 CreateTourney (char *name)
11180 {
11181         FILE *f;
11182         if(matchMode && strcmp(name, appData.tourneyFile)) {
11183              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11184         }
11185         if(name[0] == NULLCHAR) {
11186             if(appData.participants[0])
11187                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11188             return 0;
11189         }
11190         f = fopen(name, "r");
11191         if(f) { // file exists
11192             ASSIGN(appData.tourneyFile, name);
11193             ParseArgsFromFile(f); // parse it
11194         } else {
11195             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11196             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11197                 DisplayError(_("Not enough participants"), 0);
11198                 return 0;
11199             }
11200             if(CheckPlayers(appData.participants)) return 0;
11201             ASSIGN(appData.tourneyFile, name);
11202             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11203             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11204         }
11205         fclose(f);
11206         appData.noChessProgram = FALSE;
11207         appData.clockMode = TRUE;
11208         SetGNUMode();
11209         return 1;
11210 }
11211
11212 int
11213 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11214 {
11215     char buf[MSG_SIZ], *p, *q;
11216     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11217     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11218     skip = !all && group[0]; // if group requested, we start in skip mode
11219     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11220         p = names; q = buf; header = 0;
11221         while(*p && *p != '\n') *q++ = *p++;
11222         *q = 0;
11223         if(*p == '\n') p++;
11224         if(buf[0] == '#') {
11225             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11226             depth++; // we must be entering a new group
11227             if(all) continue; // suppress printing group headers when complete list requested
11228             header = 1;
11229             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11230         }
11231         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11232         if(engineList[i]) free(engineList[i]);
11233         engineList[i] = strdup(buf);
11234         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11235         if(engineMnemonic[i]) free(engineMnemonic[i]);
11236         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11237             strcat(buf, " (");
11238             sscanf(q + 8, "%s", buf + strlen(buf));
11239             strcat(buf, ")");
11240         }
11241         engineMnemonic[i] = strdup(buf);
11242         i++;
11243     }
11244     engineList[i] = engineMnemonic[i] = NULL;
11245     return i;
11246 }
11247
11248 // following implemented as macro to avoid type limitations
11249 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11250
11251 void
11252 SwapEngines (int n)
11253 {   // swap settings for first engine and other engine (so far only some selected options)
11254     int h;
11255     char *p;
11256     if(n == 0) return;
11257     SWAP(directory, p)
11258     SWAP(chessProgram, p)
11259     SWAP(isUCI, h)
11260     SWAP(hasOwnBookUCI, h)
11261     SWAP(protocolVersion, h)
11262     SWAP(reuse, h)
11263     SWAP(scoreIsAbsolute, h)
11264     SWAP(timeOdds, h)
11265     SWAP(logo, p)
11266     SWAP(pgnName, p)
11267     SWAP(pvSAN, h)
11268     SWAP(engOptions, p)
11269     SWAP(engInitString, p)
11270     SWAP(computerString, p)
11271     SWAP(features, p)
11272     SWAP(fenOverride, p)
11273     SWAP(NPS, h)
11274     SWAP(accumulateTC, h)
11275     SWAP(drawDepth, h)
11276     SWAP(host, p)
11277     SWAP(pseudo, h)
11278 }
11279
11280 int
11281 GetEngineLine (char *s, int n)
11282 {
11283     int i;
11284     char buf[MSG_SIZ];
11285     extern char *icsNames;
11286     if(!s || !*s) return 0;
11287     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11288     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11289     if(!mnemonic[i]) return 0;
11290     if(n == 11) return 1; // just testing if there was a match
11291     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11292     if(n == 1) SwapEngines(n);
11293     ParseArgsFromString(buf);
11294     if(n == 1) SwapEngines(n);
11295     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11296         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11297         ParseArgsFromString(buf);
11298     }
11299     return 1;
11300 }
11301
11302 int
11303 SetPlayer (int player, char *p)
11304 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11305     int i;
11306     char buf[MSG_SIZ], *engineName;
11307     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11308     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11309     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11310     if(mnemonic[i]) {
11311         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11312         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11313         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11314         ParseArgsFromString(buf);
11315     } else { // no engine with this nickname is installed!
11316         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11317         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11318         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11319         ModeHighlight();
11320         DisplayError(buf, 0);
11321         return 0;
11322     }
11323     free(engineName);
11324     return i;
11325 }
11326
11327 char *recentEngines;
11328
11329 void
11330 RecentEngineEvent (int nr)
11331 {
11332     int n;
11333 //    SwapEngines(1); // bump first to second
11334 //    ReplaceEngine(&second, 1); // and load it there
11335     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11336     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11337     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11338         ReplaceEngine(&first, 0);
11339         FloatToFront(&appData.recentEngineList, command[n]);
11340     }
11341 }
11342
11343 int
11344 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11345 {   // determine players from game number
11346     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11347
11348     if(appData.tourneyType == 0) {
11349         roundsPerCycle = (nPlayers - 1) | 1;
11350         pairingsPerRound = nPlayers / 2;
11351     } else if(appData.tourneyType > 0) {
11352         roundsPerCycle = nPlayers - appData.tourneyType;
11353         pairingsPerRound = appData.tourneyType;
11354     }
11355     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11356     gamesPerCycle = gamesPerRound * roundsPerCycle;
11357     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11358     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11359     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11360     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11361     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11362     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11363
11364     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11365     if(appData.roundSync) *syncInterval = gamesPerRound;
11366
11367     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11368
11369     if(appData.tourneyType == 0) {
11370         if(curPairing == (nPlayers-1)/2 ) {
11371             *whitePlayer = curRound;
11372             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11373         } else {
11374             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11375             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11376             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11377             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11378         }
11379     } else if(appData.tourneyType > 1) {
11380         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11381         *whitePlayer = curRound + appData.tourneyType;
11382     } else if(appData.tourneyType > 0) {
11383         *whitePlayer = curPairing;
11384         *blackPlayer = curRound + appData.tourneyType;
11385     }
11386
11387     // take care of white/black alternation per round.
11388     // For cycles and games this is already taken care of by default, derived from matchGame!
11389     return curRound & 1;
11390 }
11391
11392 int
11393 NextTourneyGame (int nr, int *swapColors)
11394 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11395     char *p, *q;
11396     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11397     FILE *tf;
11398     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11399     tf = fopen(appData.tourneyFile, "r");
11400     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11401     ParseArgsFromFile(tf); fclose(tf);
11402     InitTimeControls(); // TC might be altered from tourney file
11403
11404     nPlayers = CountPlayers(appData.participants); // count participants
11405     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11406     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11407
11408     if(syncInterval) {
11409         p = q = appData.results;
11410         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11411         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11412             DisplayMessage(_("Waiting for other game(s)"),"");
11413             waitingForGame = TRUE;
11414             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11415             return 0;
11416         }
11417         waitingForGame = FALSE;
11418     }
11419
11420     if(appData.tourneyType < 0) {
11421         if(nr>=0 && !pairingReceived) {
11422             char buf[1<<16];
11423             if(pairing.pr == NoProc) {
11424                 if(!appData.pairingEngine[0]) {
11425                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11426                     return 0;
11427                 }
11428                 StartChessProgram(&pairing); // starts the pairing engine
11429             }
11430             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11431             SendToProgram(buf, &pairing);
11432             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11433             SendToProgram(buf, &pairing);
11434             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11435         }
11436         pairingReceived = 0;                              // ... so we continue here
11437         *swapColors = 0;
11438         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11439         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11440         matchGame = 1; roundNr = nr / syncInterval + 1;
11441     }
11442
11443     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11444
11445     // redefine engines, engine dir, etc.
11446     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11447     if(first.pr == NoProc) {
11448       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11449       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11450     }
11451     if(second.pr == NoProc) {
11452       SwapEngines(1);
11453       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11454       SwapEngines(1);         // and make that valid for second engine by swapping
11455       InitEngine(&second, 1);
11456     }
11457     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11458     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11459     return OK;
11460 }
11461
11462 void
11463 NextMatchGame ()
11464 {   // performs game initialization that does not invoke engines, and then tries to start the game
11465     int res, firstWhite, swapColors = 0;
11466     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11467     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
11468         char buf[MSG_SIZ];
11469         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11470         if(strcmp(buf, currentDebugFile)) { // name has changed
11471             FILE *f = fopen(buf, "w");
11472             if(f) { // if opening the new file failed, just keep using the old one
11473                 ASSIGN(currentDebugFile, buf);
11474                 fclose(debugFP);
11475                 debugFP = f;
11476             }
11477             if(appData.serverFileName) {
11478                 if(serverFP) fclose(serverFP);
11479                 serverFP = fopen(appData.serverFileName, "w");
11480                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11481                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11482             }
11483         }
11484     }
11485     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11486     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11487     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11488     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11489     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11490     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11491     Reset(FALSE, first.pr != NoProc);
11492     res = LoadGameOrPosition(matchGame); // setup game
11493     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11494     if(!res) return; // abort when bad game/pos file
11495     if(appData.epd) {// in EPD mode we make sure first engine is to move
11496         firstWhite = !(forwardMostMove & 1);
11497         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11498         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11499     }
11500     TwoMachinesEvent();
11501 }
11502
11503 void
11504 UserAdjudicationEvent (int result)
11505 {
11506     ChessMove gameResult = GameIsDrawn;
11507
11508     if( result > 0 ) {
11509         gameResult = WhiteWins;
11510     }
11511     else if( result < 0 ) {
11512         gameResult = BlackWins;
11513     }
11514
11515     if( gameMode == TwoMachinesPlay ) {
11516         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11517     }
11518 }
11519
11520
11521 // [HGM] save: calculate checksum of game to make games easily identifiable
11522 int
11523 StringCheckSum (char *s)
11524 {
11525         int i = 0;
11526         if(s==NULL) return 0;
11527         while(*s) i = i*259 + *s++;
11528         return i;
11529 }
11530
11531 int
11532 GameCheckSum ()
11533 {
11534         int i, sum=0;
11535         for(i=backwardMostMove; i<forwardMostMove; i++) {
11536                 sum += pvInfoList[i].depth;
11537                 sum += StringCheckSum(parseList[i]);
11538                 sum += StringCheckSum(commentList[i]);
11539                 sum *= 261;
11540         }
11541         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11542         return sum + StringCheckSum(commentList[i]);
11543 } // end of save patch
11544
11545 void
11546 GameEnds (ChessMove result, char *resultDetails, int whosays)
11547 {
11548     GameMode nextGameMode;
11549     int isIcsGame;
11550     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11551
11552     if(endingGame) return; /* [HGM] crash: forbid recursion */
11553     endingGame = 1;
11554     if(twoBoards) { // [HGM] dual: switch back to one board
11555         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11556         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11557     }
11558     if (appData.debugMode) {
11559       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11560               result, resultDetails ? resultDetails : "(null)", whosays);
11561     }
11562
11563     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11564
11565     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11566
11567     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11568         /* If we are playing on ICS, the server decides when the
11569            game is over, but the engine can offer to draw, claim
11570            a draw, or resign.
11571          */
11572 #if ZIPPY
11573         if (appData.zippyPlay && first.initDone) {
11574             if (result == GameIsDrawn) {
11575                 /* In case draw still needs to be claimed */
11576                 SendToICS(ics_prefix);
11577                 SendToICS("draw\n");
11578             } else if (StrCaseStr(resultDetails, "resign")) {
11579                 SendToICS(ics_prefix);
11580                 SendToICS("resign\n");
11581             }
11582         }
11583 #endif
11584         endingGame = 0; /* [HGM] crash */
11585         return;
11586     }
11587
11588     /* If we're loading the game from a file, stop */
11589     if (whosays == GE_FILE) {
11590       (void) StopLoadGameTimer();
11591       gameFileFP = NULL;
11592     }
11593
11594     /* Cancel draw offers */
11595     first.offeredDraw = second.offeredDraw = 0;
11596
11597     /* If this is an ICS game, only ICS can really say it's done;
11598        if not, anyone can. */
11599     isIcsGame = (gameMode == IcsPlayingWhite ||
11600                  gameMode == IcsPlayingBlack ||
11601                  gameMode == IcsObserving    ||
11602                  gameMode == IcsExamining);
11603
11604     if (!isIcsGame || whosays == GE_ICS) {
11605         /* OK -- not an ICS game, or ICS said it was done */
11606         StopClocks();
11607         if (!isIcsGame && !appData.noChessProgram)
11608           SetUserThinkingEnables();
11609
11610         /* [HGM] if a machine claims the game end we verify this claim */
11611         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11612             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11613                 char claimer;
11614                 ChessMove trueResult = (ChessMove) -1;
11615
11616                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11617                                             first.twoMachinesColor[0] :
11618                                             second.twoMachinesColor[0] ;
11619
11620                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11621                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11622                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11623                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11624                 } else
11625                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11626                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11627                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11628                 } else
11629                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11630                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11631                 }
11632
11633                 // now verify win claims, but not in drop games, as we don't understand those yet
11634                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11635                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11636                     (result == WhiteWins && claimer == 'w' ||
11637                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11638                       if (appData.debugMode) {
11639                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11640                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11641                       }
11642                       if(result != trueResult) {
11643                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11644                               result = claimer == 'w' ? BlackWins : WhiteWins;
11645                               resultDetails = buf;
11646                       }
11647                 } else
11648                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11649                     && (forwardMostMove <= backwardMostMove ||
11650                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11651                         (claimer=='b')==(forwardMostMove&1))
11652                                                                                   ) {
11653                       /* [HGM] verify: draws that were not flagged are false claims */
11654                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11655                       result = claimer == 'w' ? BlackWins : WhiteWins;
11656                       resultDetails = buf;
11657                 }
11658                 /* (Claiming a loss is accepted no questions asked!) */
11659             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11660                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11661                 result = GameUnfinished;
11662                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11663             }
11664             /* [HGM] bare: don't allow bare King to win */
11665             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11666                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11667                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11668                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11669                && result != GameIsDrawn)
11670             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11671                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11672                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11673                         if(p >= 0 && p <= (int)WhiteKing) k++;
11674                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11675                 }
11676                 if (appData.debugMode) {
11677                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11678                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11679                 }
11680                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11681                         result = GameIsDrawn;
11682                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11683                         resultDetails = buf;
11684                 }
11685             }
11686         }
11687
11688
11689         if(serverMoves != NULL && !loadFlag) { char c = '=';
11690             if(result==WhiteWins) c = '+';
11691             if(result==BlackWins) c = '-';
11692             if(resultDetails != NULL)
11693                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11694         }
11695         if (resultDetails != NULL) {
11696             gameInfo.result = result;
11697             gameInfo.resultDetails = StrSave(resultDetails);
11698
11699             /* display last move only if game was not loaded from file */
11700             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11701                 DisplayMove(currentMove - 1);
11702
11703             if (forwardMostMove != 0) {
11704                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11705                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11706                                                                 ) {
11707                     if (*appData.saveGameFile != NULLCHAR) {
11708                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11709                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11710                         else
11711                         SaveGameToFile(appData.saveGameFile, TRUE);
11712                     } else if (appData.autoSaveGames) {
11713                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11714                     }
11715                     if (*appData.savePositionFile != NULLCHAR) {
11716                         SavePositionToFile(appData.savePositionFile);
11717                     }
11718                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11719                 }
11720             }
11721
11722             /* Tell program how game ended in case it is learning */
11723             /* [HGM] Moved this to after saving the PGN, just in case */
11724             /* engine died and we got here through time loss. In that */
11725             /* case we will get a fatal error writing the pipe, which */
11726             /* would otherwise lose us the PGN.                       */
11727             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11728             /* output during GameEnds should never be fatal anymore   */
11729             if (gameMode == MachinePlaysWhite ||
11730                 gameMode == MachinePlaysBlack ||
11731                 gameMode == TwoMachinesPlay ||
11732                 gameMode == IcsPlayingWhite ||
11733                 gameMode == IcsPlayingBlack ||
11734                 gameMode == BeginningOfGame) {
11735                 char buf[MSG_SIZ];
11736                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11737                         resultDetails);
11738                 if (first.pr != NoProc) {
11739                     SendToProgram(buf, &first);
11740                 }
11741                 if (second.pr != NoProc &&
11742                     gameMode == TwoMachinesPlay) {
11743                     SendToProgram(buf, &second);
11744                 }
11745             }
11746         }
11747
11748         if (appData.icsActive) {
11749             if (appData.quietPlay &&
11750                 (gameMode == IcsPlayingWhite ||
11751                  gameMode == IcsPlayingBlack)) {
11752                 SendToICS(ics_prefix);
11753                 SendToICS("set shout 1\n");
11754             }
11755             nextGameMode = IcsIdle;
11756             ics_user_moved = FALSE;
11757             /* clean up premove.  It's ugly when the game has ended and the
11758              * premove highlights are still on the board.
11759              */
11760             if (gotPremove) {
11761               gotPremove = FALSE;
11762               ClearPremoveHighlights();
11763               DrawPosition(FALSE, boards[currentMove]);
11764             }
11765             if (whosays == GE_ICS) {
11766                 switch (result) {
11767                 case WhiteWins:
11768                     if (gameMode == IcsPlayingWhite)
11769                         PlayIcsWinSound();
11770                     else if(gameMode == IcsPlayingBlack)
11771                         PlayIcsLossSound();
11772                     break;
11773                 case BlackWins:
11774                     if (gameMode == IcsPlayingBlack)
11775                         PlayIcsWinSound();
11776                     else if(gameMode == IcsPlayingWhite)
11777                         PlayIcsLossSound();
11778                     break;
11779                 case GameIsDrawn:
11780                     PlayIcsDrawSound();
11781                     break;
11782                 default:
11783                     PlayIcsUnfinishedSound();
11784                 }
11785             }
11786             if(appData.quitNext) { ExitEvent(0); return; }
11787         } else if (gameMode == EditGame ||
11788                    gameMode == PlayFromGameFile ||
11789                    gameMode == AnalyzeMode ||
11790                    gameMode == AnalyzeFile) {
11791             nextGameMode = gameMode;
11792         } else {
11793             nextGameMode = EndOfGame;
11794         }
11795         pausing = FALSE;
11796         ModeHighlight();
11797     } else {
11798         nextGameMode = gameMode;
11799     }
11800
11801     if (appData.noChessProgram) {
11802         gameMode = nextGameMode;
11803         ModeHighlight();
11804         endingGame = 0; /* [HGM] crash */
11805         return;
11806     }
11807
11808     if (first.reuse) {
11809         /* Put first chess program into idle state */
11810         if (first.pr != NoProc &&
11811             (gameMode == MachinePlaysWhite ||
11812              gameMode == MachinePlaysBlack ||
11813              gameMode == TwoMachinesPlay ||
11814              gameMode == IcsPlayingWhite ||
11815              gameMode == IcsPlayingBlack ||
11816              gameMode == BeginningOfGame)) {
11817             SendToProgram("force\n", &first);
11818             if (first.usePing) {
11819               char buf[MSG_SIZ];
11820               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11821               SendToProgram(buf, &first);
11822             }
11823         }
11824     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11825         /* Kill off first chess program */
11826         if (first.isr != NULL)
11827           RemoveInputSource(first.isr);
11828         first.isr = NULL;
11829
11830         if (first.pr != NoProc) {
11831             ExitAnalyzeMode();
11832             DoSleep( appData.delayBeforeQuit );
11833             SendToProgram("quit\n", &first);
11834             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11835             first.reload = TRUE;
11836         }
11837         first.pr = NoProc;
11838     }
11839     if (second.reuse) {
11840         /* Put second chess program into idle state */
11841         if (second.pr != NoProc &&
11842             gameMode == TwoMachinesPlay) {
11843             SendToProgram("force\n", &second);
11844             if (second.usePing) {
11845               char buf[MSG_SIZ];
11846               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11847               SendToProgram(buf, &second);
11848             }
11849         }
11850     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11851         /* Kill off second chess program */
11852         if (second.isr != NULL)
11853           RemoveInputSource(second.isr);
11854         second.isr = NULL;
11855
11856         if (second.pr != NoProc) {
11857             DoSleep( appData.delayBeforeQuit );
11858             SendToProgram("quit\n", &second);
11859             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11860             second.reload = TRUE;
11861         }
11862         second.pr = NoProc;
11863     }
11864
11865     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11866         char resChar = '=';
11867         switch (result) {
11868         case WhiteWins:
11869           resChar = '+';
11870           if (first.twoMachinesColor[0] == 'w') {
11871             first.matchWins++;
11872           } else {
11873             second.matchWins++;
11874           }
11875           break;
11876         case BlackWins:
11877           resChar = '-';
11878           if (first.twoMachinesColor[0] == 'b') {
11879             first.matchWins++;
11880           } else {
11881             second.matchWins++;
11882           }
11883           break;
11884         case GameUnfinished:
11885           resChar = ' ';
11886         default:
11887           break;
11888         }
11889
11890         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11891         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11892             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11893             ReserveGame(nextGame, resChar); // sets nextGame
11894             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11895             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11896         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11897
11898         if (nextGame <= appData.matchGames && !abortMatch) {
11899             gameMode = nextGameMode;
11900             matchGame = nextGame; // this will be overruled in tourney mode!
11901             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11902             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11903             endingGame = 0; /* [HGM] crash */
11904             return;
11905         } else {
11906             gameMode = nextGameMode;
11907             if(appData.epd) {
11908                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11909                 OutputKibitz(2, buf);
11910                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11911                 OutputKibitz(2, buf);
11912                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11913                 if(second.matchWins) OutputKibitz(2, buf);
11914                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11915                 OutputKibitz(2, buf);
11916             }
11917             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11918                      first.tidy, second.tidy,
11919                      first.matchWins, second.matchWins,
11920                      appData.matchGames - (first.matchWins + second.matchWins));
11921             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11922             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11923             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11924             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11925                 first.twoMachinesColor = "black\n";
11926                 second.twoMachinesColor = "white\n";
11927             } else {
11928                 first.twoMachinesColor = "white\n";
11929                 second.twoMachinesColor = "black\n";
11930             }
11931         }
11932     }
11933     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11934         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11935       ExitAnalyzeMode();
11936     gameMode = nextGameMode;
11937     ModeHighlight();
11938     endingGame = 0;  /* [HGM] crash */
11939     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11940         if(matchMode == TRUE) { // match through command line: exit with or without popup
11941             if(ranking) {
11942                 ToNrEvent(forwardMostMove);
11943                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11944                 else ExitEvent(0);
11945             } else DisplayFatalError(buf, 0, 0);
11946         } else { // match through menu; just stop, with or without popup
11947             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11948             ModeHighlight();
11949             if(ranking){
11950                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11951             } else DisplayNote(buf);
11952       }
11953       if(ranking) free(ranking);
11954     }
11955 }
11956
11957 /* Assumes program was just initialized (initString sent).
11958    Leaves program in force mode. */
11959 void
11960 FeedMovesToProgram (ChessProgramState *cps, int upto)
11961 {
11962     int i;
11963
11964     if (appData.debugMode)
11965       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11966               startedFromSetupPosition ? "position and " : "",
11967               backwardMostMove, upto, cps->which);
11968     if(currentlyInitializedVariant != gameInfo.variant) {
11969       char buf[MSG_SIZ];
11970         // [HGM] variantswitch: make engine aware of new variant
11971         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11972                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11973                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11974         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11975         SendToProgram(buf, cps);
11976         currentlyInitializedVariant = gameInfo.variant;
11977     }
11978     SendToProgram("force\n", cps);
11979     if (startedFromSetupPosition) {
11980         SendBoard(cps, backwardMostMove);
11981     if (appData.debugMode) {
11982         fprintf(debugFP, "feedMoves\n");
11983     }
11984     }
11985     for (i = backwardMostMove; i < upto; i++) {
11986         SendMoveToProgram(i, cps);
11987     }
11988 }
11989
11990
11991 int
11992 ResurrectChessProgram ()
11993 {
11994      /* The chess program may have exited.
11995         If so, restart it and feed it all the moves made so far. */
11996     static int doInit = 0;
11997
11998     if (appData.noChessProgram) return 1;
11999
12000     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12001         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12002         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12003         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12004     } else {
12005         if (first.pr != NoProc) return 1;
12006         StartChessProgram(&first);
12007     }
12008     InitChessProgram(&first, FALSE);
12009     FeedMovesToProgram(&first, currentMove);
12010
12011     if (!first.sendTime) {
12012         /* can't tell gnuchess what its clock should read,
12013            so we bow to its notion. */
12014         ResetClocks();
12015         timeRemaining[0][currentMove] = whiteTimeRemaining;
12016         timeRemaining[1][currentMove] = blackTimeRemaining;
12017     }
12018
12019     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12020                 appData.icsEngineAnalyze) && first.analysisSupport) {
12021       SendToProgram("analyze\n", &first);
12022       first.analyzing = TRUE;
12023     }
12024     return 1;
12025 }
12026
12027 /*
12028  * Button procedures
12029  */
12030 void
12031 Reset (int redraw, int init)
12032 {
12033     int i;
12034
12035     if (appData.debugMode) {
12036         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12037                 redraw, init, gameMode);
12038     }
12039     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12040     deadRanks = 0; // assume entire board is used
12041     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12042     CleanupTail(); // [HGM] vari: delete any stored variations
12043     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12044     pausing = pauseExamInvalid = FALSE;
12045     startedFromSetupPosition = blackPlaysFirst = FALSE;
12046     firstMove = TRUE;
12047     whiteFlag = blackFlag = FALSE;
12048     userOfferedDraw = FALSE;
12049     hintRequested = bookRequested = FALSE;
12050     first.maybeThinking = FALSE;
12051     second.maybeThinking = FALSE;
12052     first.bookSuspend = FALSE; // [HGM] book
12053     second.bookSuspend = FALSE;
12054     thinkOutput[0] = NULLCHAR;
12055     lastHint[0] = NULLCHAR;
12056     ClearGameInfo(&gameInfo);
12057     gameInfo.variant = StringToVariant(appData.variant);
12058     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12059         gameInfo.variant = VariantUnknown;
12060         strncpy(engineVariant, appData.variant, MSG_SIZ);
12061     }
12062     ics_user_moved = ics_clock_paused = FALSE;
12063     ics_getting_history = H_FALSE;
12064     ics_gamenum = -1;
12065     white_holding[0] = black_holding[0] = NULLCHAR;
12066     ClearProgramStats();
12067     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12068
12069     ResetFrontEnd();
12070     ClearHighlights();
12071     flipView = appData.flipView;
12072     ClearPremoveHighlights();
12073     gotPremove = FALSE;
12074     alarmSounded = FALSE;
12075     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12076
12077     GameEnds(EndOfFile, NULL, GE_PLAYER);
12078     if(appData.serverMovesName != NULL) {
12079         /* [HGM] prepare to make moves file for broadcasting */
12080         clock_t t = clock();
12081         if(serverMoves != NULL) fclose(serverMoves);
12082         serverMoves = fopen(appData.serverMovesName, "r");
12083         if(serverMoves != NULL) {
12084             fclose(serverMoves);
12085             /* delay 15 sec before overwriting, so all clients can see end */
12086             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12087         }
12088         serverMoves = fopen(appData.serverMovesName, "w");
12089     }
12090
12091     ExitAnalyzeMode();
12092     gameMode = BeginningOfGame;
12093     ModeHighlight();
12094     if(appData.icsActive) gameInfo.variant = VariantNormal;
12095     currentMove = forwardMostMove = backwardMostMove = 0;
12096     MarkTargetSquares(1);
12097     InitPosition(redraw);
12098     for (i = 0; i < MAX_MOVES; i++) {
12099         if (commentList[i] != NULL) {
12100             free(commentList[i]);
12101             commentList[i] = NULL;
12102         }
12103     }
12104     ResetClocks();
12105     timeRemaining[0][0] = whiteTimeRemaining;
12106     timeRemaining[1][0] = blackTimeRemaining;
12107
12108     if (first.pr == NoProc) {
12109         StartChessProgram(&first);
12110     }
12111     if (init) {
12112             InitChessProgram(&first, startedFromSetupPosition);
12113     }
12114     DisplayTitle("");
12115     DisplayMessage("", "");
12116     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12117     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12118     ClearMap();        // [HGM] exclude: invalidate map
12119 }
12120
12121 void
12122 AutoPlayGameLoop ()
12123 {
12124     for (;;) {
12125         if (!AutoPlayOneMove())
12126           return;
12127         if (matchMode || appData.timeDelay == 0)
12128           continue;
12129         if (appData.timeDelay < 0)
12130           return;
12131         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12132         break;
12133     }
12134 }
12135
12136 void
12137 AnalyzeNextGame()
12138 {
12139     ReloadGame(1); // next game
12140 }
12141
12142 int
12143 AutoPlayOneMove ()
12144 {
12145     int fromX, fromY, toX, toY;
12146
12147     if (appData.debugMode) {
12148       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12149     }
12150
12151     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12152       return FALSE;
12153
12154     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12155       pvInfoList[currentMove].depth = programStats.depth;
12156       pvInfoList[currentMove].score = programStats.score;
12157       pvInfoList[currentMove].time  = 0;
12158       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12159       else { // append analysis of final position as comment
12160         char buf[MSG_SIZ];
12161         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12162         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12163       }
12164       programStats.depth = 0;
12165     }
12166
12167     if (currentMove >= forwardMostMove) {
12168       if(gameMode == AnalyzeFile) {
12169           if(appData.loadGameIndex == -1) {
12170             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12171           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12172           } else {
12173           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12174         }
12175       }
12176 //      gameMode = EndOfGame;
12177 //      ModeHighlight();
12178
12179       /* [AS] Clear current move marker at the end of a game */
12180       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12181
12182       return FALSE;
12183     }
12184
12185     toX = moveList[currentMove][2] - AAA;
12186     toY = moveList[currentMove][3] - ONE;
12187
12188     if (moveList[currentMove][1] == '@') {
12189         if (appData.highlightLastMove) {
12190             SetHighlights(-1, -1, toX, toY);
12191         }
12192     } else {
12193         fromX = moveList[currentMove][0] - AAA;
12194         fromY = moveList[currentMove][1] - ONE;
12195
12196         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12197
12198         if(moveList[currentMove][4] == ';') { // multi-leg
12199             killX = moveList[currentMove][5] - AAA;
12200             killY = moveList[currentMove][6] - ONE;
12201         }
12202         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12203         killX = killY = -1;
12204
12205         if (appData.highlightLastMove) {
12206             SetHighlights(fromX, fromY, toX, toY);
12207         }
12208     }
12209     DisplayMove(currentMove);
12210     SendMoveToProgram(currentMove++, &first);
12211     DisplayBothClocks();
12212     DrawPosition(FALSE, boards[currentMove]);
12213     // [HGM] PV info: always display, routine tests if empty
12214     DisplayComment(currentMove - 1, commentList[currentMove]);
12215     return TRUE;
12216 }
12217
12218
12219 int
12220 LoadGameOneMove (ChessMove readAhead)
12221 {
12222     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12223     char promoChar = NULLCHAR;
12224     ChessMove moveType;
12225     char move[MSG_SIZ];
12226     char *p, *q;
12227
12228     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12229         gameMode != AnalyzeMode && gameMode != Training) {
12230         gameFileFP = NULL;
12231         return FALSE;
12232     }
12233
12234     yyboardindex = forwardMostMove;
12235     if (readAhead != EndOfFile) {
12236       moveType = readAhead;
12237     } else {
12238       if (gameFileFP == NULL)
12239           return FALSE;
12240       moveType = (ChessMove) Myylex();
12241     }
12242
12243     done = FALSE;
12244     switch (moveType) {
12245       case Comment:
12246         if (appData.debugMode)
12247           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12248         p = yy_text;
12249
12250         /* append the comment but don't display it */
12251         AppendComment(currentMove, p, FALSE);
12252         return TRUE;
12253
12254       case WhiteCapturesEnPassant:
12255       case BlackCapturesEnPassant:
12256       case WhitePromotion:
12257       case BlackPromotion:
12258       case WhiteNonPromotion:
12259       case BlackNonPromotion:
12260       case NormalMove:
12261       case FirstLeg:
12262       case WhiteKingSideCastle:
12263       case WhiteQueenSideCastle:
12264       case BlackKingSideCastle:
12265       case BlackQueenSideCastle:
12266       case WhiteKingSideCastleWild:
12267       case WhiteQueenSideCastleWild:
12268       case BlackKingSideCastleWild:
12269       case BlackQueenSideCastleWild:
12270       /* PUSH Fabien */
12271       case WhiteHSideCastleFR:
12272       case WhiteASideCastleFR:
12273       case BlackHSideCastleFR:
12274       case BlackASideCastleFR:
12275       /* POP Fabien */
12276         if (appData.debugMode)
12277           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12278         fromX = currentMoveString[0] - AAA;
12279         fromY = currentMoveString[1] - ONE;
12280         toX = currentMoveString[2] - AAA;
12281         toY = currentMoveString[3] - ONE;
12282         promoChar = currentMoveString[4];
12283         if(promoChar == ';') promoChar = currentMoveString[7];
12284         break;
12285
12286       case WhiteDrop:
12287       case BlackDrop:
12288         if (appData.debugMode)
12289           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12290         fromX = moveType == WhiteDrop ?
12291           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12292         (int) CharToPiece(ToLower(currentMoveString[0]));
12293         fromY = DROP_RANK;
12294         toX = currentMoveString[2] - AAA;
12295         toY = currentMoveString[3] - ONE;
12296         break;
12297
12298       case WhiteWins:
12299       case BlackWins:
12300       case GameIsDrawn:
12301       case GameUnfinished:
12302         if (appData.debugMode)
12303           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12304         p = strchr(yy_text, '{');
12305         if (p == NULL) p = strchr(yy_text, '(');
12306         if (p == NULL) {
12307             p = yy_text;
12308             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12309         } else {
12310             q = strchr(p, *p == '{' ? '}' : ')');
12311             if (q != NULL) *q = NULLCHAR;
12312             p++;
12313         }
12314         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12315         GameEnds(moveType, p, GE_FILE);
12316         done = TRUE;
12317         if (cmailMsgLoaded) {
12318             ClearHighlights();
12319             flipView = WhiteOnMove(currentMove);
12320             if (moveType == GameUnfinished) flipView = !flipView;
12321             if (appData.debugMode)
12322               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12323         }
12324         break;
12325
12326       case EndOfFile:
12327         if (appData.debugMode)
12328           fprintf(debugFP, "Parser hit end of file\n");
12329         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12330           case MT_NONE:
12331           case MT_CHECK:
12332             break;
12333           case MT_CHECKMATE:
12334           case MT_STAINMATE:
12335             if (WhiteOnMove(currentMove)) {
12336                 GameEnds(BlackWins, "Black mates", GE_FILE);
12337             } else {
12338                 GameEnds(WhiteWins, "White mates", GE_FILE);
12339             }
12340             break;
12341           case MT_STALEMATE:
12342             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12343             break;
12344         }
12345         done = TRUE;
12346         break;
12347
12348       case MoveNumberOne:
12349         if (lastLoadGameStart == GNUChessGame) {
12350             /* GNUChessGames have numbers, but they aren't move numbers */
12351             if (appData.debugMode)
12352               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12353                       yy_text, (int) moveType);
12354             return LoadGameOneMove(EndOfFile); /* tail recursion */
12355         }
12356         /* else fall thru */
12357
12358       case XBoardGame:
12359       case GNUChessGame:
12360       case PGNTag:
12361         /* Reached start of next game in file */
12362         if (appData.debugMode)
12363           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12364         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12365           case MT_NONE:
12366           case MT_CHECK:
12367             break;
12368           case MT_CHECKMATE:
12369           case MT_STAINMATE:
12370             if (WhiteOnMove(currentMove)) {
12371                 GameEnds(BlackWins, "Black mates", GE_FILE);
12372             } else {
12373                 GameEnds(WhiteWins, "White mates", GE_FILE);
12374             }
12375             break;
12376           case MT_STALEMATE:
12377             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12378             break;
12379         }
12380         done = TRUE;
12381         break;
12382
12383       case PositionDiagram:     /* should not happen; ignore */
12384       case ElapsedTime:         /* ignore */
12385       case NAG:                 /* ignore */
12386         if (appData.debugMode)
12387           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12388                   yy_text, (int) moveType);
12389         return LoadGameOneMove(EndOfFile); /* tail recursion */
12390
12391       case IllegalMove:
12392         if (appData.testLegality) {
12393             if (appData.debugMode)
12394               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12395             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12396                     (forwardMostMove / 2) + 1,
12397                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12398             DisplayError(move, 0);
12399             done = TRUE;
12400         } else {
12401             if (appData.debugMode)
12402               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12403                       yy_text, currentMoveString);
12404             if(currentMoveString[1] == '@') {
12405                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12406                 fromY = DROP_RANK;
12407             } else {
12408                 fromX = currentMoveString[0] - AAA;
12409                 fromY = currentMoveString[1] - ONE;
12410             }
12411             toX = currentMoveString[2] - AAA;
12412             toY = currentMoveString[3] - ONE;
12413             promoChar = currentMoveString[4];
12414         }
12415         break;
12416
12417       case AmbiguousMove:
12418         if (appData.debugMode)
12419           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12420         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12421                 (forwardMostMove / 2) + 1,
12422                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12423         DisplayError(move, 0);
12424         done = TRUE;
12425         break;
12426
12427       default:
12428       case ImpossibleMove:
12429         if (appData.debugMode)
12430           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12431         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12432                 (forwardMostMove / 2) + 1,
12433                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12434         DisplayError(move, 0);
12435         done = TRUE;
12436         break;
12437     }
12438
12439     if (done) {
12440         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12441             DrawPosition(FALSE, boards[currentMove]);
12442             DisplayBothClocks();
12443             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12444               DisplayComment(currentMove - 1, commentList[currentMove]);
12445         }
12446         (void) StopLoadGameTimer();
12447         gameFileFP = NULL;
12448         cmailOldMove = forwardMostMove;
12449         return FALSE;
12450     } else {
12451         /* currentMoveString is set as a side-effect of yylex */
12452
12453         thinkOutput[0] = NULLCHAR;
12454         MakeMove(fromX, fromY, toX, toY, promoChar);
12455         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12456         currentMove = forwardMostMove;
12457         return TRUE;
12458     }
12459 }
12460
12461 /* Load the nth game from the given file */
12462 int
12463 LoadGameFromFile (char *filename, int n, char *title, int useList)
12464 {
12465     FILE *f;
12466     char buf[MSG_SIZ];
12467
12468     if (strcmp(filename, "-") == 0) {
12469         f = stdin;
12470         title = "stdin";
12471     } else {
12472         f = fopen(filename, "rb");
12473         if (f == NULL) {
12474           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12475             DisplayError(buf, errno);
12476             return FALSE;
12477         }
12478     }
12479     if (fseek(f, 0, 0) == -1) {
12480         /* f is not seekable; probably a pipe */
12481         useList = FALSE;
12482     }
12483     if (useList && n == 0) {
12484         int error = GameListBuild(f);
12485         if (error) {
12486             DisplayError(_("Cannot build game list"), error);
12487         } else if (!ListEmpty(&gameList) &&
12488                    ((ListGame *) gameList.tailPred)->number > 1) {
12489             GameListPopUp(f, title);
12490             return TRUE;
12491         }
12492         GameListDestroy();
12493         n = 1;
12494     }
12495     if (n == 0) n = 1;
12496     return LoadGame(f, n, title, FALSE);
12497 }
12498
12499
12500 void
12501 MakeRegisteredMove ()
12502 {
12503     int fromX, fromY, toX, toY;
12504     char promoChar;
12505     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12506         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12507           case CMAIL_MOVE:
12508           case CMAIL_DRAW:
12509             if (appData.debugMode)
12510               fprintf(debugFP, "Restoring %s for game %d\n",
12511                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12512
12513             thinkOutput[0] = NULLCHAR;
12514             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12515             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12516             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12517             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12518             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12519             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12520             MakeMove(fromX, fromY, toX, toY, promoChar);
12521             ShowMove(fromX, fromY, toX, toY);
12522
12523             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12524               case MT_NONE:
12525               case MT_CHECK:
12526                 break;
12527
12528               case MT_CHECKMATE:
12529               case MT_STAINMATE:
12530                 if (WhiteOnMove(currentMove)) {
12531                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12532                 } else {
12533                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12534                 }
12535                 break;
12536
12537               case MT_STALEMATE:
12538                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12539                 break;
12540             }
12541
12542             break;
12543
12544           case CMAIL_RESIGN:
12545             if (WhiteOnMove(currentMove)) {
12546                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12547             } else {
12548                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12549             }
12550             break;
12551
12552           case CMAIL_ACCEPT:
12553             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12554             break;
12555
12556           default:
12557             break;
12558         }
12559     }
12560
12561     return;
12562 }
12563
12564 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12565 int
12566 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12567 {
12568     int retVal;
12569
12570     if (gameNumber > nCmailGames) {
12571         DisplayError(_("No more games in this message"), 0);
12572         return FALSE;
12573     }
12574     if (f == lastLoadGameFP) {
12575         int offset = gameNumber - lastLoadGameNumber;
12576         if (offset == 0) {
12577             cmailMsg[0] = NULLCHAR;
12578             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12579                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12580                 nCmailMovesRegistered--;
12581             }
12582             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12583             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12584                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12585             }
12586         } else {
12587             if (! RegisterMove()) return FALSE;
12588         }
12589     }
12590
12591     retVal = LoadGame(f, gameNumber, title, useList);
12592
12593     /* Make move registered during previous look at this game, if any */
12594     MakeRegisteredMove();
12595
12596     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12597         commentList[currentMove]
12598           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12599         DisplayComment(currentMove - 1, commentList[currentMove]);
12600     }
12601
12602     return retVal;
12603 }
12604
12605 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12606 int
12607 ReloadGame (int offset)
12608 {
12609     int gameNumber = lastLoadGameNumber + offset;
12610     if (lastLoadGameFP == NULL) {
12611         DisplayError(_("No game has been loaded yet"), 0);
12612         return FALSE;
12613     }
12614     if (gameNumber <= 0) {
12615         DisplayError(_("Can't back up any further"), 0);
12616         return FALSE;
12617     }
12618     if (cmailMsgLoaded) {
12619         return CmailLoadGame(lastLoadGameFP, gameNumber,
12620                              lastLoadGameTitle, lastLoadGameUseList);
12621     } else {
12622         return LoadGame(lastLoadGameFP, gameNumber,
12623                         lastLoadGameTitle, lastLoadGameUseList);
12624     }
12625 }
12626
12627 int keys[EmptySquare+1];
12628
12629 int
12630 PositionMatches (Board b1, Board b2)
12631 {
12632     int r, f, sum=0;
12633     switch(appData.searchMode) {
12634         case 1: return CompareWithRights(b1, b2);
12635         case 2:
12636             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12637                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12638             }
12639             return TRUE;
12640         case 3:
12641             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12642               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12643                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12644             }
12645             return sum==0;
12646         case 4:
12647             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12648                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12649             }
12650             return sum==0;
12651     }
12652     return TRUE;
12653 }
12654
12655 #define Q_PROMO  4
12656 #define Q_EP     3
12657 #define Q_BCASTL 2
12658 #define Q_WCASTL 1
12659
12660 int pieceList[256], quickBoard[256];
12661 ChessSquare pieceType[256] = { EmptySquare };
12662 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12663 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12664 int soughtTotal, turn;
12665 Boolean epOK, flipSearch;
12666
12667 typedef struct {
12668     unsigned char piece, to;
12669 } Move;
12670
12671 #define DSIZE (250000)
12672
12673 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12674 Move *moveDatabase = initialSpace;
12675 unsigned int movePtr, dataSize = DSIZE;
12676
12677 int
12678 MakePieceList (Board board, int *counts)
12679 {
12680     int r, f, n=Q_PROMO, total=0;
12681     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12682     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12683         int sq = f + (r<<4);
12684         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12685             quickBoard[sq] = ++n;
12686             pieceList[n] = sq;
12687             pieceType[n] = board[r][f];
12688             counts[board[r][f]]++;
12689             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12690             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12691             total++;
12692         }
12693     }
12694     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12695     return total;
12696 }
12697
12698 void
12699 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12700 {
12701     int sq = fromX + (fromY<<4);
12702     int piece = quickBoard[sq], rook;
12703     quickBoard[sq] = 0;
12704     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12705     if(piece == pieceList[1] && fromY == toY) {
12706       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12707         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12708         moveDatabase[movePtr++].piece = Q_WCASTL;
12709         quickBoard[sq] = piece;
12710         piece = quickBoard[from]; quickBoard[from] = 0;
12711         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12712       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12713         quickBoard[sq] = 0; // remove Rook
12714         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12715         moveDatabase[movePtr++].piece = Q_WCASTL;
12716         quickBoard[sq] = pieceList[1]; // put King
12717         piece = rook;
12718         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12719       }
12720     } else
12721     if(piece == pieceList[2] && fromY == toY) {
12722       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12723         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12724         moveDatabase[movePtr++].piece = Q_BCASTL;
12725         quickBoard[sq] = piece;
12726         piece = quickBoard[from]; quickBoard[from] = 0;
12727         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12728       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12729         quickBoard[sq] = 0; // remove Rook
12730         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12731         moveDatabase[movePtr++].piece = Q_BCASTL;
12732         quickBoard[sq] = pieceList[2]; // put King
12733         piece = rook;
12734         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12735       }
12736     } else
12737     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12738         quickBoard[(fromY<<4)+toX] = 0;
12739         moveDatabase[movePtr].piece = Q_EP;
12740         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12741         moveDatabase[movePtr].to = sq;
12742     } else
12743     if(promoPiece != pieceType[piece]) {
12744         moveDatabase[movePtr++].piece = Q_PROMO;
12745         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12746     }
12747     moveDatabase[movePtr].piece = piece;
12748     quickBoard[sq] = piece;
12749     movePtr++;
12750 }
12751
12752 int
12753 PackGame (Board board)
12754 {
12755     Move *newSpace = NULL;
12756     moveDatabase[movePtr].piece = 0; // terminate previous game
12757     if(movePtr > dataSize) {
12758         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12759         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12760         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12761         if(newSpace) {
12762             int i;
12763             Move *p = moveDatabase, *q = newSpace;
12764             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12765             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12766             moveDatabase = newSpace;
12767         } else { // calloc failed, we must be out of memory. Too bad...
12768             dataSize = 0; // prevent calloc events for all subsequent games
12769             return 0;     // and signal this one isn't cached
12770         }
12771     }
12772     movePtr++;
12773     MakePieceList(board, counts);
12774     return movePtr;
12775 }
12776
12777 int
12778 QuickCompare (Board board, int *minCounts, int *maxCounts)
12779 {   // compare according to search mode
12780     int r, f;
12781     switch(appData.searchMode)
12782     {
12783       case 1: // exact position match
12784         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12785         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12786             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12787         }
12788         break;
12789       case 2: // can have extra material on empty squares
12790         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12791             if(board[r][f] == EmptySquare) continue;
12792             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12793         }
12794         break;
12795       case 3: // material with exact Pawn structure
12796         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12797             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12798             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12799         } // fall through to material comparison
12800       case 4: // exact material
12801         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12802         break;
12803       case 6: // material range with given imbalance
12804         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12805         // fall through to range comparison
12806       case 5: // material range
12807         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12808     }
12809     return TRUE;
12810 }
12811
12812 int
12813 QuickScan (Board board, Move *move)
12814 {   // reconstruct game,and compare all positions in it
12815     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12816     do {
12817         int piece = move->piece;
12818         int to = move->to, from = pieceList[piece];
12819         if(found < 0) { // if already found just scan to game end for final piece count
12820           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12821            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12822            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12823                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12824             ) {
12825             static int lastCounts[EmptySquare+1];
12826             int i;
12827             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12828             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12829           } else stretch = 0;
12830           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12831           if(found >= 0 && !appData.minPieces) return found;
12832         }
12833         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12834           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12835           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12836             piece = (++move)->piece;
12837             from = pieceList[piece];
12838             counts[pieceType[piece]]--;
12839             pieceType[piece] = (ChessSquare) move->to;
12840             counts[move->to]++;
12841           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12842             counts[pieceType[quickBoard[to]]]--;
12843             quickBoard[to] = 0; total--;
12844             move++;
12845             continue;
12846           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12847             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12848             from  = pieceList[piece]; // so this must be King
12849             quickBoard[from] = 0;
12850             pieceList[piece] = to;
12851             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12852             quickBoard[from] = 0; // rook
12853             quickBoard[to] = piece;
12854             to = move->to; piece = move->piece;
12855             goto aftercastle;
12856           }
12857         }
12858         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12859         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12860         quickBoard[from] = 0;
12861       aftercastle:
12862         quickBoard[to] = piece;
12863         pieceList[piece] = to;
12864         cnt++; turn ^= 3;
12865         move++;
12866     } while(1);
12867 }
12868
12869 void
12870 InitSearch ()
12871 {
12872     int r, f;
12873     flipSearch = FALSE;
12874     CopyBoard(soughtBoard, boards[currentMove]);
12875     soughtTotal = MakePieceList(soughtBoard, maxSought);
12876     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12877     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12878     CopyBoard(reverseBoard, boards[currentMove]);
12879     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12880         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12881         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12882         reverseBoard[r][f] = piece;
12883     }
12884     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12885     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12886     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12887                  || (boards[currentMove][CASTLING][2] == NoRights ||
12888                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12889                  && (boards[currentMove][CASTLING][5] == NoRights ||
12890                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12891       ) {
12892         flipSearch = TRUE;
12893         CopyBoard(flipBoard, soughtBoard);
12894         CopyBoard(rotateBoard, reverseBoard);
12895         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12896             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12897             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12898         }
12899     }
12900     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12901     if(appData.searchMode >= 5) {
12902         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12903         MakePieceList(soughtBoard, minSought);
12904         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12905     }
12906     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12907         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12908 }
12909
12910 GameInfo dummyInfo;
12911 static int creatingBook;
12912
12913 int
12914 GameContainsPosition (FILE *f, ListGame *lg)
12915 {
12916     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12917     int fromX, fromY, toX, toY;
12918     char promoChar;
12919     static int initDone=FALSE;
12920
12921     // weed out games based on numerical tag comparison
12922     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12923     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12924     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12925     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12926     if(!initDone) {
12927         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12928         initDone = TRUE;
12929     }
12930     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12931     else CopyBoard(boards[scratch], initialPosition); // default start position
12932     if(lg->moves) {
12933         turn = btm + 1;
12934         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12935         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12936     }
12937     if(btm) plyNr++;
12938     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12939     fseek(f, lg->offset, 0);
12940     yynewfile(f);
12941     while(1) {
12942         yyboardindex = scratch;
12943         quickFlag = plyNr+1;
12944         next = Myylex();
12945         quickFlag = 0;
12946         switch(next) {
12947             case PGNTag:
12948                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12949             default:
12950                 continue;
12951
12952             case XBoardGame:
12953             case GNUChessGame:
12954                 if(plyNr) return -1; // after we have seen moves, this is for new game
12955               continue;
12956
12957             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12958             case ImpossibleMove:
12959             case WhiteWins: // game ends here with these four
12960             case BlackWins:
12961             case GameIsDrawn:
12962             case GameUnfinished:
12963                 return -1;
12964
12965             case IllegalMove:
12966                 if(appData.testLegality) return -1;
12967             case WhiteCapturesEnPassant:
12968             case BlackCapturesEnPassant:
12969             case WhitePromotion:
12970             case BlackPromotion:
12971             case WhiteNonPromotion:
12972             case BlackNonPromotion:
12973             case NormalMove:
12974             case FirstLeg:
12975             case WhiteKingSideCastle:
12976             case WhiteQueenSideCastle:
12977             case BlackKingSideCastle:
12978             case BlackQueenSideCastle:
12979             case WhiteKingSideCastleWild:
12980             case WhiteQueenSideCastleWild:
12981             case BlackKingSideCastleWild:
12982             case BlackQueenSideCastleWild:
12983             case WhiteHSideCastleFR:
12984             case WhiteASideCastleFR:
12985             case BlackHSideCastleFR:
12986             case BlackASideCastleFR:
12987                 fromX = currentMoveString[0] - AAA;
12988                 fromY = currentMoveString[1] - ONE;
12989                 toX = currentMoveString[2] - AAA;
12990                 toY = currentMoveString[3] - ONE;
12991                 promoChar = currentMoveString[4];
12992                 break;
12993             case WhiteDrop:
12994             case BlackDrop:
12995                 fromX = next == WhiteDrop ?
12996                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12997                   (int) CharToPiece(ToLower(currentMoveString[0]));
12998                 fromY = DROP_RANK;
12999                 toX = currentMoveString[2] - AAA;
13000                 toY = currentMoveString[3] - ONE;
13001                 promoChar = 0;
13002                 break;
13003         }
13004         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13005         plyNr++;
13006         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13007         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13008         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13009         if(appData.findMirror) {
13010             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13011             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13012         }
13013     }
13014 }
13015
13016 /* Load the nth game from open file f */
13017 int
13018 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13019 {
13020     ChessMove cm;
13021     char buf[MSG_SIZ];
13022     int gn = gameNumber;
13023     ListGame *lg = NULL;
13024     int numPGNTags = 0, i;
13025     int err, pos = -1;
13026     GameMode oldGameMode;
13027     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13028     char oldName[MSG_SIZ];
13029
13030     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13031
13032     if (appData.debugMode)
13033         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13034
13035     if (gameMode == Training )
13036         SetTrainingModeOff();
13037
13038     oldGameMode = gameMode;
13039     if (gameMode != BeginningOfGame) {
13040       Reset(FALSE, TRUE);
13041     }
13042     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13043
13044     gameFileFP = f;
13045     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13046         fclose(lastLoadGameFP);
13047     }
13048
13049     if (useList) {
13050         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13051
13052         if (lg) {
13053             fseek(f, lg->offset, 0);
13054             GameListHighlight(gameNumber);
13055             pos = lg->position;
13056             gn = 1;
13057         }
13058         else {
13059             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13060               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13061             else
13062             DisplayError(_("Game number out of range"), 0);
13063             return FALSE;
13064         }
13065     } else {
13066         GameListDestroy();
13067         if (fseek(f, 0, 0) == -1) {
13068             if (f == lastLoadGameFP ?
13069                 gameNumber == lastLoadGameNumber + 1 :
13070                 gameNumber == 1) {
13071                 gn = 1;
13072             } else {
13073                 DisplayError(_("Can't seek on game file"), 0);
13074                 return FALSE;
13075             }
13076         }
13077     }
13078     lastLoadGameFP = f;
13079     lastLoadGameNumber = gameNumber;
13080     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13081     lastLoadGameUseList = useList;
13082
13083     yynewfile(f);
13084
13085     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13086       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13087                 lg->gameInfo.black);
13088             DisplayTitle(buf);
13089     } else if (*title != NULLCHAR) {
13090         if (gameNumber > 1) {
13091           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13092             DisplayTitle(buf);
13093         } else {
13094             DisplayTitle(title);
13095         }
13096     }
13097
13098     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13099         gameMode = PlayFromGameFile;
13100         ModeHighlight();
13101     }
13102
13103     currentMove = forwardMostMove = backwardMostMove = 0;
13104     CopyBoard(boards[0], initialPosition);
13105     StopClocks();
13106
13107     /*
13108      * Skip the first gn-1 games in the file.
13109      * Also skip over anything that precedes an identifiable
13110      * start of game marker, to avoid being confused by
13111      * garbage at the start of the file.  Currently
13112      * recognized start of game markers are the move number "1",
13113      * the pattern "gnuchess .* game", the pattern
13114      * "^[#;%] [^ ]* game file", and a PGN tag block.
13115      * A game that starts with one of the latter two patterns
13116      * will also have a move number 1, possibly
13117      * following a position diagram.
13118      * 5-4-02: Let's try being more lenient and allowing a game to
13119      * start with an unnumbered move.  Does that break anything?
13120      */
13121     cm = lastLoadGameStart = EndOfFile;
13122     while (gn > 0) {
13123         yyboardindex = forwardMostMove;
13124         cm = (ChessMove) Myylex();
13125         switch (cm) {
13126           case EndOfFile:
13127             if (cmailMsgLoaded) {
13128                 nCmailGames = CMAIL_MAX_GAMES - gn;
13129             } else {
13130                 Reset(TRUE, TRUE);
13131                 DisplayError(_("Game not found in file"), 0);
13132             }
13133             return FALSE;
13134
13135           case GNUChessGame:
13136           case XBoardGame:
13137             gn--;
13138             lastLoadGameStart = cm;
13139             break;
13140
13141           case MoveNumberOne:
13142             switch (lastLoadGameStart) {
13143               case GNUChessGame:
13144               case XBoardGame:
13145               case PGNTag:
13146                 break;
13147               case MoveNumberOne:
13148               case EndOfFile:
13149                 gn--;           /* count this game */
13150                 lastLoadGameStart = cm;
13151                 break;
13152               default:
13153                 /* impossible */
13154                 break;
13155             }
13156             break;
13157
13158           case PGNTag:
13159             switch (lastLoadGameStart) {
13160               case GNUChessGame:
13161               case PGNTag:
13162               case MoveNumberOne:
13163               case EndOfFile:
13164                 gn--;           /* count this game */
13165                 lastLoadGameStart = cm;
13166                 break;
13167               case XBoardGame:
13168                 lastLoadGameStart = cm; /* game counted already */
13169                 break;
13170               default:
13171                 /* impossible */
13172                 break;
13173             }
13174             if (gn > 0) {
13175                 do {
13176                     yyboardindex = forwardMostMove;
13177                     cm = (ChessMove) Myylex();
13178                 } while (cm == PGNTag || cm == Comment);
13179             }
13180             break;
13181
13182           case WhiteWins:
13183           case BlackWins:
13184           case GameIsDrawn:
13185             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13186                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13187                     != CMAIL_OLD_RESULT) {
13188                     nCmailResults ++ ;
13189                     cmailResult[  CMAIL_MAX_GAMES
13190                                 - gn - 1] = CMAIL_OLD_RESULT;
13191                 }
13192             }
13193             break;
13194
13195           case NormalMove:
13196           case FirstLeg:
13197             /* Only a NormalMove can be at the start of a game
13198              * without a position diagram. */
13199             if (lastLoadGameStart == EndOfFile ) {
13200               gn--;
13201               lastLoadGameStart = MoveNumberOne;
13202             }
13203             break;
13204
13205           default:
13206             break;
13207         }
13208     }
13209
13210     if (appData.debugMode)
13211       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13212
13213     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13214
13215     if (cm == XBoardGame) {
13216         /* Skip any header junk before position diagram and/or move 1 */
13217         for (;;) {
13218             yyboardindex = forwardMostMove;
13219             cm = (ChessMove) Myylex();
13220
13221             if (cm == EndOfFile ||
13222                 cm == GNUChessGame || cm == XBoardGame) {
13223                 /* Empty game; pretend end-of-file and handle later */
13224                 cm = EndOfFile;
13225                 break;
13226             }
13227
13228             if (cm == MoveNumberOne || cm == PositionDiagram ||
13229                 cm == PGNTag || cm == Comment)
13230               break;
13231         }
13232     } else if (cm == GNUChessGame) {
13233         if (gameInfo.event != NULL) {
13234             free(gameInfo.event);
13235         }
13236         gameInfo.event = StrSave(yy_text);
13237     }
13238
13239     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13240     while (cm == PGNTag) {
13241         if (appData.debugMode)
13242           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13243         err = ParsePGNTag(yy_text, &gameInfo);
13244         if (!err) numPGNTags++;
13245
13246         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13247         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13248             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13249             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13250             InitPosition(TRUE);
13251             oldVariant = gameInfo.variant;
13252             if (appData.debugMode)
13253               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13254         }
13255
13256
13257         if (gameInfo.fen != NULL) {
13258           Board initial_position;
13259           startedFromSetupPosition = TRUE;
13260           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13261             Reset(TRUE, TRUE);
13262             DisplayError(_("Bad FEN position in file"), 0);
13263             return FALSE;
13264           }
13265           CopyBoard(boards[0], initial_position);
13266           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13267             CopyBoard(initialPosition, initial_position);
13268           if (blackPlaysFirst) {
13269             currentMove = forwardMostMove = backwardMostMove = 1;
13270             CopyBoard(boards[1], initial_position);
13271             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13272             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13273             timeRemaining[0][1] = whiteTimeRemaining;
13274             timeRemaining[1][1] = blackTimeRemaining;
13275             if (commentList[0] != NULL) {
13276               commentList[1] = commentList[0];
13277               commentList[0] = NULL;
13278             }
13279           } else {
13280             currentMove = forwardMostMove = backwardMostMove = 0;
13281           }
13282           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13283           {   int i;
13284               initialRulePlies = FENrulePlies;
13285               for( i=0; i< nrCastlingRights; i++ )
13286                   initialRights[i] = initial_position[CASTLING][i];
13287           }
13288           yyboardindex = forwardMostMove;
13289           free(gameInfo.fen);
13290           gameInfo.fen = NULL;
13291         }
13292
13293         yyboardindex = forwardMostMove;
13294         cm = (ChessMove) Myylex();
13295
13296         /* Handle comments interspersed among the tags */
13297         while (cm == Comment) {
13298             char *p;
13299             if (appData.debugMode)
13300               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13301             p = yy_text;
13302             AppendComment(currentMove, p, FALSE);
13303             yyboardindex = forwardMostMove;
13304             cm = (ChessMove) Myylex();
13305         }
13306     }
13307
13308     /* don't rely on existence of Event tag since if game was
13309      * pasted from clipboard the Event tag may not exist
13310      */
13311     if (numPGNTags > 0){
13312         char *tags;
13313         if (gameInfo.variant == VariantNormal) {
13314           VariantClass v = StringToVariant(gameInfo.event);
13315           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13316           if(v < VariantShogi) gameInfo.variant = v;
13317         }
13318         if (!matchMode) {
13319           if( appData.autoDisplayTags ) {
13320             tags = PGNTags(&gameInfo);
13321             TagsPopUp(tags, CmailMsg());
13322             free(tags);
13323           }
13324         }
13325     } else {
13326         /* Make something up, but don't display it now */
13327         SetGameInfo();
13328         TagsPopDown();
13329     }
13330
13331     if (cm == PositionDiagram) {
13332         int i, j;
13333         char *p;
13334         Board initial_position;
13335
13336         if (appData.debugMode)
13337           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13338
13339         if (!startedFromSetupPosition) {
13340             p = yy_text;
13341             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13342               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13343                 switch (*p) {
13344                   case '{':
13345                   case '[':
13346                   case '-':
13347                   case ' ':
13348                   case '\t':
13349                   case '\n':
13350                   case '\r':
13351                     break;
13352                   default:
13353                     initial_position[i][j++] = CharToPiece(*p);
13354                     break;
13355                 }
13356             while (*p == ' ' || *p == '\t' ||
13357                    *p == '\n' || *p == '\r') p++;
13358
13359             if (strncmp(p, "black", strlen("black"))==0)
13360               blackPlaysFirst = TRUE;
13361             else
13362               blackPlaysFirst = FALSE;
13363             startedFromSetupPosition = TRUE;
13364
13365             CopyBoard(boards[0], initial_position);
13366             if (blackPlaysFirst) {
13367                 currentMove = forwardMostMove = backwardMostMove = 1;
13368                 CopyBoard(boards[1], initial_position);
13369                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13370                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13371                 timeRemaining[0][1] = whiteTimeRemaining;
13372                 timeRemaining[1][1] = blackTimeRemaining;
13373                 if (commentList[0] != NULL) {
13374                     commentList[1] = commentList[0];
13375                     commentList[0] = NULL;
13376                 }
13377             } else {
13378                 currentMove = forwardMostMove = backwardMostMove = 0;
13379             }
13380         }
13381         yyboardindex = forwardMostMove;
13382         cm = (ChessMove) Myylex();
13383     }
13384
13385   if(!creatingBook) {
13386     if (first.pr == NoProc) {
13387         StartChessProgram(&first);
13388     }
13389     InitChessProgram(&first, FALSE);
13390     if(gameInfo.variant == VariantUnknown && *oldName) {
13391         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13392         gameInfo.variant = v;
13393     }
13394     SendToProgram("force\n", &first);
13395     if (startedFromSetupPosition) {
13396         SendBoard(&first, forwardMostMove);
13397     if (appData.debugMode) {
13398         fprintf(debugFP, "Load Game\n");
13399     }
13400         DisplayBothClocks();
13401     }
13402   }
13403
13404     /* [HGM] server: flag to write setup moves in broadcast file as one */
13405     loadFlag = appData.suppressLoadMoves;
13406
13407     while (cm == Comment) {
13408         char *p;
13409         if (appData.debugMode)
13410           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13411         p = yy_text;
13412         AppendComment(currentMove, p, FALSE);
13413         yyboardindex = forwardMostMove;
13414         cm = (ChessMove) Myylex();
13415     }
13416
13417     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13418         cm == WhiteWins || cm == BlackWins ||
13419         cm == GameIsDrawn || cm == GameUnfinished) {
13420         DisplayMessage("", _("No moves in game"));
13421         if (cmailMsgLoaded) {
13422             if (appData.debugMode)
13423               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13424             ClearHighlights();
13425             flipView = FALSE;
13426         }
13427         DrawPosition(FALSE, boards[currentMove]);
13428         DisplayBothClocks();
13429         gameMode = EditGame;
13430         ModeHighlight();
13431         gameFileFP = NULL;
13432         cmailOldMove = 0;
13433         return TRUE;
13434     }
13435
13436     // [HGM] PV info: routine tests if comment empty
13437     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13438         DisplayComment(currentMove - 1, commentList[currentMove]);
13439     }
13440     if (!matchMode && appData.timeDelay != 0)
13441       DrawPosition(FALSE, boards[currentMove]);
13442
13443     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13444       programStats.ok_to_send = 1;
13445     }
13446
13447     /* if the first token after the PGN tags is a move
13448      * and not move number 1, retrieve it from the parser
13449      */
13450     if (cm != MoveNumberOne)
13451         LoadGameOneMove(cm);
13452
13453     /* load the remaining moves from the file */
13454     while (LoadGameOneMove(EndOfFile)) {
13455       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13456       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13457     }
13458
13459     /* rewind to the start of the game */
13460     currentMove = backwardMostMove;
13461
13462     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13463
13464     if (oldGameMode == AnalyzeFile) {
13465       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13466       AnalyzeFileEvent();
13467     } else
13468     if (oldGameMode == AnalyzeMode) {
13469       AnalyzeFileEvent();
13470     }
13471
13472     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13473         long int w, b; // [HGM] adjourn: restore saved clock times
13474         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13475         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13476             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13477             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13478         }
13479     }
13480
13481     if(creatingBook) return TRUE;
13482     if (!matchMode && pos > 0) {
13483         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13484     } else
13485     if (matchMode || appData.timeDelay == 0) {
13486       ToEndEvent();
13487     } else if (appData.timeDelay > 0) {
13488       AutoPlayGameLoop();
13489     }
13490
13491     if (appData.debugMode)
13492         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13493
13494     loadFlag = 0; /* [HGM] true game starts */
13495     return TRUE;
13496 }
13497
13498 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13499 int
13500 ReloadPosition (int offset)
13501 {
13502     int positionNumber = lastLoadPositionNumber + offset;
13503     if (lastLoadPositionFP == NULL) {
13504         DisplayError(_("No position has been loaded yet"), 0);
13505         return FALSE;
13506     }
13507     if (positionNumber <= 0) {
13508         DisplayError(_("Can't back up any further"), 0);
13509         return FALSE;
13510     }
13511     return LoadPosition(lastLoadPositionFP, positionNumber,
13512                         lastLoadPositionTitle);
13513 }
13514
13515 /* Load the nth position from the given file */
13516 int
13517 LoadPositionFromFile (char *filename, int n, char *title)
13518 {
13519     FILE *f;
13520     char buf[MSG_SIZ];
13521
13522     if (strcmp(filename, "-") == 0) {
13523         return LoadPosition(stdin, n, "stdin");
13524     } else {
13525         f = fopen(filename, "rb");
13526         if (f == NULL) {
13527             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13528             DisplayError(buf, errno);
13529             return FALSE;
13530         } else {
13531             return LoadPosition(f, n, title);
13532         }
13533     }
13534 }
13535
13536 /* Load the nth position from the given open file, and close it */
13537 int
13538 LoadPosition (FILE *f, int positionNumber, char *title)
13539 {
13540     char *p, line[MSG_SIZ];
13541     Board initial_position;
13542     int i, j, fenMode, pn;
13543
13544     if (gameMode == Training )
13545         SetTrainingModeOff();
13546
13547     if (gameMode != BeginningOfGame) {
13548         Reset(FALSE, TRUE);
13549     }
13550     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13551         fclose(lastLoadPositionFP);
13552     }
13553     if (positionNumber == 0) positionNumber = 1;
13554     lastLoadPositionFP = f;
13555     lastLoadPositionNumber = positionNumber;
13556     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13557     if (first.pr == NoProc && !appData.noChessProgram) {
13558       StartChessProgram(&first);
13559       InitChessProgram(&first, FALSE);
13560     }
13561     pn = positionNumber;
13562     if (positionNumber < 0) {
13563         /* Negative position number means to seek to that byte offset */
13564         if (fseek(f, -positionNumber, 0) == -1) {
13565             DisplayError(_("Can't seek on position file"), 0);
13566             return FALSE;
13567         };
13568         pn = 1;
13569     } else {
13570         if (fseek(f, 0, 0) == -1) {
13571             if (f == lastLoadPositionFP ?
13572                 positionNumber == lastLoadPositionNumber + 1 :
13573                 positionNumber == 1) {
13574                 pn = 1;
13575             } else {
13576                 DisplayError(_("Can't seek on position file"), 0);
13577                 return FALSE;
13578             }
13579         }
13580     }
13581     /* See if this file is FEN or old-style xboard */
13582     if (fgets(line, MSG_SIZ, f) == NULL) {
13583         DisplayError(_("Position not found in file"), 0);
13584         return FALSE;
13585     }
13586     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13587     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13588
13589     if (pn >= 2) {
13590         if (fenMode || line[0] == '#') pn--;
13591         while (pn > 0) {
13592             /* skip positions before number pn */
13593             if (fgets(line, MSG_SIZ, f) == NULL) {
13594                 Reset(TRUE, TRUE);
13595                 DisplayError(_("Position not found in file"), 0);
13596                 return FALSE;
13597             }
13598             if (fenMode || line[0] == '#') pn--;
13599         }
13600     }
13601
13602     if (fenMode) {
13603         char *p;
13604         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13605             DisplayError(_("Bad FEN position in file"), 0);
13606             return FALSE;
13607         }
13608         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13609             sscanf(p+4, "%[^;]", bestMove);
13610         } else *bestMove = NULLCHAR;
13611         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13612             sscanf(p+4, "%[^;]", avoidMove);
13613         } else *avoidMove = NULLCHAR;
13614     } else {
13615         (void) fgets(line, MSG_SIZ, f);
13616         (void) fgets(line, MSG_SIZ, f);
13617
13618         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13619             (void) fgets(line, MSG_SIZ, f);
13620             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13621                 if (*p == ' ')
13622                   continue;
13623                 initial_position[i][j++] = CharToPiece(*p);
13624             }
13625         }
13626
13627         blackPlaysFirst = FALSE;
13628         if (!feof(f)) {
13629             (void) fgets(line, MSG_SIZ, f);
13630             if (strncmp(line, "black", strlen("black"))==0)
13631               blackPlaysFirst = TRUE;
13632         }
13633     }
13634     startedFromSetupPosition = TRUE;
13635
13636     CopyBoard(boards[0], initial_position);
13637     if (blackPlaysFirst) {
13638         currentMove = forwardMostMove = backwardMostMove = 1;
13639         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13640         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13641         CopyBoard(boards[1], initial_position);
13642         DisplayMessage("", _("Black to play"));
13643     } else {
13644         currentMove = forwardMostMove = backwardMostMove = 0;
13645         DisplayMessage("", _("White to play"));
13646     }
13647     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13648     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13649         SendToProgram("force\n", &first);
13650         SendBoard(&first, forwardMostMove);
13651     }
13652     if (appData.debugMode) {
13653 int i, j;
13654   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13655   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13656         fprintf(debugFP, "Load Position\n");
13657     }
13658
13659     if (positionNumber > 1) {
13660       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13661         DisplayTitle(line);
13662     } else {
13663         DisplayTitle(title);
13664     }
13665     gameMode = EditGame;
13666     ModeHighlight();
13667     ResetClocks();
13668     timeRemaining[0][1] = whiteTimeRemaining;
13669     timeRemaining[1][1] = blackTimeRemaining;
13670     DrawPosition(FALSE, boards[currentMove]);
13671
13672     return TRUE;
13673 }
13674
13675
13676 void
13677 CopyPlayerNameIntoFileName (char **dest, char *src)
13678 {
13679     while (*src != NULLCHAR && *src != ',') {
13680         if (*src == ' ') {
13681             *(*dest)++ = '_';
13682             src++;
13683         } else {
13684             *(*dest)++ = *src++;
13685         }
13686     }
13687 }
13688
13689 char *
13690 DefaultFileName (char *ext)
13691 {
13692     static char def[MSG_SIZ];
13693     char *p;
13694
13695     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13696         p = def;
13697         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13698         *p++ = '-';
13699         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13700         *p++ = '.';
13701         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13702     } else {
13703         def[0] = NULLCHAR;
13704     }
13705     return def;
13706 }
13707
13708 /* Save the current game to the given file */
13709 int
13710 SaveGameToFile (char *filename, int append)
13711 {
13712     FILE *f;
13713     char buf[MSG_SIZ];
13714     int result, i, t,tot=0;
13715
13716     if (strcmp(filename, "-") == 0) {
13717         return SaveGame(stdout, 0, NULL);
13718     } else {
13719         for(i=0; i<10; i++) { // upto 10 tries
13720              f = fopen(filename, append ? "a" : "w");
13721              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13722              if(f || errno != 13) break;
13723              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13724              tot += t;
13725         }
13726         if (f == NULL) {
13727             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13728             DisplayError(buf, errno);
13729             return FALSE;
13730         } else {
13731             safeStrCpy(buf, lastMsg, MSG_SIZ);
13732             DisplayMessage(_("Waiting for access to save file"), "");
13733             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13734             DisplayMessage(_("Saving game"), "");
13735             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13736             result = SaveGame(f, 0, NULL);
13737             DisplayMessage(buf, "");
13738             return result;
13739         }
13740     }
13741 }
13742
13743 char *
13744 SavePart (char *str)
13745 {
13746     static char buf[MSG_SIZ];
13747     char *p;
13748
13749     p = strchr(str, ' ');
13750     if (p == NULL) return str;
13751     strncpy(buf, str, p - str);
13752     buf[p - str] = NULLCHAR;
13753     return buf;
13754 }
13755
13756 #define PGN_MAX_LINE 75
13757
13758 #define PGN_SIDE_WHITE  0
13759 #define PGN_SIDE_BLACK  1
13760
13761 static int
13762 FindFirstMoveOutOfBook (int side)
13763 {
13764     int result = -1;
13765
13766     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13767         int index = backwardMostMove;
13768         int has_book_hit = 0;
13769
13770         if( (index % 2) != side ) {
13771             index++;
13772         }
13773
13774         while( index < forwardMostMove ) {
13775             /* Check to see if engine is in book */
13776             int depth = pvInfoList[index].depth;
13777             int score = pvInfoList[index].score;
13778             int in_book = 0;
13779
13780             if( depth <= 2 ) {
13781                 in_book = 1;
13782             }
13783             else if( score == 0 && depth == 63 ) {
13784                 in_book = 1; /* Zappa */
13785             }
13786             else if( score == 2 && depth == 99 ) {
13787                 in_book = 1; /* Abrok */
13788             }
13789
13790             has_book_hit += in_book;
13791
13792             if( ! in_book ) {
13793                 result = index;
13794
13795                 break;
13796             }
13797
13798             index += 2;
13799         }
13800     }
13801
13802     return result;
13803 }
13804
13805 void
13806 GetOutOfBookInfo (char * buf)
13807 {
13808     int oob[2];
13809     int i;
13810     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13811
13812     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13813     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13814
13815     *buf = '\0';
13816
13817     if( oob[0] >= 0 || oob[1] >= 0 ) {
13818         for( i=0; i<2; i++ ) {
13819             int idx = oob[i];
13820
13821             if( idx >= 0 ) {
13822                 if( i > 0 && oob[0] >= 0 ) {
13823                     strcat( buf, "   " );
13824                 }
13825
13826                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13827                 sprintf( buf+strlen(buf), "%s%.2f",
13828                     pvInfoList[idx].score >= 0 ? "+" : "",
13829                     pvInfoList[idx].score / 100.0 );
13830             }
13831         }
13832     }
13833 }
13834
13835 /* Save game in PGN style */
13836 static void
13837 SaveGamePGN2 (FILE *f)
13838 {
13839     int i, offset, linelen, newblock;
13840 //    char *movetext;
13841     char numtext[32];
13842     int movelen, numlen, blank;
13843     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13844
13845     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13846
13847     PrintPGNTags(f, &gameInfo);
13848
13849     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13850
13851     if (backwardMostMove > 0 || startedFromSetupPosition) {
13852         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13853         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13854         fprintf(f, "\n{--------------\n");
13855         PrintPosition(f, backwardMostMove);
13856         fprintf(f, "--------------}\n");
13857         free(fen);
13858     }
13859     else {
13860         /* [AS] Out of book annotation */
13861         if( appData.saveOutOfBookInfo ) {
13862             char buf[64];
13863
13864             GetOutOfBookInfo( buf );
13865
13866             if( buf[0] != '\0' ) {
13867                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13868             }
13869         }
13870
13871         fprintf(f, "\n");
13872     }
13873
13874     i = backwardMostMove;
13875     linelen = 0;
13876     newblock = TRUE;
13877
13878     while (i < forwardMostMove) {
13879         /* Print comments preceding this move */
13880         if (commentList[i] != NULL) {
13881             if (linelen > 0) fprintf(f, "\n");
13882             fprintf(f, "%s", commentList[i]);
13883             linelen = 0;
13884             newblock = TRUE;
13885         }
13886
13887         /* Format move number */
13888         if ((i % 2) == 0)
13889           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13890         else
13891           if (newblock)
13892             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13893           else
13894             numtext[0] = NULLCHAR;
13895
13896         numlen = strlen(numtext);
13897         newblock = FALSE;
13898
13899         /* Print move number */
13900         blank = linelen > 0 && numlen > 0;
13901         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13902             fprintf(f, "\n");
13903             linelen = 0;
13904             blank = 0;
13905         }
13906         if (blank) {
13907             fprintf(f, " ");
13908             linelen++;
13909         }
13910         fprintf(f, "%s", numtext);
13911         linelen += numlen;
13912
13913         /* Get move */
13914         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13915         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13916
13917         /* Print move */
13918         blank = linelen > 0 && movelen > 0;
13919         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13920             fprintf(f, "\n");
13921             linelen = 0;
13922             blank = 0;
13923         }
13924         if (blank) {
13925             fprintf(f, " ");
13926             linelen++;
13927         }
13928         fprintf(f, "%s", move_buffer);
13929         linelen += movelen;
13930
13931         /* [AS] Add PV info if present */
13932         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13933             /* [HGM] add time */
13934             char buf[MSG_SIZ]; int seconds;
13935
13936             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13937
13938             if( seconds <= 0)
13939               buf[0] = 0;
13940             else
13941               if( seconds < 30 )
13942                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13943               else
13944                 {
13945                   seconds = (seconds + 4)/10; // round to full seconds
13946                   if( seconds < 60 )
13947                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13948                   else
13949                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13950                 }
13951
13952             if(appData.cumulativeTimePGN) {
13953                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13954             }
13955
13956             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13957                       pvInfoList[i].score >= 0 ? "+" : "",
13958                       pvInfoList[i].score / 100.0,
13959                       pvInfoList[i].depth,
13960                       buf );
13961
13962             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13963
13964             /* Print score/depth */
13965             blank = linelen > 0 && movelen > 0;
13966             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13967                 fprintf(f, "\n");
13968                 linelen = 0;
13969                 blank = 0;
13970             }
13971             if (blank) {
13972                 fprintf(f, " ");
13973                 linelen++;
13974             }
13975             fprintf(f, "%s", move_buffer);
13976             linelen += movelen;
13977         }
13978
13979         i++;
13980     }
13981
13982     /* Start a new line */
13983     if (linelen > 0) fprintf(f, "\n");
13984
13985     /* Print comments after last move */
13986     if (commentList[i] != NULL) {
13987         fprintf(f, "%s\n", commentList[i]);
13988     }
13989
13990     /* Print result */
13991     if (gameInfo.resultDetails != NULL &&
13992         gameInfo.resultDetails[0] != NULLCHAR) {
13993         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13994         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13995            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13996             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13997         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13998     } else {
13999         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14000     }
14001 }
14002
14003 /* Save game in PGN style and close the file */
14004 int
14005 SaveGamePGN (FILE *f)
14006 {
14007     SaveGamePGN2(f);
14008     fclose(f);
14009     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14010     return TRUE;
14011 }
14012
14013 /* Save game in old style and close the file */
14014 int
14015 SaveGameOldStyle (FILE *f)
14016 {
14017     int i, offset;
14018     time_t tm;
14019
14020     tm = time((time_t *) NULL);
14021
14022     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14023     PrintOpponents(f);
14024
14025     if (backwardMostMove > 0 || startedFromSetupPosition) {
14026         fprintf(f, "\n[--------------\n");
14027         PrintPosition(f, backwardMostMove);
14028         fprintf(f, "--------------]\n");
14029     } else {
14030         fprintf(f, "\n");
14031     }
14032
14033     i = backwardMostMove;
14034     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14035
14036     while (i < forwardMostMove) {
14037         if (commentList[i] != NULL) {
14038             fprintf(f, "[%s]\n", commentList[i]);
14039         }
14040
14041         if ((i % 2) == 1) {
14042             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14043             i++;
14044         } else {
14045             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14046             i++;
14047             if (commentList[i] != NULL) {
14048                 fprintf(f, "\n");
14049                 continue;
14050             }
14051             if (i >= forwardMostMove) {
14052                 fprintf(f, "\n");
14053                 break;
14054             }
14055             fprintf(f, "%s\n", parseList[i]);
14056             i++;
14057         }
14058     }
14059
14060     if (commentList[i] != NULL) {
14061         fprintf(f, "[%s]\n", commentList[i]);
14062     }
14063
14064     /* This isn't really the old style, but it's close enough */
14065     if (gameInfo.resultDetails != NULL &&
14066         gameInfo.resultDetails[0] != NULLCHAR) {
14067         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14068                 gameInfo.resultDetails);
14069     } else {
14070         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14071     }
14072
14073     fclose(f);
14074     return TRUE;
14075 }
14076
14077 /* Save the current game to open file f and close the file */
14078 int
14079 SaveGame (FILE *f, int dummy, char *dummy2)
14080 {
14081     if (gameMode == EditPosition) EditPositionDone(TRUE);
14082     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14083     if (appData.oldSaveStyle)
14084       return SaveGameOldStyle(f);
14085     else
14086       return SaveGamePGN(f);
14087 }
14088
14089 /* Save the current position to the given file */
14090 int
14091 SavePositionToFile (char *filename)
14092 {
14093     FILE *f;
14094     char buf[MSG_SIZ];
14095
14096     if (strcmp(filename, "-") == 0) {
14097         return SavePosition(stdout, 0, NULL);
14098     } else {
14099         f = fopen(filename, "a");
14100         if (f == NULL) {
14101             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14102             DisplayError(buf, errno);
14103             return FALSE;
14104         } else {
14105             safeStrCpy(buf, lastMsg, MSG_SIZ);
14106             DisplayMessage(_("Waiting for access to save file"), "");
14107             flock(fileno(f), LOCK_EX); // [HGM] lock
14108             DisplayMessage(_("Saving position"), "");
14109             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14110             SavePosition(f, 0, NULL);
14111             DisplayMessage(buf, "");
14112             return TRUE;
14113         }
14114     }
14115 }
14116
14117 /* Save the current position to the given open file and close the file */
14118 int
14119 SavePosition (FILE *f, int dummy, char *dummy2)
14120 {
14121     time_t tm;
14122     char *fen;
14123
14124     if (gameMode == EditPosition) EditPositionDone(TRUE);
14125     if (appData.oldSaveStyle) {
14126         tm = time((time_t *) NULL);
14127
14128         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14129         PrintOpponents(f);
14130         fprintf(f, "[--------------\n");
14131         PrintPosition(f, currentMove);
14132         fprintf(f, "--------------]\n");
14133     } else {
14134         fen = PositionToFEN(currentMove, NULL, 1);
14135         fprintf(f, "%s\n", fen);
14136         free(fen);
14137     }
14138     fclose(f);
14139     return TRUE;
14140 }
14141
14142 void
14143 ReloadCmailMsgEvent (int unregister)
14144 {
14145 #if !WIN32
14146     static char *inFilename = NULL;
14147     static char *outFilename;
14148     int i;
14149     struct stat inbuf, outbuf;
14150     int status;
14151
14152     /* Any registered moves are unregistered if unregister is set, */
14153     /* i.e. invoked by the signal handler */
14154     if (unregister) {
14155         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14156             cmailMoveRegistered[i] = FALSE;
14157             if (cmailCommentList[i] != NULL) {
14158                 free(cmailCommentList[i]);
14159                 cmailCommentList[i] = NULL;
14160             }
14161         }
14162         nCmailMovesRegistered = 0;
14163     }
14164
14165     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14166         cmailResult[i] = CMAIL_NOT_RESULT;
14167     }
14168     nCmailResults = 0;
14169
14170     if (inFilename == NULL) {
14171         /* Because the filenames are static they only get malloced once  */
14172         /* and they never get freed                                      */
14173         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14174         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14175
14176         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14177         sprintf(outFilename, "%s.out", appData.cmailGameName);
14178     }
14179
14180     status = stat(outFilename, &outbuf);
14181     if (status < 0) {
14182         cmailMailedMove = FALSE;
14183     } else {
14184         status = stat(inFilename, &inbuf);
14185         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14186     }
14187
14188     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14189        counts the games, notes how each one terminated, etc.
14190
14191        It would be nice to remove this kludge and instead gather all
14192        the information while building the game list.  (And to keep it
14193        in the game list nodes instead of having a bunch of fixed-size
14194        parallel arrays.)  Note this will require getting each game's
14195        termination from the PGN tags, as the game list builder does
14196        not process the game moves.  --mann
14197        */
14198     cmailMsgLoaded = TRUE;
14199     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14200
14201     /* Load first game in the file or popup game menu */
14202     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14203
14204 #endif /* !WIN32 */
14205     return;
14206 }
14207
14208 int
14209 RegisterMove ()
14210 {
14211     FILE *f;
14212     char string[MSG_SIZ];
14213
14214     if (   cmailMailedMove
14215         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14216         return TRUE;            /* Allow free viewing  */
14217     }
14218
14219     /* Unregister move to ensure that we don't leave RegisterMove        */
14220     /* with the move registered when the conditions for registering no   */
14221     /* longer hold                                                       */
14222     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14223         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14224         nCmailMovesRegistered --;
14225
14226         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14227           {
14228               free(cmailCommentList[lastLoadGameNumber - 1]);
14229               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14230           }
14231     }
14232
14233     if (cmailOldMove == -1) {
14234         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14235         return FALSE;
14236     }
14237
14238     if (currentMove > cmailOldMove + 1) {
14239         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14240         return FALSE;
14241     }
14242
14243     if (currentMove < cmailOldMove) {
14244         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14245         return FALSE;
14246     }
14247
14248     if (forwardMostMove > currentMove) {
14249         /* Silently truncate extra moves */
14250         TruncateGame();
14251     }
14252
14253     if (   (currentMove == cmailOldMove + 1)
14254         || (   (currentMove == cmailOldMove)
14255             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14256                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14257         if (gameInfo.result != GameUnfinished) {
14258             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14259         }
14260
14261         if (commentList[currentMove] != NULL) {
14262             cmailCommentList[lastLoadGameNumber - 1]
14263               = StrSave(commentList[currentMove]);
14264         }
14265         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14266
14267         if (appData.debugMode)
14268           fprintf(debugFP, "Saving %s for game %d\n",
14269                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14270
14271         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14272
14273         f = fopen(string, "w");
14274         if (appData.oldSaveStyle) {
14275             SaveGameOldStyle(f); /* also closes the file */
14276
14277             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14278             f = fopen(string, "w");
14279             SavePosition(f, 0, NULL); /* also closes the file */
14280         } else {
14281             fprintf(f, "{--------------\n");
14282             PrintPosition(f, currentMove);
14283             fprintf(f, "--------------}\n\n");
14284
14285             SaveGame(f, 0, NULL); /* also closes the file*/
14286         }
14287
14288         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14289         nCmailMovesRegistered ++;
14290     } else if (nCmailGames == 1) {
14291         DisplayError(_("You have not made a move yet"), 0);
14292         return FALSE;
14293     }
14294
14295     return TRUE;
14296 }
14297
14298 void
14299 MailMoveEvent ()
14300 {
14301 #if !WIN32
14302     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14303     FILE *commandOutput;
14304     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14305     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14306     int nBuffers;
14307     int i;
14308     int archived;
14309     char *arcDir;
14310
14311     if (! cmailMsgLoaded) {
14312         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14313         return;
14314     }
14315
14316     if (nCmailGames == nCmailResults) {
14317         DisplayError(_("No unfinished games"), 0);
14318         return;
14319     }
14320
14321 #if CMAIL_PROHIBIT_REMAIL
14322     if (cmailMailedMove) {
14323       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);
14324         DisplayError(msg, 0);
14325         return;
14326     }
14327 #endif
14328
14329     if (! (cmailMailedMove || RegisterMove())) return;
14330
14331     if (   cmailMailedMove
14332         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14333       snprintf(string, MSG_SIZ, partCommandString,
14334                appData.debugMode ? " -v" : "", appData.cmailGameName);
14335         commandOutput = popen(string, "r");
14336
14337         if (commandOutput == NULL) {
14338             DisplayError(_("Failed to invoke cmail"), 0);
14339         } else {
14340             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14341                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14342             }
14343             if (nBuffers > 1) {
14344                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14345                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14346                 nBytes = MSG_SIZ - 1;
14347             } else {
14348                 (void) memcpy(msg, buffer, nBytes);
14349             }
14350             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14351
14352             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14353                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14354
14355                 archived = TRUE;
14356                 for (i = 0; i < nCmailGames; i ++) {
14357                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14358                         archived = FALSE;
14359                     }
14360                 }
14361                 if (   archived
14362                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14363                         != NULL)) {
14364                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14365                            arcDir,
14366                            appData.cmailGameName,
14367                            gameInfo.date);
14368                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14369                     cmailMsgLoaded = FALSE;
14370                 }
14371             }
14372
14373             DisplayInformation(msg);
14374             pclose(commandOutput);
14375         }
14376     } else {
14377         if ((*cmailMsg) != '\0') {
14378             DisplayInformation(cmailMsg);
14379         }
14380     }
14381
14382     return;
14383 #endif /* !WIN32 */
14384 }
14385
14386 char *
14387 CmailMsg ()
14388 {
14389 #if WIN32
14390     return NULL;
14391 #else
14392     int  prependComma = 0;
14393     char number[5];
14394     char string[MSG_SIZ];       /* Space for game-list */
14395     int  i;
14396
14397     if (!cmailMsgLoaded) return "";
14398
14399     if (cmailMailedMove) {
14400       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14401     } else {
14402         /* Create a list of games left */
14403       snprintf(string, MSG_SIZ, "[");
14404         for (i = 0; i < nCmailGames; i ++) {
14405             if (! (   cmailMoveRegistered[i]
14406                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14407                 if (prependComma) {
14408                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14409                 } else {
14410                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14411                     prependComma = 1;
14412                 }
14413
14414                 strcat(string, number);
14415             }
14416         }
14417         strcat(string, "]");
14418
14419         if (nCmailMovesRegistered + nCmailResults == 0) {
14420             switch (nCmailGames) {
14421               case 1:
14422                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14423                 break;
14424
14425               case 2:
14426                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14427                 break;
14428
14429               default:
14430                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14431                          nCmailGames);
14432                 break;
14433             }
14434         } else {
14435             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14436               case 1:
14437                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14438                          string);
14439                 break;
14440
14441               case 0:
14442                 if (nCmailResults == nCmailGames) {
14443                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14444                 } else {
14445                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14446                 }
14447                 break;
14448
14449               default:
14450                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14451                          string);
14452             }
14453         }
14454     }
14455     return cmailMsg;
14456 #endif /* WIN32 */
14457 }
14458
14459 void
14460 ResetGameEvent ()
14461 {
14462     if (gameMode == Training)
14463       SetTrainingModeOff();
14464
14465     Reset(TRUE, TRUE);
14466     cmailMsgLoaded = FALSE;
14467     if (appData.icsActive) {
14468       SendToICS(ics_prefix);
14469       SendToICS("refresh\n");
14470     }
14471 }
14472
14473 void
14474 ExitEvent (int status)
14475 {
14476     exiting++;
14477     if (exiting > 2) {
14478       /* Give up on clean exit */
14479       exit(status);
14480     }
14481     if (exiting > 1) {
14482       /* Keep trying for clean exit */
14483       return;
14484     }
14485
14486     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14487     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14488
14489     if (telnetISR != NULL) {
14490       RemoveInputSource(telnetISR);
14491     }
14492     if (icsPR != NoProc) {
14493       DestroyChildProcess(icsPR, TRUE);
14494     }
14495
14496     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14497     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14498
14499     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14500     /* make sure this other one finishes before killing it!                  */
14501     if(endingGame) { int count = 0;
14502         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14503         while(endingGame && count++ < 10) DoSleep(1);
14504         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14505     }
14506
14507     /* Kill off chess programs */
14508     if (first.pr != NoProc) {
14509         ExitAnalyzeMode();
14510
14511         DoSleep( appData.delayBeforeQuit );
14512         SendToProgram("quit\n", &first);
14513         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14514     }
14515     if (second.pr != NoProc) {
14516         DoSleep( appData.delayBeforeQuit );
14517         SendToProgram("quit\n", &second);
14518         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14519     }
14520     if (first.isr != NULL) {
14521         RemoveInputSource(first.isr);
14522     }
14523     if (second.isr != NULL) {
14524         RemoveInputSource(second.isr);
14525     }
14526
14527     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14528     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14529
14530     ShutDownFrontEnd();
14531     exit(status);
14532 }
14533
14534 void
14535 PauseEngine (ChessProgramState *cps)
14536 {
14537     SendToProgram("pause\n", cps);
14538     cps->pause = 2;
14539 }
14540
14541 void
14542 UnPauseEngine (ChessProgramState *cps)
14543 {
14544     SendToProgram("resume\n", cps);
14545     cps->pause = 1;
14546 }
14547
14548 void
14549 PauseEvent ()
14550 {
14551     if (appData.debugMode)
14552         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14553     if (pausing) {
14554         pausing = FALSE;
14555         ModeHighlight();
14556         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14557             StartClocks();
14558             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14559                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14560                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14561             }
14562             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14563             HandleMachineMove(stashedInputMove, stalledEngine);
14564             stalledEngine = NULL;
14565             return;
14566         }
14567         if (gameMode == MachinePlaysWhite ||
14568             gameMode == TwoMachinesPlay   ||
14569             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14570             if(first.pause)  UnPauseEngine(&first);
14571             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14572             if(second.pause) UnPauseEngine(&second);
14573             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14574             StartClocks();
14575         } else {
14576             DisplayBothClocks();
14577         }
14578         if (gameMode == PlayFromGameFile) {
14579             if (appData.timeDelay >= 0)
14580                 AutoPlayGameLoop();
14581         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14582             Reset(FALSE, TRUE);
14583             SendToICS(ics_prefix);
14584             SendToICS("refresh\n");
14585         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14586             ForwardInner(forwardMostMove);
14587         }
14588         pauseExamInvalid = FALSE;
14589     } else {
14590         switch (gameMode) {
14591           default:
14592             return;
14593           case IcsExamining:
14594             pauseExamForwardMostMove = forwardMostMove;
14595             pauseExamInvalid = FALSE;
14596             /* fall through */
14597           case IcsObserving:
14598           case IcsPlayingWhite:
14599           case IcsPlayingBlack:
14600             pausing = TRUE;
14601             ModeHighlight();
14602             return;
14603           case PlayFromGameFile:
14604             (void) StopLoadGameTimer();
14605             pausing = TRUE;
14606             ModeHighlight();
14607             break;
14608           case BeginningOfGame:
14609             if (appData.icsActive) return;
14610             /* else fall through */
14611           case MachinePlaysWhite:
14612           case MachinePlaysBlack:
14613           case TwoMachinesPlay:
14614             if (forwardMostMove == 0)
14615               return;           /* don't pause if no one has moved */
14616             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14617                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14618                 if(onMove->pause) {           // thinking engine can be paused
14619                     PauseEngine(onMove);      // do it
14620                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14621                         PauseEngine(onMove->other);
14622                     else
14623                         SendToProgram("easy\n", onMove->other);
14624                     StopClocks();
14625                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14626             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14627                 if(first.pause) {
14628                     PauseEngine(&first);
14629                     StopClocks();
14630                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14631             } else { // human on move, pause pondering by either method
14632                 if(first.pause)
14633                     PauseEngine(&first);
14634                 else if(appData.ponderNextMove)
14635                     SendToProgram("easy\n", &first);
14636                 StopClocks();
14637             }
14638             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14639           case AnalyzeMode:
14640             pausing = TRUE;
14641             ModeHighlight();
14642             break;
14643         }
14644     }
14645 }
14646
14647 void
14648 EditCommentEvent ()
14649 {
14650     char title[MSG_SIZ];
14651
14652     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14653       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14654     } else {
14655       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14656                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14657                parseList[currentMove - 1]);
14658     }
14659
14660     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14661 }
14662
14663
14664 void
14665 EditTagsEvent ()
14666 {
14667     char *tags = PGNTags(&gameInfo);
14668     bookUp = FALSE;
14669     EditTagsPopUp(tags, NULL);
14670     free(tags);
14671 }
14672
14673 void
14674 ToggleSecond ()
14675 {
14676   if(second.analyzing) {
14677     SendToProgram("exit\n", &second);
14678     second.analyzing = FALSE;
14679   } else {
14680     if (second.pr == NoProc) StartChessProgram(&second);
14681     InitChessProgram(&second, FALSE);
14682     FeedMovesToProgram(&second, currentMove);
14683
14684     SendToProgram("analyze\n", &second);
14685     second.analyzing = TRUE;
14686   }
14687 }
14688
14689 /* Toggle ShowThinking */
14690 void
14691 ToggleShowThinking()
14692 {
14693   appData.showThinking = !appData.showThinking;
14694   ShowThinkingEvent();
14695 }
14696
14697 int
14698 AnalyzeModeEvent ()
14699 {
14700     char buf[MSG_SIZ];
14701
14702     if (!first.analysisSupport) {
14703       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14704       DisplayError(buf, 0);
14705       return 0;
14706     }
14707     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14708     if (appData.icsActive) {
14709         if (gameMode != IcsObserving) {
14710           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14711             DisplayError(buf, 0);
14712             /* secure check */
14713             if (appData.icsEngineAnalyze) {
14714                 if (appData.debugMode)
14715                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14716                 ExitAnalyzeMode();
14717                 ModeHighlight();
14718             }
14719             return 0;
14720         }
14721         /* if enable, user wants to disable icsEngineAnalyze */
14722         if (appData.icsEngineAnalyze) {
14723                 ExitAnalyzeMode();
14724                 ModeHighlight();
14725                 return 0;
14726         }
14727         appData.icsEngineAnalyze = TRUE;
14728         if (appData.debugMode)
14729             fprintf(debugFP, "ICS engine analyze starting... \n");
14730     }
14731
14732     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14733     if (appData.noChessProgram || gameMode == AnalyzeMode)
14734       return 0;
14735
14736     if (gameMode != AnalyzeFile) {
14737         if (!appData.icsEngineAnalyze) {
14738                EditGameEvent();
14739                if (gameMode != EditGame) return 0;
14740         }
14741         if (!appData.showThinking) ToggleShowThinking();
14742         ResurrectChessProgram();
14743         SendToProgram("analyze\n", &first);
14744         first.analyzing = TRUE;
14745         /*first.maybeThinking = TRUE;*/
14746         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14747         EngineOutputPopUp();
14748     }
14749     if (!appData.icsEngineAnalyze) {
14750         gameMode = AnalyzeMode;
14751         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14752     }
14753     pausing = FALSE;
14754     ModeHighlight();
14755     SetGameInfo();
14756
14757     StartAnalysisClock();
14758     GetTimeMark(&lastNodeCountTime);
14759     lastNodeCount = 0;
14760     return 1;
14761 }
14762
14763 void
14764 AnalyzeFileEvent ()
14765 {
14766     if (appData.noChessProgram || gameMode == AnalyzeFile)
14767       return;
14768
14769     if (!first.analysisSupport) {
14770       char buf[MSG_SIZ];
14771       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14772       DisplayError(buf, 0);
14773       return;
14774     }
14775
14776     if (gameMode != AnalyzeMode) {
14777         keepInfo = 1; // mere annotating should not alter PGN tags
14778         EditGameEvent();
14779         keepInfo = 0;
14780         if (gameMode != EditGame) return;
14781         if (!appData.showThinking) ToggleShowThinking();
14782         ResurrectChessProgram();
14783         SendToProgram("analyze\n", &first);
14784         first.analyzing = TRUE;
14785         /*first.maybeThinking = TRUE;*/
14786         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14787         EngineOutputPopUp();
14788     }
14789     gameMode = AnalyzeFile;
14790     pausing = FALSE;
14791     ModeHighlight();
14792
14793     StartAnalysisClock();
14794     GetTimeMark(&lastNodeCountTime);
14795     lastNodeCount = 0;
14796     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14797     AnalysisPeriodicEvent(1);
14798 }
14799
14800 void
14801 MachineWhiteEvent ()
14802 {
14803     char buf[MSG_SIZ];
14804     char *bookHit = NULL;
14805
14806     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14807       return;
14808
14809
14810     if (gameMode == PlayFromGameFile ||
14811         gameMode == TwoMachinesPlay  ||
14812         gameMode == Training         ||
14813         gameMode == AnalyzeMode      ||
14814         gameMode == EndOfGame)
14815         EditGameEvent();
14816
14817     if (gameMode == EditPosition)
14818         EditPositionDone(TRUE);
14819
14820     if (!WhiteOnMove(currentMove)) {
14821         DisplayError(_("It is not White's turn"), 0);
14822         return;
14823     }
14824
14825     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14826       ExitAnalyzeMode();
14827
14828     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14829         gameMode == AnalyzeFile)
14830         TruncateGame();
14831
14832     ResurrectChessProgram();    /* in case it isn't running */
14833     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14834         gameMode = MachinePlaysWhite;
14835         ResetClocks();
14836     } else
14837     gameMode = MachinePlaysWhite;
14838     pausing = FALSE;
14839     ModeHighlight();
14840     SetGameInfo();
14841     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14842     DisplayTitle(buf);
14843     if (first.sendName) {
14844       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14845       SendToProgram(buf, &first);
14846     }
14847     if (first.sendTime) {
14848       if (first.useColors) {
14849         SendToProgram("black\n", &first); /*gnu kludge*/
14850       }
14851       SendTimeRemaining(&first, TRUE);
14852     }
14853     if (first.useColors) {
14854       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14855     }
14856     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14857     SetMachineThinkingEnables();
14858     first.maybeThinking = TRUE;
14859     StartClocks();
14860     firstMove = FALSE;
14861
14862     if (appData.autoFlipView && !flipView) {
14863       flipView = !flipView;
14864       DrawPosition(FALSE, NULL);
14865       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14866     }
14867
14868     if(bookHit) { // [HGM] book: simulate book reply
14869         static char bookMove[MSG_SIZ]; // a bit generous?
14870
14871         programStats.nodes = programStats.depth = programStats.time =
14872         programStats.score = programStats.got_only_move = 0;
14873         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14874
14875         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14876         strcat(bookMove, bookHit);
14877         HandleMachineMove(bookMove, &first);
14878     }
14879 }
14880
14881 void
14882 MachineBlackEvent ()
14883 {
14884   char buf[MSG_SIZ];
14885   char *bookHit = NULL;
14886
14887     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14888         return;
14889
14890
14891     if (gameMode == PlayFromGameFile ||
14892         gameMode == TwoMachinesPlay  ||
14893         gameMode == Training         ||
14894         gameMode == AnalyzeMode      ||
14895         gameMode == EndOfGame)
14896         EditGameEvent();
14897
14898     if (gameMode == EditPosition)
14899         EditPositionDone(TRUE);
14900
14901     if (WhiteOnMove(currentMove)) {
14902         DisplayError(_("It is not Black's turn"), 0);
14903         return;
14904     }
14905
14906     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14907       ExitAnalyzeMode();
14908
14909     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14910         gameMode == AnalyzeFile)
14911         TruncateGame();
14912
14913     ResurrectChessProgram();    /* in case it isn't running */
14914     gameMode = MachinePlaysBlack;
14915     pausing = FALSE;
14916     ModeHighlight();
14917     SetGameInfo();
14918     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14919     DisplayTitle(buf);
14920     if (first.sendName) {
14921       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14922       SendToProgram(buf, &first);
14923     }
14924     if (first.sendTime) {
14925       if (first.useColors) {
14926         SendToProgram("white\n", &first); /*gnu kludge*/
14927       }
14928       SendTimeRemaining(&first, FALSE);
14929     }
14930     if (first.useColors) {
14931       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14932     }
14933     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14934     SetMachineThinkingEnables();
14935     first.maybeThinking = TRUE;
14936     StartClocks();
14937
14938     if (appData.autoFlipView && flipView) {
14939       flipView = !flipView;
14940       DrawPosition(FALSE, NULL);
14941       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14942     }
14943     if(bookHit) { // [HGM] book: simulate book reply
14944         static char bookMove[MSG_SIZ]; // a bit generous?
14945
14946         programStats.nodes = programStats.depth = programStats.time =
14947         programStats.score = programStats.got_only_move = 0;
14948         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14949
14950         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14951         strcat(bookMove, bookHit);
14952         HandleMachineMove(bookMove, &first);
14953     }
14954 }
14955
14956
14957 void
14958 DisplayTwoMachinesTitle ()
14959 {
14960     char buf[MSG_SIZ];
14961     if (appData.matchGames > 0) {
14962         if(appData.tourneyFile[0]) {
14963           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14964                    gameInfo.white, _("vs."), gameInfo.black,
14965                    nextGame+1, appData.matchGames+1,
14966                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14967         } else
14968         if (first.twoMachinesColor[0] == 'w') {
14969           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14970                    gameInfo.white, _("vs."),  gameInfo.black,
14971                    first.matchWins, second.matchWins,
14972                    matchGame - 1 - (first.matchWins + second.matchWins));
14973         } else {
14974           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14975                    gameInfo.white, _("vs."), gameInfo.black,
14976                    second.matchWins, first.matchWins,
14977                    matchGame - 1 - (first.matchWins + second.matchWins));
14978         }
14979     } else {
14980       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14981     }
14982     DisplayTitle(buf);
14983 }
14984
14985 void
14986 SettingsMenuIfReady ()
14987 {
14988   if (second.lastPing != second.lastPong) {
14989     DisplayMessage("", _("Waiting for second chess program"));
14990     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14991     return;
14992   }
14993   ThawUI();
14994   DisplayMessage("", "");
14995   SettingsPopUp(&second);
14996 }
14997
14998 int
14999 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15000 {
15001     char buf[MSG_SIZ];
15002     if (cps->pr == NoProc) {
15003         StartChessProgram(cps);
15004         if (cps->protocolVersion == 1) {
15005           retry();
15006           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15007         } else {
15008           /* kludge: allow timeout for initial "feature" command */
15009           if(retry != TwoMachinesEventIfReady) FreezeUI();
15010           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15011           DisplayMessage("", buf);
15012           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15013         }
15014         return 1;
15015     }
15016     return 0;
15017 }
15018
15019 void
15020 TwoMachinesEvent P((void))
15021 {
15022     int i;
15023     char buf[MSG_SIZ];
15024     ChessProgramState *onmove;
15025     char *bookHit = NULL;
15026     static int stalling = 0;
15027     TimeMark now;
15028     long wait;
15029
15030     if (appData.noChessProgram) return;
15031
15032     switch (gameMode) {
15033       case TwoMachinesPlay:
15034         return;
15035       case MachinePlaysWhite:
15036       case MachinePlaysBlack:
15037         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15038             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15039             return;
15040         }
15041         /* fall through */
15042       case BeginningOfGame:
15043       case PlayFromGameFile:
15044       case EndOfGame:
15045         EditGameEvent();
15046         if (gameMode != EditGame) return;
15047         break;
15048       case EditPosition:
15049         EditPositionDone(TRUE);
15050         break;
15051       case AnalyzeMode:
15052       case AnalyzeFile:
15053         ExitAnalyzeMode();
15054         break;
15055       case EditGame:
15056       default:
15057         break;
15058     }
15059
15060 //    forwardMostMove = currentMove;
15061     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15062     startingEngine = TRUE;
15063
15064     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15065
15066     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15067     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15068       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15069       return;
15070     }
15071   if(!appData.epd) {
15072     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15073
15074     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15075                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15076         startingEngine = matchMode = FALSE;
15077         DisplayError("second engine does not play this", 0);
15078         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15079         EditGameEvent(); // switch back to EditGame mode
15080         return;
15081     }
15082
15083     if(!stalling) {
15084       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15085       SendToProgram("force\n", &second);
15086       stalling = 1;
15087       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15088       return;
15089     }
15090   }
15091     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15092     if(appData.matchPause>10000 || appData.matchPause<10)
15093                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15094     wait = SubtractTimeMarks(&now, &pauseStart);
15095     if(wait < appData.matchPause) {
15096         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15097         return;
15098     }
15099     // we are now committed to starting the game
15100     stalling = 0;
15101     DisplayMessage("", "");
15102   if(!appData.epd) {
15103     if (startedFromSetupPosition) {
15104         SendBoard(&second, backwardMostMove);
15105     if (appData.debugMode) {
15106         fprintf(debugFP, "Two Machines\n");
15107     }
15108     }
15109     for (i = backwardMostMove; i < forwardMostMove; i++) {
15110         SendMoveToProgram(i, &second);
15111     }
15112   }
15113
15114     gameMode = TwoMachinesPlay;
15115     pausing = startingEngine = FALSE;
15116     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15117     SetGameInfo();
15118     DisplayTwoMachinesTitle();
15119     firstMove = TRUE;
15120     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15121         onmove = &first;
15122     } else {
15123         onmove = &second;
15124     }
15125     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15126     SendToProgram(first.computerString, &first);
15127     if (first.sendName) {
15128       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15129       SendToProgram(buf, &first);
15130     }
15131   if(!appData.epd) {
15132     SendToProgram(second.computerString, &second);
15133     if (second.sendName) {
15134       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15135       SendToProgram(buf, &second);
15136     }
15137   }
15138
15139     ResetClocks();
15140     if (!first.sendTime || !second.sendTime) {
15141         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15142         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15143     }
15144     if (onmove->sendTime) {
15145       if (onmove->useColors) {
15146         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15147       }
15148       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15149     }
15150     if (onmove->useColors) {
15151       SendToProgram(onmove->twoMachinesColor, onmove);
15152     }
15153     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15154 //    SendToProgram("go\n", onmove);
15155     onmove->maybeThinking = TRUE;
15156     SetMachineThinkingEnables();
15157
15158     StartClocks();
15159
15160     if(bookHit) { // [HGM] book: simulate book reply
15161         static char bookMove[MSG_SIZ]; // a bit generous?
15162
15163         programStats.nodes = programStats.depth = programStats.time =
15164         programStats.score = programStats.got_only_move = 0;
15165         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15166
15167         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15168         strcat(bookMove, bookHit);
15169         savedMessage = bookMove; // args for deferred call
15170         savedState = onmove;
15171         ScheduleDelayedEvent(DeferredBookMove, 1);
15172     }
15173 }
15174
15175 void
15176 TrainingEvent ()
15177 {
15178     if (gameMode == Training) {
15179       SetTrainingModeOff();
15180       gameMode = PlayFromGameFile;
15181       DisplayMessage("", _("Training mode off"));
15182     } else {
15183       gameMode = Training;
15184       animateTraining = appData.animate;
15185
15186       /* make sure we are not already at the end of the game */
15187       if (currentMove < forwardMostMove) {
15188         SetTrainingModeOn();
15189         DisplayMessage("", _("Training mode on"));
15190       } else {
15191         gameMode = PlayFromGameFile;
15192         DisplayError(_("Already at end of game"), 0);
15193       }
15194     }
15195     ModeHighlight();
15196 }
15197
15198 void
15199 IcsClientEvent ()
15200 {
15201     if (!appData.icsActive) return;
15202     switch (gameMode) {
15203       case IcsPlayingWhite:
15204       case IcsPlayingBlack:
15205       case IcsObserving:
15206       case IcsIdle:
15207       case BeginningOfGame:
15208       case IcsExamining:
15209         return;
15210
15211       case EditGame:
15212         break;
15213
15214       case EditPosition:
15215         EditPositionDone(TRUE);
15216         break;
15217
15218       case AnalyzeMode:
15219       case AnalyzeFile:
15220         ExitAnalyzeMode();
15221         break;
15222
15223       default:
15224         EditGameEvent();
15225         break;
15226     }
15227
15228     gameMode = IcsIdle;
15229     ModeHighlight();
15230     return;
15231 }
15232
15233 void
15234 EditGameEvent ()
15235 {
15236     int i;
15237
15238     switch (gameMode) {
15239       case Training:
15240         SetTrainingModeOff();
15241         break;
15242       case MachinePlaysWhite:
15243       case MachinePlaysBlack:
15244       case BeginningOfGame:
15245         SendToProgram("force\n", &first);
15246         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15247             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15248                 char buf[MSG_SIZ];
15249                 abortEngineThink = TRUE;
15250                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15251                 SendToProgram(buf, &first);
15252                 DisplayMessage("Aborting engine think", "");
15253                 FreezeUI();
15254             }
15255         }
15256         SetUserThinkingEnables();
15257         break;
15258       case PlayFromGameFile:
15259         (void) StopLoadGameTimer();
15260         if (gameFileFP != NULL) {
15261             gameFileFP = NULL;
15262         }
15263         break;
15264       case EditPosition:
15265         EditPositionDone(TRUE);
15266         break;
15267       case AnalyzeMode:
15268       case AnalyzeFile:
15269         ExitAnalyzeMode();
15270         SendToProgram("force\n", &first);
15271         break;
15272       case TwoMachinesPlay:
15273         GameEnds(EndOfFile, NULL, GE_PLAYER);
15274         ResurrectChessProgram();
15275         SetUserThinkingEnables();
15276         break;
15277       case EndOfGame:
15278         ResurrectChessProgram();
15279         break;
15280       case IcsPlayingBlack:
15281       case IcsPlayingWhite:
15282         DisplayError(_("Warning: You are still playing a game"), 0);
15283         break;
15284       case IcsObserving:
15285         DisplayError(_("Warning: You are still observing a game"), 0);
15286         break;
15287       case IcsExamining:
15288         DisplayError(_("Warning: You are still examining a game"), 0);
15289         break;
15290       case IcsIdle:
15291         break;
15292       case EditGame:
15293       default:
15294         return;
15295     }
15296
15297     pausing = FALSE;
15298     StopClocks();
15299     first.offeredDraw = second.offeredDraw = 0;
15300
15301     if (gameMode == PlayFromGameFile) {
15302         whiteTimeRemaining = timeRemaining[0][currentMove];
15303         blackTimeRemaining = timeRemaining[1][currentMove];
15304         DisplayTitle("");
15305     }
15306
15307     if (gameMode == MachinePlaysWhite ||
15308         gameMode == MachinePlaysBlack ||
15309         gameMode == TwoMachinesPlay ||
15310         gameMode == EndOfGame) {
15311         i = forwardMostMove;
15312         while (i > currentMove) {
15313             SendToProgram("undo\n", &first);
15314             i--;
15315         }
15316         if(!adjustedClock) {
15317         whiteTimeRemaining = timeRemaining[0][currentMove];
15318         blackTimeRemaining = timeRemaining[1][currentMove];
15319         DisplayBothClocks();
15320         }
15321         if (whiteFlag || blackFlag) {
15322             whiteFlag = blackFlag = 0;
15323         }
15324         DisplayTitle("");
15325     }
15326
15327     gameMode = EditGame;
15328     ModeHighlight();
15329     SetGameInfo();
15330 }
15331
15332 void
15333 EditPositionEvent ()
15334 {
15335     int i;
15336     if (gameMode == EditPosition) {
15337         EditGameEvent();
15338         return;
15339     }
15340
15341     EditGameEvent();
15342     if (gameMode != EditGame) return;
15343
15344     gameMode = EditPosition;
15345     ModeHighlight();
15346     SetGameInfo();
15347     CopyBoard(rightsBoard, nullBoard);
15348     if (currentMove > 0)
15349       CopyBoard(boards[0], boards[currentMove]);
15350     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15351       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15352
15353     blackPlaysFirst = !WhiteOnMove(currentMove);
15354     ResetClocks();
15355     currentMove = forwardMostMove = backwardMostMove = 0;
15356     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15357     DisplayMove(-1);
15358     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15359 }
15360
15361 void
15362 ExitAnalyzeMode ()
15363 {
15364     /* [DM] icsEngineAnalyze - possible call from other functions */
15365     if (appData.icsEngineAnalyze) {
15366         appData.icsEngineAnalyze = FALSE;
15367
15368         DisplayMessage("",_("Close ICS engine analyze..."));
15369     }
15370     if (first.analysisSupport && first.analyzing) {
15371       SendToBoth("exit\n");
15372       first.analyzing = second.analyzing = FALSE;
15373     }
15374     thinkOutput[0] = NULLCHAR;
15375 }
15376
15377 void
15378 EditPositionDone (Boolean fakeRights)
15379 {
15380     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15381
15382     startedFromSetupPosition = TRUE;
15383     InitChessProgram(&first, FALSE);
15384     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15385       int r, f;
15386       boards[0][EP_STATUS] = EP_NONE;
15387       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15388       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15389         if(rightsBoard[r][f]) {
15390           ChessSquare p = boards[0][r][f];
15391           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15392           else if(p == king) boards[0][CASTLING][2] = f;
15393           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15394           else rightsBoard[r][f] = 2; // mark for second pass
15395         }
15396       }
15397       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15398         if(rightsBoard[r][f] == 2) {
15399           ChessSquare p = boards[0][r][f];
15400           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15401           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15402         }
15403       }
15404     }
15405     SendToProgram("force\n", &first);
15406     if (blackPlaysFirst) {
15407         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15408         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15409         currentMove = forwardMostMove = backwardMostMove = 1;
15410         CopyBoard(boards[1], boards[0]);
15411     } else {
15412         currentMove = forwardMostMove = backwardMostMove = 0;
15413     }
15414     SendBoard(&first, forwardMostMove);
15415     if (appData.debugMode) {
15416         fprintf(debugFP, "EditPosDone\n");
15417     }
15418     DisplayTitle("");
15419     DisplayMessage("", "");
15420     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15421     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15422     gameMode = EditGame;
15423     ModeHighlight();
15424     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15425     ClearHighlights(); /* [AS] */
15426 }
15427
15428 /* Pause for `ms' milliseconds */
15429 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15430 void
15431 TimeDelay (long ms)
15432 {
15433     TimeMark m1, m2;
15434
15435     GetTimeMark(&m1);
15436     do {
15437         GetTimeMark(&m2);
15438     } while (SubtractTimeMarks(&m2, &m1) < ms);
15439 }
15440
15441 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15442 void
15443 SendMultiLineToICS (char *buf)
15444 {
15445     char temp[MSG_SIZ+1], *p;
15446     int len;
15447
15448     len = strlen(buf);
15449     if (len > MSG_SIZ)
15450       len = MSG_SIZ;
15451
15452     strncpy(temp, buf, len);
15453     temp[len] = 0;
15454
15455     p = temp;
15456     while (*p) {
15457         if (*p == '\n' || *p == '\r')
15458           *p = ' ';
15459         ++p;
15460     }
15461
15462     strcat(temp, "\n");
15463     SendToICS(temp);
15464     SendToPlayer(temp, strlen(temp));
15465 }
15466
15467 void
15468 SetWhiteToPlayEvent ()
15469 {
15470     if (gameMode == EditPosition) {
15471         blackPlaysFirst = FALSE;
15472         DisplayBothClocks();    /* works because currentMove is 0 */
15473     } else if (gameMode == IcsExamining) {
15474         SendToICS(ics_prefix);
15475         SendToICS("tomove white\n");
15476     }
15477 }
15478
15479 void
15480 SetBlackToPlayEvent ()
15481 {
15482     if (gameMode == EditPosition) {
15483         blackPlaysFirst = TRUE;
15484         currentMove = 1;        /* kludge */
15485         DisplayBothClocks();
15486         currentMove = 0;
15487     } else if (gameMode == IcsExamining) {
15488         SendToICS(ics_prefix);
15489         SendToICS("tomove black\n");
15490     }
15491 }
15492
15493 void
15494 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15495 {
15496     char buf[MSG_SIZ];
15497     ChessSquare piece = boards[0][y][x];
15498     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15499     static int lastVariant;
15500     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15501
15502     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15503
15504     switch (selection) {
15505       case ClearBoard:
15506         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15507         MarkTargetSquares(1);
15508         CopyBoard(currentBoard, boards[0]);
15509         CopyBoard(menuBoard, initialPosition);
15510         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15511             SendToICS(ics_prefix);
15512             SendToICS("bsetup clear\n");
15513         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15514             SendToICS(ics_prefix);
15515             SendToICS("clearboard\n");
15516         } else {
15517             int nonEmpty = 0;
15518             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15519                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15520                 for (y = 0; y < BOARD_HEIGHT; y++) {
15521                     if (gameMode == IcsExamining) {
15522                         if (boards[currentMove][y][x] != EmptySquare) {
15523                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15524                                     AAA + x, ONE + y);
15525                             SendToICS(buf);
15526                         }
15527                     } else if(boards[0][y][x] != DarkSquare) {
15528                         if(boards[0][y][x] != p) nonEmpty++;
15529                         boards[0][y][x] = p;
15530                     }
15531                 }
15532             }
15533             CopyBoard(rightsBoard, nullBoard);
15534             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15535                 int r, i;
15536                 for(r = 0; r < BOARD_HEIGHT; r++) {
15537                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15538                     ChessSquare p = menuBoard[r][x];
15539                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15540                   }
15541                 }
15542                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15543                 DisplayMessage("Clicking clock again restores position", "");
15544                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15545                 if(!nonEmpty) { // asked to clear an empty board
15546                     CopyBoard(boards[0], menuBoard);
15547                 } else
15548                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15549                     CopyBoard(boards[0], initialPosition);
15550                 } else
15551                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15552                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15553                     CopyBoard(boards[0], erasedBoard);
15554                 } else
15555                     CopyBoard(erasedBoard, currentBoard);
15556
15557                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15558                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15559             }
15560         }
15561         if (gameMode == EditPosition) {
15562             DrawPosition(FALSE, boards[0]);
15563         }
15564         break;
15565
15566       case WhitePlay:
15567         SetWhiteToPlayEvent();
15568         break;
15569
15570       case BlackPlay:
15571         SetBlackToPlayEvent();
15572         break;
15573
15574       case EmptySquare:
15575         if (gameMode == IcsExamining) {
15576             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15577             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15578             SendToICS(buf);
15579         } else {
15580             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15581                 if(x == BOARD_LEFT-2) {
15582                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15583                     boards[0][y][1] = 0;
15584                 } else
15585                 if(x == BOARD_RGHT+1) {
15586                     if(y >= gameInfo.holdingsSize) break;
15587                     boards[0][y][BOARD_WIDTH-2] = 0;
15588                 } else break;
15589             }
15590             boards[0][y][x] = EmptySquare;
15591             DrawPosition(FALSE, boards[0]);
15592         }
15593         break;
15594
15595       case PromotePiece:
15596         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15597            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15598             selection = (ChessSquare) (PROMOTED(piece));
15599         } else if(piece == EmptySquare) selection = WhiteSilver;
15600         else selection = (ChessSquare)((int)piece - 1);
15601         goto defaultlabel;
15602
15603       case DemotePiece:
15604         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15605            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15606             selection = (ChessSquare) (DEMOTED(piece));
15607         } else if(piece == EmptySquare) selection = BlackSilver;
15608         else selection = (ChessSquare)((int)piece + 1);
15609         goto defaultlabel;
15610
15611       case WhiteQueen:
15612       case BlackQueen:
15613         if(gameInfo.variant == VariantShatranj ||
15614            gameInfo.variant == VariantXiangqi  ||
15615            gameInfo.variant == VariantCourier  ||
15616            gameInfo.variant == VariantASEAN    ||
15617            gameInfo.variant == VariantMakruk     )
15618             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15619         goto defaultlabel;
15620
15621       case WhiteRook:
15622         baseRank = 0;
15623       case BlackRook:
15624         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15625         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15626         goto defaultlabel;
15627
15628       case WhiteKing:
15629         baseRank = 0;
15630       case BlackKing:
15631         if(gameInfo.variant == VariantXiangqi)
15632             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15633         if(gameInfo.variant == VariantKnightmate)
15634             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15635         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15636       default:
15637         defaultlabel:
15638         if (gameMode == IcsExamining) {
15639             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15640             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15641                      PieceToChar(selection), AAA + x, ONE + y);
15642             SendToICS(buf);
15643         } else {
15644             rightsBoard[y][x] = hasRights;
15645             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15646                 int n;
15647                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15648                     n = PieceToNumber(selection - BlackPawn);
15649                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15650                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15651                     boards[0][BOARD_HEIGHT-1-n][1]++;
15652                 } else
15653                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15654                     n = PieceToNumber(selection);
15655                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15656                     boards[0][n][BOARD_WIDTH-1] = selection;
15657                     boards[0][n][BOARD_WIDTH-2]++;
15658                 }
15659             } else
15660             boards[0][y][x] = selection;
15661             DrawPosition(TRUE, boards[0]);
15662             ClearHighlights();
15663             fromX = fromY = -1;
15664         }
15665         break;
15666     }
15667 }
15668
15669
15670 void
15671 DropMenuEvent (ChessSquare selection, int x, int y)
15672 {
15673     ChessMove moveType;
15674
15675     switch (gameMode) {
15676       case IcsPlayingWhite:
15677       case MachinePlaysBlack:
15678         if (!WhiteOnMove(currentMove)) {
15679             DisplayMoveError(_("It is Black's turn"));
15680             return;
15681         }
15682         moveType = WhiteDrop;
15683         break;
15684       case IcsPlayingBlack:
15685       case MachinePlaysWhite:
15686         if (WhiteOnMove(currentMove)) {
15687             DisplayMoveError(_("It is White's turn"));
15688             return;
15689         }
15690         moveType = BlackDrop;
15691         break;
15692       case EditGame:
15693         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15694         break;
15695       default:
15696         return;
15697     }
15698
15699     if (moveType == BlackDrop && selection < BlackPawn) {
15700       selection = (ChessSquare) ((int) selection
15701                                  + (int) BlackPawn - (int) WhitePawn);
15702     }
15703     if (boards[currentMove][y][x] != EmptySquare) {
15704         DisplayMoveError(_("That square is occupied"));
15705         return;
15706     }
15707
15708     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15709 }
15710
15711 void
15712 AcceptEvent ()
15713 {
15714     /* Accept a pending offer of any kind from opponent */
15715
15716     if (appData.icsActive) {
15717         SendToICS(ics_prefix);
15718         SendToICS("accept\n");
15719     } else if (cmailMsgLoaded) {
15720         if (currentMove == cmailOldMove &&
15721             commentList[cmailOldMove] != NULL &&
15722             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15723                    "Black offers a draw" : "White offers a draw")) {
15724             TruncateGame();
15725             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15727         } else {
15728             DisplayError(_("There is no pending offer on this move"), 0);
15729             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15730         }
15731     } else {
15732         /* Not used for offers from chess program */
15733     }
15734 }
15735
15736 void
15737 DeclineEvent ()
15738 {
15739     /* Decline a pending offer of any kind from opponent */
15740
15741     if (appData.icsActive) {
15742         SendToICS(ics_prefix);
15743         SendToICS("decline\n");
15744     } else if (cmailMsgLoaded) {
15745         if (currentMove == cmailOldMove &&
15746             commentList[cmailOldMove] != NULL &&
15747             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15748                    "Black offers a draw" : "White offers a draw")) {
15749 #ifdef NOTDEF
15750             AppendComment(cmailOldMove, "Draw declined", TRUE);
15751             DisplayComment(cmailOldMove - 1, "Draw declined");
15752 #endif /*NOTDEF*/
15753         } else {
15754             DisplayError(_("There is no pending offer on this move"), 0);
15755         }
15756     } else {
15757         /* Not used for offers from chess program */
15758     }
15759 }
15760
15761 void
15762 RematchEvent ()
15763 {
15764     /* Issue ICS rematch command */
15765     if (appData.icsActive) {
15766         SendToICS(ics_prefix);
15767         SendToICS("rematch\n");
15768     }
15769 }
15770
15771 void
15772 CallFlagEvent ()
15773 {
15774     /* Call your opponent's flag (claim a win on time) */
15775     if (appData.icsActive) {
15776         SendToICS(ics_prefix);
15777         SendToICS("flag\n");
15778     } else {
15779         switch (gameMode) {
15780           default:
15781             return;
15782           case MachinePlaysWhite:
15783             if (whiteFlag) {
15784                 if (blackFlag)
15785                   GameEnds(GameIsDrawn, "Both players ran out of time",
15786                            GE_PLAYER);
15787                 else
15788                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15789             } else {
15790                 DisplayError(_("Your opponent is not out of time"), 0);
15791             }
15792             break;
15793           case MachinePlaysBlack:
15794             if (blackFlag) {
15795                 if (whiteFlag)
15796                   GameEnds(GameIsDrawn, "Both players ran out of time",
15797                            GE_PLAYER);
15798                 else
15799                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15800             } else {
15801                 DisplayError(_("Your opponent is not out of time"), 0);
15802             }
15803             break;
15804         }
15805     }
15806 }
15807
15808 void
15809 ClockClick (int which)
15810 {       // [HGM] code moved to back-end from winboard.c
15811         if(which) { // black clock
15812           if (gameMode == EditPosition || gameMode == IcsExamining) {
15813             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15814             SetBlackToPlayEvent();
15815           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15816                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15817           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15818           } else if (shiftKey) {
15819             AdjustClock(which, -1);
15820           } else if (gameMode == IcsPlayingWhite ||
15821                      gameMode == MachinePlaysBlack) {
15822             CallFlagEvent();
15823           }
15824         } else { // white clock
15825           if (gameMode == EditPosition || gameMode == IcsExamining) {
15826             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15827             SetWhiteToPlayEvent();
15828           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15829                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15830           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15831           } else if (shiftKey) {
15832             AdjustClock(which, -1);
15833           } else if (gameMode == IcsPlayingBlack ||
15834                    gameMode == MachinePlaysWhite) {
15835             CallFlagEvent();
15836           }
15837         }
15838 }
15839
15840 void
15841 DrawEvent ()
15842 {
15843     /* Offer draw or accept pending draw offer from opponent */
15844
15845     if (appData.icsActive) {
15846         /* Note: tournament rules require draw offers to be
15847            made after you make your move but before you punch
15848            your clock.  Currently ICS doesn't let you do that;
15849            instead, you immediately punch your clock after making
15850            a move, but you can offer a draw at any time. */
15851
15852         SendToICS(ics_prefix);
15853         SendToICS("draw\n");
15854         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15855     } else if (cmailMsgLoaded) {
15856         if (currentMove == cmailOldMove &&
15857             commentList[cmailOldMove] != NULL &&
15858             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15859                    "Black offers a draw" : "White offers a draw")) {
15860             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15861             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15862         } else if (currentMove == cmailOldMove + 1) {
15863             char *offer = WhiteOnMove(cmailOldMove) ?
15864               "White offers a draw" : "Black offers a draw";
15865             AppendComment(currentMove, offer, TRUE);
15866             DisplayComment(currentMove - 1, offer);
15867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15868         } else {
15869             DisplayError(_("You must make your move before offering a draw"), 0);
15870             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15871         }
15872     } else if (first.offeredDraw) {
15873         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15874     } else {
15875         if (first.sendDrawOffers) {
15876             SendToProgram("draw\n", &first);
15877             userOfferedDraw = TRUE;
15878         }
15879     }
15880 }
15881
15882 void
15883 AdjournEvent ()
15884 {
15885     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15886
15887     if (appData.icsActive) {
15888         SendToICS(ics_prefix);
15889         SendToICS("adjourn\n");
15890     } else {
15891         /* Currently GNU Chess doesn't offer or accept Adjourns */
15892     }
15893 }
15894
15895
15896 void
15897 AbortEvent ()
15898 {
15899     /* Offer Abort or accept pending Abort offer from opponent */
15900
15901     if (appData.icsActive) {
15902         SendToICS(ics_prefix);
15903         SendToICS("abort\n");
15904     } else {
15905         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15906     }
15907 }
15908
15909 void
15910 ResignEvent ()
15911 {
15912     /* Resign.  You can do this even if it's not your turn. */
15913
15914     if (appData.icsActive) {
15915         SendToICS(ics_prefix);
15916         SendToICS("resign\n");
15917     } else {
15918         switch (gameMode) {
15919           case MachinePlaysWhite:
15920             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15921             break;
15922           case MachinePlaysBlack:
15923             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15924             break;
15925           case EditGame:
15926             if (cmailMsgLoaded) {
15927                 TruncateGame();
15928                 if (WhiteOnMove(cmailOldMove)) {
15929                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15930                 } else {
15931                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15932                 }
15933                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15934             }
15935             break;
15936           default:
15937             break;
15938         }
15939     }
15940 }
15941
15942
15943 void
15944 StopObservingEvent ()
15945 {
15946     /* Stop observing current games */
15947     SendToICS(ics_prefix);
15948     SendToICS("unobserve\n");
15949 }
15950
15951 void
15952 StopExaminingEvent ()
15953 {
15954     /* Stop observing current game */
15955     SendToICS(ics_prefix);
15956     SendToICS("unexamine\n");
15957 }
15958
15959 void
15960 ForwardInner (int target)
15961 {
15962     int limit; int oldSeekGraphUp = seekGraphUp;
15963
15964     if (appData.debugMode)
15965         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15966                 target, currentMove, forwardMostMove);
15967
15968     if (gameMode == EditPosition)
15969       return;
15970
15971     seekGraphUp = FALSE;
15972     MarkTargetSquares(1);
15973     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15974
15975     if (gameMode == PlayFromGameFile && !pausing)
15976       PauseEvent();
15977
15978     if (gameMode == IcsExamining && pausing)
15979       limit = pauseExamForwardMostMove;
15980     else
15981       limit = forwardMostMove;
15982
15983     if (target > limit) target = limit;
15984
15985     if (target > 0 && moveList[target - 1][0]) {
15986         int fromX, fromY, toX, toY;
15987         toX = moveList[target - 1][2] - AAA;
15988         toY = moveList[target - 1][3] - ONE;
15989         if (moveList[target - 1][1] == '@') {
15990             if (appData.highlightLastMove) {
15991                 SetHighlights(-1, -1, toX, toY);
15992             }
15993         } else {
15994             fromX = moveList[target - 1][0] - AAA;
15995             fromY = moveList[target - 1][1] - ONE;
15996             if (target == currentMove + 1) {
15997                 if(moveList[target - 1][4] == ';') { // multi-leg
15998                     killX = moveList[target - 1][5] - AAA;
15999                     killY = moveList[target - 1][6] - ONE;
16000                 }
16001                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16002                 killX = killY = -1;
16003             }
16004             if (appData.highlightLastMove) {
16005                 SetHighlights(fromX, fromY, toX, toY);
16006             }
16007         }
16008     }
16009     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16010         gameMode == Training || gameMode == PlayFromGameFile ||
16011         gameMode == AnalyzeFile) {
16012         while (currentMove < target) {
16013             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16014             SendMoveToProgram(currentMove++, &first);
16015         }
16016     } else {
16017         currentMove = target;
16018     }
16019
16020     if (gameMode == EditGame || gameMode == EndOfGame) {
16021         whiteTimeRemaining = timeRemaining[0][currentMove];
16022         blackTimeRemaining = timeRemaining[1][currentMove];
16023     }
16024     DisplayBothClocks();
16025     DisplayMove(currentMove - 1);
16026     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16027     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16028     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16029         DisplayComment(currentMove - 1, commentList[currentMove]);
16030     }
16031     ClearMap(); // [HGM] exclude: invalidate map
16032 }
16033
16034
16035 void
16036 ForwardEvent ()
16037 {
16038     if (gameMode == IcsExamining && !pausing) {
16039         SendToICS(ics_prefix);
16040         SendToICS("forward\n");
16041     } else {
16042         ForwardInner(currentMove + 1);
16043     }
16044 }
16045
16046 void
16047 ToEndEvent ()
16048 {
16049     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16050         /* to optimze, we temporarily turn off analysis mode while we feed
16051          * the remaining moves to the engine. Otherwise we get analysis output
16052          * after each move.
16053          */
16054         if (first.analysisSupport) {
16055           SendToProgram("exit\nforce\n", &first);
16056           first.analyzing = FALSE;
16057         }
16058     }
16059
16060     if (gameMode == IcsExamining && !pausing) {
16061         SendToICS(ics_prefix);
16062         SendToICS("forward 999999\n");
16063     } else {
16064         ForwardInner(forwardMostMove);
16065     }
16066
16067     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16068         /* we have fed all the moves, so reactivate analysis mode */
16069         SendToProgram("analyze\n", &first);
16070         first.analyzing = TRUE;
16071         /*first.maybeThinking = TRUE;*/
16072         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16073     }
16074 }
16075
16076 void
16077 BackwardInner (int target)
16078 {
16079     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16080
16081     if (appData.debugMode)
16082         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16083                 target, currentMove, forwardMostMove);
16084
16085     if (gameMode == EditPosition) return;
16086     seekGraphUp = FALSE;
16087     MarkTargetSquares(1);
16088     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16089     if (currentMove <= backwardMostMove) {
16090         ClearHighlights();
16091         DrawPosition(full_redraw, boards[currentMove]);
16092         return;
16093     }
16094     if (gameMode == PlayFromGameFile && !pausing)
16095       PauseEvent();
16096
16097     if (moveList[target][0]) {
16098         int fromX, fromY, toX, toY;
16099         toX = moveList[target][2] - AAA;
16100         toY = moveList[target][3] - ONE;
16101         if (moveList[target][1] == '@') {
16102             if (appData.highlightLastMove) {
16103                 SetHighlights(-1, -1, toX, toY);
16104             }
16105         } else {
16106             fromX = moveList[target][0] - AAA;
16107             fromY = moveList[target][1] - ONE;
16108             if (target == currentMove - 1) {
16109                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16110             }
16111             if (appData.highlightLastMove) {
16112                 SetHighlights(fromX, fromY, toX, toY);
16113             }
16114         }
16115     }
16116     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16117         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16118         while (currentMove > target) {
16119             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16120                 // null move cannot be undone. Reload program with move history before it.
16121                 int i;
16122                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16123                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16124                 }
16125                 SendBoard(&first, i);
16126               if(second.analyzing) SendBoard(&second, i);
16127                 for(currentMove=i; currentMove<target; currentMove++) {
16128                     SendMoveToProgram(currentMove, &first);
16129                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16130                 }
16131                 break;
16132             }
16133             SendToBoth("undo\n");
16134             currentMove--;
16135         }
16136     } else {
16137         currentMove = target;
16138     }
16139
16140     if (gameMode == EditGame || gameMode == EndOfGame) {
16141         whiteTimeRemaining = timeRemaining[0][currentMove];
16142         blackTimeRemaining = timeRemaining[1][currentMove];
16143     }
16144     DisplayBothClocks();
16145     DisplayMove(currentMove - 1);
16146     DrawPosition(full_redraw, boards[currentMove]);
16147     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16148     // [HGM] PV info: routine tests if comment empty
16149     DisplayComment(currentMove - 1, commentList[currentMove]);
16150     ClearMap(); // [HGM] exclude: invalidate map
16151 }
16152
16153 void
16154 BackwardEvent ()
16155 {
16156     if (gameMode == IcsExamining && !pausing) {
16157         SendToICS(ics_prefix);
16158         SendToICS("backward\n");
16159     } else {
16160         BackwardInner(currentMove - 1);
16161     }
16162 }
16163
16164 void
16165 ToStartEvent ()
16166 {
16167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16168         /* to optimize, we temporarily turn off analysis mode while we undo
16169          * all the moves. Otherwise we get analysis output after each undo.
16170          */
16171         if (first.analysisSupport) {
16172           SendToProgram("exit\nforce\n", &first);
16173           first.analyzing = FALSE;
16174         }
16175     }
16176
16177     if (gameMode == IcsExamining && !pausing) {
16178         SendToICS(ics_prefix);
16179         SendToICS("backward 999999\n");
16180     } else {
16181         BackwardInner(backwardMostMove);
16182     }
16183
16184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16185         /* we have fed all the moves, so reactivate analysis mode */
16186         SendToProgram("analyze\n", &first);
16187         first.analyzing = TRUE;
16188         /*first.maybeThinking = TRUE;*/
16189         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16190     }
16191 }
16192
16193 void
16194 ToNrEvent (int to)
16195 {
16196   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16197   if (to >= forwardMostMove) to = forwardMostMove;
16198   if (to <= backwardMostMove) to = backwardMostMove;
16199   if (to < currentMove) {
16200     BackwardInner(to);
16201   } else {
16202     ForwardInner(to);
16203   }
16204 }
16205
16206 void
16207 RevertEvent (Boolean annotate)
16208 {
16209     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16210         return;
16211     }
16212     if (gameMode != IcsExamining) {
16213         DisplayError(_("You are not examining a game"), 0);
16214         return;
16215     }
16216     if (pausing) {
16217         DisplayError(_("You can't revert while pausing"), 0);
16218         return;
16219     }
16220     SendToICS(ics_prefix);
16221     SendToICS("revert\n");
16222 }
16223
16224 void
16225 RetractMoveEvent ()
16226 {
16227     switch (gameMode) {
16228       case MachinePlaysWhite:
16229       case MachinePlaysBlack:
16230         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16231             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16232             return;
16233         }
16234         if (forwardMostMove < 2) return;
16235         currentMove = forwardMostMove = forwardMostMove - 2;
16236         whiteTimeRemaining = timeRemaining[0][currentMove];
16237         blackTimeRemaining = timeRemaining[1][currentMove];
16238         DisplayBothClocks();
16239         DisplayMove(currentMove - 1);
16240         ClearHighlights();/*!! could figure this out*/
16241         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16242         SendToProgram("remove\n", &first);
16243         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16244         break;
16245
16246       case BeginningOfGame:
16247       default:
16248         break;
16249
16250       case IcsPlayingWhite:
16251       case IcsPlayingBlack:
16252         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16253             SendToICS(ics_prefix);
16254             SendToICS("takeback 2\n");
16255         } else {
16256             SendToICS(ics_prefix);
16257             SendToICS("takeback 1\n");
16258         }
16259         break;
16260     }
16261 }
16262
16263 void
16264 MoveNowEvent ()
16265 {
16266     ChessProgramState *cps;
16267
16268     switch (gameMode) {
16269       case MachinePlaysWhite:
16270         if (!WhiteOnMove(forwardMostMove)) {
16271             DisplayError(_("It is your turn"), 0);
16272             return;
16273         }
16274         cps = &first;
16275         break;
16276       case MachinePlaysBlack:
16277         if (WhiteOnMove(forwardMostMove)) {
16278             DisplayError(_("It is your turn"), 0);
16279             return;
16280         }
16281         cps = &first;
16282         break;
16283       case TwoMachinesPlay:
16284         if (WhiteOnMove(forwardMostMove) ==
16285             (first.twoMachinesColor[0] == 'w')) {
16286             cps = &first;
16287         } else {
16288             cps = &second;
16289         }
16290         break;
16291       case BeginningOfGame:
16292       default:
16293         return;
16294     }
16295     SendToProgram("?\n", cps);
16296 }
16297
16298 void
16299 TruncateGameEvent ()
16300 {
16301     EditGameEvent();
16302     if (gameMode != EditGame) return;
16303     TruncateGame();
16304 }
16305
16306 void
16307 TruncateGame ()
16308 {
16309     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16310     if (forwardMostMove > currentMove) {
16311         if (gameInfo.resultDetails != NULL) {
16312             free(gameInfo.resultDetails);
16313             gameInfo.resultDetails = NULL;
16314             gameInfo.result = GameUnfinished;
16315         }
16316         forwardMostMove = currentMove;
16317         HistorySet(parseList, backwardMostMove, forwardMostMove,
16318                    currentMove-1);
16319     }
16320 }
16321
16322 void
16323 HintEvent ()
16324 {
16325     if (appData.noChessProgram) return;
16326     switch (gameMode) {
16327       case MachinePlaysWhite:
16328         if (WhiteOnMove(forwardMostMove)) {
16329             DisplayError(_("Wait until your turn."), 0);
16330             return;
16331         }
16332         break;
16333       case BeginningOfGame:
16334       case MachinePlaysBlack:
16335         if (!WhiteOnMove(forwardMostMove)) {
16336             DisplayError(_("Wait until your turn."), 0);
16337             return;
16338         }
16339         break;
16340       default:
16341         DisplayError(_("No hint available"), 0);
16342         return;
16343     }
16344     SendToProgram("hint\n", &first);
16345     hintRequested = TRUE;
16346 }
16347
16348 int
16349 SaveSelected (FILE *g, int dummy, char *dummy2)
16350 {
16351     ListGame * lg = (ListGame *) gameList.head;
16352     int nItem, cnt=0;
16353     FILE *f;
16354
16355     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16356         DisplayError(_("Game list not loaded or empty"), 0);
16357         return 0;
16358     }
16359
16360     creatingBook = TRUE; // suppresses stuff during load game
16361
16362     /* Get list size */
16363     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16364         if(lg->position >= 0) { // selected?
16365             LoadGame(f, nItem, "", TRUE);
16366             SaveGamePGN2(g); // leaves g open
16367             cnt++; DoEvents();
16368         }
16369         lg = (ListGame *) lg->node.succ;
16370     }
16371
16372     fclose(g);
16373     creatingBook = FALSE;
16374
16375     return cnt;
16376 }
16377
16378 void
16379 CreateBookEvent ()
16380 {
16381     ListGame * lg = (ListGame *) gameList.head;
16382     FILE *f, *g;
16383     int nItem;
16384     static int secondTime = FALSE;
16385
16386     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16387         DisplayError(_("Game list not loaded or empty"), 0);
16388         return;
16389     }
16390
16391     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16392         fclose(g);
16393         secondTime++;
16394         DisplayNote(_("Book file exists! Try again for overwrite."));
16395         return;
16396     }
16397
16398     creatingBook = TRUE;
16399     secondTime = FALSE;
16400
16401     /* Get list size */
16402     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16403         if(lg->position >= 0) {
16404             LoadGame(f, nItem, "", TRUE);
16405             AddGameToBook(TRUE);
16406             DoEvents();
16407         }
16408         lg = (ListGame *) lg->node.succ;
16409     }
16410
16411     creatingBook = FALSE;
16412     FlushBook();
16413 }
16414
16415 void
16416 BookEvent ()
16417 {
16418     if (appData.noChessProgram) return;
16419     switch (gameMode) {
16420       case MachinePlaysWhite:
16421         if (WhiteOnMove(forwardMostMove)) {
16422             DisplayError(_("Wait until your turn."), 0);
16423             return;
16424         }
16425         break;
16426       case BeginningOfGame:
16427       case MachinePlaysBlack:
16428         if (!WhiteOnMove(forwardMostMove)) {
16429             DisplayError(_("Wait until your turn."), 0);
16430             return;
16431         }
16432         break;
16433       case EditPosition:
16434         EditPositionDone(TRUE);
16435         break;
16436       case TwoMachinesPlay:
16437         return;
16438       default:
16439         break;
16440     }
16441     SendToProgram("bk\n", &first);
16442     bookOutput[0] = NULLCHAR;
16443     bookRequested = TRUE;
16444 }
16445
16446 void
16447 AboutGameEvent ()
16448 {
16449     char *tags = PGNTags(&gameInfo);
16450     TagsPopUp(tags, CmailMsg());
16451     free(tags);
16452 }
16453
16454 /* end button procedures */
16455
16456 void
16457 PrintPosition (FILE *fp, int move)
16458 {
16459     int i, j;
16460
16461     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16462         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16463             char c = PieceToChar(boards[move][i][j]);
16464             fputc(c == '?' ? '.' : c, fp);
16465             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16466         }
16467     }
16468     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16469       fprintf(fp, "white to play\n");
16470     else
16471       fprintf(fp, "black to play\n");
16472 }
16473
16474 void
16475 PrintOpponents (FILE *fp)
16476 {
16477     if (gameInfo.white != NULL) {
16478         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16479     } else {
16480         fprintf(fp, "\n");
16481     }
16482 }
16483
16484 /* Find last component of program's own name, using some heuristics */
16485 void
16486 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16487 {
16488     char *p, *q, c;
16489     int local = (strcmp(host, "localhost") == 0);
16490     while (!local && (p = strchr(prog, ';')) != NULL) {
16491         p++;
16492         while (*p == ' ') p++;
16493         prog = p;
16494     }
16495     if (*prog == '"' || *prog == '\'') {
16496         q = strchr(prog + 1, *prog);
16497     } else {
16498         q = strchr(prog, ' ');
16499     }
16500     if (q == NULL) q = prog + strlen(prog);
16501     p = q;
16502     while (p >= prog && *p != '/' && *p != '\\') p--;
16503     p++;
16504     if(p == prog && *p == '"') p++;
16505     c = *q; *q = 0;
16506     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16507     memcpy(buf, p, q - p);
16508     buf[q - p] = NULLCHAR;
16509     if (!local) {
16510         strcat(buf, "@");
16511         strcat(buf, host);
16512     }
16513 }
16514
16515 char *
16516 TimeControlTagValue ()
16517 {
16518     char buf[MSG_SIZ];
16519     if (!appData.clockMode) {
16520       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16521     } else if (movesPerSession > 0) {
16522       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16523     } else if (timeIncrement == 0) {
16524       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16525     } else {
16526       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16527     }
16528     return StrSave(buf);
16529 }
16530
16531 void
16532 SetGameInfo ()
16533 {
16534     /* This routine is used only for certain modes */
16535     VariantClass v = gameInfo.variant;
16536     ChessMove r = GameUnfinished;
16537     char *p = NULL;
16538
16539     if(keepInfo) return;
16540
16541     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16542         r = gameInfo.result;
16543         p = gameInfo.resultDetails;
16544         gameInfo.resultDetails = NULL;
16545     }
16546     ClearGameInfo(&gameInfo);
16547     gameInfo.variant = v;
16548
16549     switch (gameMode) {
16550       case MachinePlaysWhite:
16551         gameInfo.event = StrSave( appData.pgnEventHeader );
16552         gameInfo.site = StrSave(HostName());
16553         gameInfo.date = PGNDate();
16554         gameInfo.round = StrSave("-");
16555         gameInfo.white = StrSave(first.tidy);
16556         gameInfo.black = StrSave(UserName());
16557         gameInfo.timeControl = TimeControlTagValue();
16558         break;
16559
16560       case MachinePlaysBlack:
16561         gameInfo.event = StrSave( appData.pgnEventHeader );
16562         gameInfo.site = StrSave(HostName());
16563         gameInfo.date = PGNDate();
16564         gameInfo.round = StrSave("-");
16565         gameInfo.white = StrSave(UserName());
16566         gameInfo.black = StrSave(first.tidy);
16567         gameInfo.timeControl = TimeControlTagValue();
16568         break;
16569
16570       case TwoMachinesPlay:
16571         gameInfo.event = StrSave( appData.pgnEventHeader );
16572         gameInfo.site = StrSave(HostName());
16573         gameInfo.date = PGNDate();
16574         if (roundNr > 0) {
16575             char buf[MSG_SIZ];
16576             snprintf(buf, MSG_SIZ, "%d", roundNr);
16577             gameInfo.round = StrSave(buf);
16578         } else {
16579             gameInfo.round = StrSave("-");
16580         }
16581         if (first.twoMachinesColor[0] == 'w') {
16582             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16583             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16584         } else {
16585             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16586             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16587         }
16588         gameInfo.timeControl = TimeControlTagValue();
16589         break;
16590
16591       case EditGame:
16592         gameInfo.event = StrSave("Edited game");
16593         gameInfo.site = StrSave(HostName());
16594         gameInfo.date = PGNDate();
16595         gameInfo.round = StrSave("-");
16596         gameInfo.white = StrSave("-");
16597         gameInfo.black = StrSave("-");
16598         gameInfo.result = r;
16599         gameInfo.resultDetails = p;
16600         break;
16601
16602       case EditPosition:
16603         gameInfo.event = StrSave("Edited position");
16604         gameInfo.site = StrSave(HostName());
16605         gameInfo.date = PGNDate();
16606         gameInfo.round = StrSave("-");
16607         gameInfo.white = StrSave("-");
16608         gameInfo.black = StrSave("-");
16609         break;
16610
16611       case IcsPlayingWhite:
16612       case IcsPlayingBlack:
16613       case IcsObserving:
16614       case IcsExamining:
16615         break;
16616
16617       case PlayFromGameFile:
16618         gameInfo.event = StrSave("Game from non-PGN file");
16619         gameInfo.site = StrSave(HostName());
16620         gameInfo.date = PGNDate();
16621         gameInfo.round = StrSave("-");
16622         gameInfo.white = StrSave("?");
16623         gameInfo.black = StrSave("?");
16624         break;
16625
16626       default:
16627         break;
16628     }
16629 }
16630
16631 void
16632 ReplaceComment (int index, char *text)
16633 {
16634     int len;
16635     char *p;
16636     float score;
16637
16638     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16639        pvInfoList[index-1].depth == len &&
16640        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16641        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16642     while (*text == '\n') text++;
16643     len = strlen(text);
16644     while (len > 0 && text[len - 1] == '\n') len--;
16645
16646     if (commentList[index] != NULL)
16647       free(commentList[index]);
16648
16649     if (len == 0) {
16650         commentList[index] = NULL;
16651         return;
16652     }
16653   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16654       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16655       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16656     commentList[index] = (char *) malloc(len + 2);
16657     strncpy(commentList[index], text, len);
16658     commentList[index][len] = '\n';
16659     commentList[index][len + 1] = NULLCHAR;
16660   } else {
16661     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16662     char *p;
16663     commentList[index] = (char *) malloc(len + 7);
16664     safeStrCpy(commentList[index], "{\n", 3);
16665     safeStrCpy(commentList[index]+2, text, len+1);
16666     commentList[index][len+2] = NULLCHAR;
16667     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16668     strcat(commentList[index], "\n}\n");
16669   }
16670 }
16671
16672 void
16673 CrushCRs (char *text)
16674 {
16675   char *p = text;
16676   char *q = text;
16677   char ch;
16678
16679   do {
16680     ch = *p++;
16681     if (ch == '\r') continue;
16682     *q++ = ch;
16683   } while (ch != '\0');
16684 }
16685
16686 void
16687 AppendComment (int index, char *text, Boolean addBraces)
16688 /* addBraces  tells if we should add {} */
16689 {
16690     int oldlen, len;
16691     char *old;
16692
16693 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16694     if(addBraces == 3) addBraces = 0; else // force appending literally
16695     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16696
16697     CrushCRs(text);
16698     while (*text == '\n') text++;
16699     len = strlen(text);
16700     while (len > 0 && text[len - 1] == '\n') len--;
16701     text[len] = NULLCHAR;
16702
16703     if (len == 0) return;
16704
16705     if (commentList[index] != NULL) {
16706       Boolean addClosingBrace = addBraces;
16707         old = commentList[index];
16708         oldlen = strlen(old);
16709         while(commentList[index][oldlen-1] ==  '\n')
16710           commentList[index][--oldlen] = NULLCHAR;
16711         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16712         safeStrCpy(commentList[index], old, oldlen + len + 6);
16713         free(old);
16714         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16715         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16716           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16717           while (*text == '\n') { text++; len--; }
16718           commentList[index][--oldlen] = NULLCHAR;
16719       }
16720         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16721         else          strcat(commentList[index], "\n");
16722         strcat(commentList[index], text);
16723         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16724         else          strcat(commentList[index], "\n");
16725     } else {
16726         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16727         if(addBraces)
16728           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16729         else commentList[index][0] = NULLCHAR;
16730         strcat(commentList[index], text);
16731         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16732         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16733     }
16734 }
16735
16736 static char *
16737 FindStr (char * text, char * sub_text)
16738 {
16739     char * result = strstr( text, sub_text );
16740
16741     if( result != NULL ) {
16742         result += strlen( sub_text );
16743     }
16744
16745     return result;
16746 }
16747
16748 /* [AS] Try to extract PV info from PGN comment */
16749 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16750 char *
16751 GetInfoFromComment (int index, char * text)
16752 {
16753     char * sep = text, *p;
16754
16755     if( text != NULL && index > 0 ) {
16756         int score = 0;
16757         int depth = 0;
16758         int time = -1, sec = 0, deci;
16759         char * s_eval = FindStr( text, "[%eval " );
16760         char * s_emt = FindStr( text, "[%emt " );
16761 #if 0
16762         if( s_eval != NULL || s_emt != NULL ) {
16763 #else
16764         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16765 #endif
16766             /* New style */
16767             char delim;
16768
16769             if( s_eval != NULL ) {
16770                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16771                     return text;
16772                 }
16773
16774                 if( delim != ']' ) {
16775                     return text;
16776                 }
16777             }
16778
16779             if( s_emt != NULL ) {
16780             }
16781                 return text;
16782         }
16783         else {
16784             /* We expect something like: [+|-]nnn.nn/dd */
16785             int score_lo = 0;
16786
16787             if(*text != '{') return text; // [HGM] braces: must be normal comment
16788
16789             sep = strchr( text, '/' );
16790             if( sep == NULL || sep < (text+4) ) {
16791                 return text;
16792             }
16793
16794             p = text;
16795             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16796             if(p[1] == '(') { // comment starts with PV
16797                p = strchr(p, ')'); // locate end of PV
16798                if(p == NULL || sep < p+5) return text;
16799                // at this point we have something like "{(.*) +0.23/6 ..."
16800                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16801                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16802                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16803             }
16804             time = -1; sec = -1; deci = -1;
16805             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16806                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16807                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16808                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16809                 return text;
16810             }
16811
16812             if( score_lo < 0 || score_lo >= 100 ) {
16813                 return text;
16814             }
16815
16816             if(sec >= 0) time = 600*time + 10*sec; else
16817             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16818
16819             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16820
16821             /* [HGM] PV time: now locate end of PV info */
16822             while( *++sep >= '0' && *sep <= '9'); // strip depth
16823             if(time >= 0)
16824             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16825             if(sec >= 0)
16826             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16827             if(deci >= 0)
16828             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16829             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16830         }
16831
16832         if( depth <= 0 ) {
16833             return text;
16834         }
16835
16836         if( time < 0 ) {
16837             time = -1;
16838         }
16839
16840         pvInfoList[index-1].depth = depth;
16841         pvInfoList[index-1].score = score;
16842         pvInfoList[index-1].time  = 10*time; // centi-sec
16843         if(*sep == '}') *sep = 0; else *--sep = '{';
16844         if(p != text) {
16845             while(*p++ = *sep++)
16846                                 ;
16847             sep = text;
16848         } // squeeze out space between PV and comment, and return both
16849     }
16850     return sep;
16851 }
16852
16853 void
16854 SendToProgram (char *message, ChessProgramState *cps)
16855 {
16856     int count, outCount, error;
16857     char buf[MSG_SIZ];
16858
16859     if (cps->pr == NoProc) return;
16860     Attention(cps);
16861
16862     if (appData.debugMode) {
16863         TimeMark now;
16864         GetTimeMark(&now);
16865         fprintf(debugFP, "%ld >%-6s: %s",
16866                 SubtractTimeMarks(&now, &programStartTime),
16867                 cps->which, message);
16868         if(serverFP)
16869             fprintf(serverFP, "%ld >%-6s: %s",
16870                 SubtractTimeMarks(&now, &programStartTime),
16871                 cps->which, message), fflush(serverFP);
16872     }
16873
16874     count = strlen(message);
16875     outCount = OutputToProcess(cps->pr, message, count, &error);
16876     if (outCount < count && !exiting
16877                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16878       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16879       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16880         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16881             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16882                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16883                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16884                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16885             } else {
16886                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16887                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16888                 gameInfo.result = res;
16889             }
16890             gameInfo.resultDetails = StrSave(buf);
16891         }
16892         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16893         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16894     }
16895 }
16896
16897 void
16898 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16899 {
16900     char *end_str;
16901     char buf[MSG_SIZ];
16902     ChessProgramState *cps = (ChessProgramState *)closure;
16903
16904     if (isr != cps->isr) return; /* Killed intentionally */
16905     if (count <= 0) {
16906         if (count == 0) {
16907             RemoveInputSource(cps->isr);
16908             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16909                     _(cps->which), cps->program);
16910             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16911             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16912                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16913                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16914                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16915                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16916                 } else {
16917                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16918                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16919                     gameInfo.result = res;
16920                 }
16921                 gameInfo.resultDetails = StrSave(buf);
16922             }
16923             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16924             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16925         } else {
16926             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16927                     _(cps->which), cps->program);
16928             RemoveInputSource(cps->isr);
16929
16930             /* [AS] Program is misbehaving badly... kill it */
16931             if( count == -2 ) {
16932                 DestroyChildProcess( cps->pr, 9 );
16933                 cps->pr = NoProc;
16934             }
16935
16936             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16937         }
16938         return;
16939     }
16940
16941     if ((end_str = strchr(message, '\r')) != NULL)
16942       *end_str = NULLCHAR;
16943     if ((end_str = strchr(message, '\n')) != NULL)
16944       *end_str = NULLCHAR;
16945
16946     if (appData.debugMode) {
16947         TimeMark now; int print = 1;
16948         char *quote = ""; char c; int i;
16949
16950         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16951                 char start = message[0];
16952                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16953                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16954                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16955                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16956                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16957                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16958                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16959                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16960                    sscanf(message, "hint: %c", &c)!=1 &&
16961                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16962                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16963                     print = (appData.engineComments >= 2);
16964                 }
16965                 message[0] = start; // restore original message
16966         }
16967         if(print) {
16968                 GetTimeMark(&now);
16969                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16970                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16971                         quote,
16972                         message);
16973                 if(serverFP)
16974                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16975                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16976                         quote,
16977                         message), fflush(serverFP);
16978         }
16979     }
16980
16981     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16982     if (appData.icsEngineAnalyze) {
16983         if (strstr(message, "whisper") != NULL ||
16984              strstr(message, "kibitz") != NULL ||
16985             strstr(message, "tellics") != NULL) return;
16986     }
16987
16988     HandleMachineMove(message, cps);
16989 }
16990
16991
16992 void
16993 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16994 {
16995     char buf[MSG_SIZ];
16996     int seconds;
16997
16998     if( timeControl_2 > 0 ) {
16999         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17000             tc = timeControl_2;
17001         }
17002     }
17003     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17004     inc /= cps->timeOdds;
17005     st  /= cps->timeOdds;
17006
17007     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17008
17009     if (st > 0) {
17010       /* Set exact time per move, normally using st command */
17011       if (cps->stKludge) {
17012         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17013         seconds = st % 60;
17014         if (seconds == 0) {
17015           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17016         } else {
17017           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17018         }
17019       } else {
17020         snprintf(buf, MSG_SIZ, "st %d\n", st);
17021       }
17022     } else {
17023       /* Set conventional or incremental time control, using level command */
17024       if (seconds == 0) {
17025         /* Note old gnuchess bug -- minutes:seconds used to not work.
17026            Fixed in later versions, but still avoid :seconds
17027            when seconds is 0. */
17028         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17029       } else {
17030         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17031                  seconds, inc/1000.);
17032       }
17033     }
17034     SendToProgram(buf, cps);
17035
17036     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17037     /* Orthogonally, limit search to given depth */
17038     if (sd > 0) {
17039       if (cps->sdKludge) {
17040         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17041       } else {
17042         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17043       }
17044       SendToProgram(buf, cps);
17045     }
17046
17047     if(cps->nps >= 0) { /* [HGM] nps */
17048         if(cps->supportsNPS == FALSE)
17049           cps->nps = -1; // don't use if engine explicitly says not supported!
17050         else {
17051           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17052           SendToProgram(buf, cps);
17053         }
17054     }
17055 }
17056
17057 ChessProgramState *
17058 WhitePlayer ()
17059 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17060 {
17061     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17062        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17063         return &second;
17064     return &first;
17065 }
17066
17067 void
17068 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17069 {
17070     char message[MSG_SIZ];
17071     long time, otime;
17072
17073     /* Note: this routine must be called when the clocks are stopped
17074        or when they have *just* been set or switched; otherwise
17075        it will be off by the time since the current tick started.
17076     */
17077     if (machineWhite) {
17078         time = whiteTimeRemaining / 10;
17079         otime = blackTimeRemaining / 10;
17080     } else {
17081         time = blackTimeRemaining / 10;
17082         otime = whiteTimeRemaining / 10;
17083     }
17084     /* [HGM] translate opponent's time by time-odds factor */
17085     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17086
17087     if (time <= 0) time = 1;
17088     if (otime <= 0) otime = 1;
17089
17090     snprintf(message, MSG_SIZ, "time %ld\n", time);
17091     SendToProgram(message, cps);
17092
17093     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17094     SendToProgram(message, cps);
17095 }
17096
17097 char *
17098 EngineDefinedVariant (ChessProgramState *cps, int n)
17099 {   // return name of n-th unknown variant that engine supports
17100     static char buf[MSG_SIZ];
17101     char *p, *s = cps->variants;
17102     if(!s) return NULL;
17103     do { // parse string from variants feature
17104       VariantClass v;
17105         p = strchr(s, ',');
17106         if(p) *p = NULLCHAR;
17107       v = StringToVariant(s);
17108       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17109         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17110             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17111                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17112                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17113                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17114             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17115         }
17116         if(p) *p++ = ',';
17117         if(n < 0) return buf;
17118     } while(s = p);
17119     return NULL;
17120 }
17121
17122 int
17123 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17124 {
17125   char buf[MSG_SIZ];
17126   int len = strlen(name);
17127   int val;
17128
17129   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17130     (*p) += len + 1;
17131     sscanf(*p, "%d", &val);
17132     *loc = (val != 0);
17133     while (**p && **p != ' ')
17134       (*p)++;
17135     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17136     SendToProgram(buf, cps);
17137     return TRUE;
17138   }
17139   return FALSE;
17140 }
17141
17142 int
17143 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17144 {
17145   char buf[MSG_SIZ];
17146   int len = strlen(name);
17147   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17148     (*p) += len + 1;
17149     sscanf(*p, "%d", loc);
17150     while (**p && **p != ' ') (*p)++;
17151     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17152     SendToProgram(buf, cps);
17153     return TRUE;
17154   }
17155   return FALSE;
17156 }
17157
17158 int
17159 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17160 {
17161   char buf[MSG_SIZ];
17162   int len = strlen(name);
17163   if (strncmp((*p), name, len) == 0
17164       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17165     (*p) += len + 2;
17166     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17167     sscanf(*p, "%[^\"]", *loc);
17168     while (**p && **p != '\"') (*p)++;
17169     if (**p == '\"') (*p)++;
17170     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17171     SendToProgram(buf, cps);
17172     return TRUE;
17173   }
17174   return FALSE;
17175 }
17176
17177 int
17178 ParseOption (Option *opt, ChessProgramState *cps)
17179 // [HGM] options: process the string that defines an engine option, and determine
17180 // name, type, default value, and allowed value range
17181 {
17182         char *p, *q, buf[MSG_SIZ];
17183         int n, min = (-1)<<31, max = 1<<31, def;
17184
17185         opt->target = &opt->value;   // OK for spin/slider and checkbox
17186         if(p = strstr(opt->name, " -spin ")) {
17187             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17188             if(max < min) max = min; // enforce consistency
17189             if(def < min) def = min;
17190             if(def > max) def = max;
17191             opt->value = def;
17192             opt->min = min;
17193             opt->max = max;
17194             opt->type = Spin;
17195         } else if((p = strstr(opt->name, " -slider "))) {
17196             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17197             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17198             if(max < min) max = min; // enforce consistency
17199             if(def < min) def = min;
17200             if(def > max) def = max;
17201             opt->value = def;
17202             opt->min = min;
17203             opt->max = max;
17204             opt->type = Spin; // Slider;
17205         } else if((p = strstr(opt->name, " -string "))) {
17206             opt->textValue = p+9;
17207             opt->type = TextBox;
17208             opt->target = &opt->textValue;
17209         } else if((p = strstr(opt->name, " -file "))) {
17210             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17211             opt->target = opt->textValue = p+7;
17212             opt->type = FileName; // FileName;
17213             opt->target = &opt->textValue;
17214         } else if((p = strstr(opt->name, " -path "))) {
17215             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17216             opt->target = opt->textValue = p+7;
17217             opt->type = PathName; // PathName;
17218             opt->target = &opt->textValue;
17219         } else if(p = strstr(opt->name, " -check ")) {
17220             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17221             opt->value = (def != 0);
17222             opt->type = CheckBox;
17223         } else if(p = strstr(opt->name, " -combo ")) {
17224             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17225             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17226             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17227             opt->value = n = 0;
17228             while(q = StrStr(q, " /// ")) {
17229                 n++; *q = 0;    // count choices, and null-terminate each of them
17230                 q += 5;
17231                 if(*q == '*') { // remember default, which is marked with * prefix
17232                     q++;
17233                     opt->value = n;
17234                 }
17235                 cps->comboList[cps->comboCnt++] = q;
17236             }
17237             cps->comboList[cps->comboCnt++] = NULL;
17238             opt->max = n + 1;
17239             opt->type = ComboBox;
17240         } else if(p = strstr(opt->name, " -button")) {
17241             opt->type = Button;
17242         } else if(p = strstr(opt->name, " -save")) {
17243             opt->type = SaveButton;
17244         } else return FALSE;
17245         *p = 0; // terminate option name
17246         // now look if the command-line options define a setting for this engine option.
17247         if(cps->optionSettings && cps->optionSettings[0])
17248             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17249         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17250           snprintf(buf, MSG_SIZ, "option %s", p);
17251                 if(p = strstr(buf, ",")) *p = 0;
17252                 if(q = strchr(buf, '=')) switch(opt->type) {
17253                     case ComboBox:
17254                         for(n=0; n<opt->max; n++)
17255                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17256                         break;
17257                     case TextBox:
17258                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17259                         break;
17260                     case Spin:
17261                     case CheckBox:
17262                         opt->value = atoi(q+1);
17263                     default:
17264                         break;
17265                 }
17266                 strcat(buf, "\n");
17267                 SendToProgram(buf, cps);
17268         }
17269         return TRUE;
17270 }
17271
17272 void
17273 FeatureDone (ChessProgramState *cps, int val)
17274 {
17275   DelayedEventCallback cb = GetDelayedEvent();
17276   if ((cb == InitBackEnd3 && cps == &first) ||
17277       (cb == SettingsMenuIfReady && cps == &second) ||
17278       (cb == LoadEngine) ||
17279       (cb == TwoMachinesEventIfReady)) {
17280     CancelDelayedEvent();
17281     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17282   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17283   cps->initDone = val;
17284   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17285 }
17286
17287 /* Parse feature command from engine */
17288 void
17289 ParseFeatures (char *args, ChessProgramState *cps)
17290 {
17291   char *p = args;
17292   char *q = NULL;
17293   int val;
17294   char buf[MSG_SIZ];
17295
17296   for (;;) {
17297     while (*p == ' ') p++;
17298     if (*p == NULLCHAR) return;
17299
17300     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17301     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17302     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17303     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17304     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17305     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17306     if (BoolFeature(&p, "reuse", &val, cps)) {
17307       /* Engine can disable reuse, but can't enable it if user said no */
17308       if (!val) cps->reuse = FALSE;
17309       continue;
17310     }
17311     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17312     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17313       if (gameMode == TwoMachinesPlay) {
17314         DisplayTwoMachinesTitle();
17315       } else {
17316         DisplayTitle("");
17317       }
17318       continue;
17319     }
17320     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17321     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17322     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17323     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17324     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17325     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17326     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17327     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17328     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17329     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17330     if (IntFeature(&p, "done", &val, cps)) {
17331       FeatureDone(cps, val);
17332       continue;
17333     }
17334     /* Added by Tord: */
17335     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17336     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17337     /* End of additions by Tord */
17338
17339     /* [HGM] added features: */
17340     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17341     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17342     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17343     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17344     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17345     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17346     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17347     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17348         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17349         FREE(cps->option[cps->nrOptions].name);
17350         cps->option[cps->nrOptions].name = q; q = NULL;
17351         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17352           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17353             SendToProgram(buf, cps);
17354             continue;
17355         }
17356         if(cps->nrOptions >= MAX_OPTIONS) {
17357             cps->nrOptions--;
17358             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17359             DisplayError(buf, 0);
17360         }
17361         continue;
17362     }
17363     /* End of additions by HGM */
17364
17365     /* unknown feature: complain and skip */
17366     q = p;
17367     while (*q && *q != '=') q++;
17368     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17369     SendToProgram(buf, cps);
17370     p = q;
17371     if (*p == '=') {
17372       p++;
17373       if (*p == '\"') {
17374         p++;
17375         while (*p && *p != '\"') p++;
17376         if (*p == '\"') p++;
17377       } else {
17378         while (*p && *p != ' ') p++;
17379       }
17380     }
17381   }
17382
17383 }
17384
17385 void
17386 PeriodicUpdatesEvent (int newState)
17387 {
17388     if (newState == appData.periodicUpdates)
17389       return;
17390
17391     appData.periodicUpdates=newState;
17392
17393     /* Display type changes, so update it now */
17394 //    DisplayAnalysis();
17395
17396     /* Get the ball rolling again... */
17397     if (newState) {
17398         AnalysisPeriodicEvent(1);
17399         StartAnalysisClock();
17400     }
17401 }
17402
17403 void
17404 PonderNextMoveEvent (int newState)
17405 {
17406     if (newState == appData.ponderNextMove) return;
17407     if (gameMode == EditPosition) EditPositionDone(TRUE);
17408     if (newState) {
17409         SendToProgram("hard\n", &first);
17410         if (gameMode == TwoMachinesPlay) {
17411             SendToProgram("hard\n", &second);
17412         }
17413     } else {
17414         SendToProgram("easy\n", &first);
17415         thinkOutput[0] = NULLCHAR;
17416         if (gameMode == TwoMachinesPlay) {
17417             SendToProgram("easy\n", &second);
17418         }
17419     }
17420     appData.ponderNextMove = newState;
17421 }
17422
17423 void
17424 NewSettingEvent (int option, int *feature, char *command, int value)
17425 {
17426     char buf[MSG_SIZ];
17427
17428     if (gameMode == EditPosition) EditPositionDone(TRUE);
17429     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17430     if(feature == NULL || *feature) SendToProgram(buf, &first);
17431     if (gameMode == TwoMachinesPlay) {
17432         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17433     }
17434 }
17435
17436 void
17437 ShowThinkingEvent ()
17438 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17439 {
17440     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17441     int newState = appData.showThinking
17442         // [HGM] thinking: other features now need thinking output as well
17443         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17444
17445     if (oldState == newState) return;
17446     oldState = newState;
17447     if (gameMode == EditPosition) EditPositionDone(TRUE);
17448     if (oldState) {
17449         SendToProgram("post\n", &first);
17450         if (gameMode == TwoMachinesPlay) {
17451             SendToProgram("post\n", &second);
17452         }
17453     } else {
17454         SendToProgram("nopost\n", &first);
17455         thinkOutput[0] = NULLCHAR;
17456         if (gameMode == TwoMachinesPlay) {
17457             SendToProgram("nopost\n", &second);
17458         }
17459     }
17460 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17461 }
17462
17463 void
17464 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17465 {
17466   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17467   if (pr == NoProc) return;
17468   AskQuestion(title, question, replyPrefix, pr);
17469 }
17470
17471 void
17472 TypeInEvent (char firstChar)
17473 {
17474     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17475         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17476         gameMode == AnalyzeMode || gameMode == EditGame ||
17477         gameMode == EditPosition || gameMode == IcsExamining ||
17478         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17479         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17480                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17481                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17482         gameMode == Training) PopUpMoveDialog(firstChar);
17483 }
17484
17485 void
17486 TypeInDoneEvent (char *move)
17487 {
17488         Board board;
17489         int n, fromX, fromY, toX, toY;
17490         char promoChar;
17491         ChessMove moveType;
17492
17493         // [HGM] FENedit
17494         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17495                 EditPositionPasteFEN(move);
17496                 return;
17497         }
17498         // [HGM] movenum: allow move number to be typed in any mode
17499         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17500           ToNrEvent(2*n-1);
17501           return;
17502         }
17503         // undocumented kludge: allow command-line option to be typed in!
17504         // (potentially fatal, and does not implement the effect of the option.)
17505         // should only be used for options that are values on which future decisions will be made,
17506         // and definitely not on options that would be used during initialization.
17507         if(strstr(move, "!!! -") == move) {
17508             ParseArgsFromString(move+4);
17509             return;
17510         }
17511
17512       if (gameMode != EditGame && currentMove != forwardMostMove &&
17513         gameMode != Training) {
17514         DisplayMoveError(_("Displayed move is not current"));
17515       } else {
17516         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17517           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17518         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17519         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17520           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17521           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17522         } else {
17523           DisplayMoveError(_("Could not parse move"));
17524         }
17525       }
17526 }
17527
17528 void
17529 DisplayMove (int moveNumber)
17530 {
17531     char message[MSG_SIZ];
17532     char res[MSG_SIZ];
17533     char cpThinkOutput[MSG_SIZ];
17534
17535     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17536
17537     if (moveNumber == forwardMostMove - 1 ||
17538         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17539
17540         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17541
17542         if (strchr(cpThinkOutput, '\n')) {
17543             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17544         }
17545     } else {
17546         *cpThinkOutput = NULLCHAR;
17547     }
17548
17549     /* [AS] Hide thinking from human user */
17550     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17551         *cpThinkOutput = NULLCHAR;
17552         if( thinkOutput[0] != NULLCHAR ) {
17553             int i;
17554
17555             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17556                 cpThinkOutput[i] = '.';
17557             }
17558             cpThinkOutput[i] = NULLCHAR;
17559             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17560         }
17561     }
17562
17563     if (moveNumber == forwardMostMove - 1 &&
17564         gameInfo.resultDetails != NULL) {
17565         if (gameInfo.resultDetails[0] == NULLCHAR) {
17566           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17567         } else {
17568           snprintf(res, MSG_SIZ, " {%s} %s",
17569                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17570         }
17571     } else {
17572         res[0] = NULLCHAR;
17573     }
17574
17575     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17576         DisplayMessage(res, cpThinkOutput);
17577     } else {
17578       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17579                 WhiteOnMove(moveNumber) ? " " : ".. ",
17580                 parseList[moveNumber], res);
17581         DisplayMessage(message, cpThinkOutput);
17582     }
17583 }
17584
17585 void
17586 DisplayComment (int moveNumber, char *text)
17587 {
17588     char title[MSG_SIZ];
17589
17590     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17591       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17592     } else {
17593       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17594               WhiteOnMove(moveNumber) ? " " : ".. ",
17595               parseList[moveNumber]);
17596     }
17597     if (text != NULL && (appData.autoDisplayComment || commentUp))
17598         CommentPopUp(title, text);
17599 }
17600
17601 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17602  * might be busy thinking or pondering.  It can be omitted if your
17603  * gnuchess is configured to stop thinking immediately on any user
17604  * input.  However, that gnuchess feature depends on the FIONREAD
17605  * ioctl, which does not work properly on some flavors of Unix.
17606  */
17607 void
17608 Attention (ChessProgramState *cps)
17609 {
17610 #if ATTENTION
17611     if (!cps->useSigint) return;
17612     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17613     switch (gameMode) {
17614       case MachinePlaysWhite:
17615       case MachinePlaysBlack:
17616       case TwoMachinesPlay:
17617       case IcsPlayingWhite:
17618       case IcsPlayingBlack:
17619       case AnalyzeMode:
17620       case AnalyzeFile:
17621         /* Skip if we know it isn't thinking */
17622         if (!cps->maybeThinking) return;
17623         if (appData.debugMode)
17624           fprintf(debugFP, "Interrupting %s\n", cps->which);
17625         InterruptChildProcess(cps->pr);
17626         cps->maybeThinking = FALSE;
17627         break;
17628       default:
17629         break;
17630     }
17631 #endif /*ATTENTION*/
17632 }
17633
17634 int
17635 CheckFlags ()
17636 {
17637     if (whiteTimeRemaining <= 0) {
17638         if (!whiteFlag) {
17639             whiteFlag = TRUE;
17640             if (appData.icsActive) {
17641                 if (appData.autoCallFlag &&
17642                     gameMode == IcsPlayingBlack && !blackFlag) {
17643                   SendToICS(ics_prefix);
17644                   SendToICS("flag\n");
17645                 }
17646             } else {
17647                 if (blackFlag) {
17648                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17649                 } else {
17650                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17651                     if (appData.autoCallFlag) {
17652                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17653                         return TRUE;
17654                     }
17655                 }
17656             }
17657         }
17658     }
17659     if (blackTimeRemaining <= 0) {
17660         if (!blackFlag) {
17661             blackFlag = TRUE;
17662             if (appData.icsActive) {
17663                 if (appData.autoCallFlag &&
17664                     gameMode == IcsPlayingWhite && !whiteFlag) {
17665                   SendToICS(ics_prefix);
17666                   SendToICS("flag\n");
17667                 }
17668             } else {
17669                 if (whiteFlag) {
17670                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17671                 } else {
17672                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17673                     if (appData.autoCallFlag) {
17674                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17675                         return TRUE;
17676                     }
17677                 }
17678             }
17679         }
17680     }
17681     return FALSE;
17682 }
17683
17684 void
17685 CheckTimeControl ()
17686 {
17687     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17688         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17689
17690     /*
17691      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17692      */
17693     if ( !WhiteOnMove(forwardMostMove) ) {
17694         /* White made time control */
17695         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17696         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17697         /* [HGM] time odds: correct new time quota for time odds! */
17698                                             / WhitePlayer()->timeOdds;
17699         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17700     } else {
17701         lastBlack -= blackTimeRemaining;
17702         /* Black made time control */
17703         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17704                                             / WhitePlayer()->other->timeOdds;
17705         lastWhite = whiteTimeRemaining;
17706     }
17707 }
17708
17709 void
17710 DisplayBothClocks ()
17711 {
17712     int wom = gameMode == EditPosition ?
17713       !blackPlaysFirst : WhiteOnMove(currentMove);
17714     DisplayWhiteClock(whiteTimeRemaining, wom);
17715     DisplayBlackClock(blackTimeRemaining, !wom);
17716 }
17717
17718
17719 /* Timekeeping seems to be a portability nightmare.  I think everyone
17720    has ftime(), but I'm really not sure, so I'm including some ifdefs
17721    to use other calls if you don't.  Clocks will be less accurate if
17722    you have neither ftime nor gettimeofday.
17723 */
17724
17725 /* VS 2008 requires the #include outside of the function */
17726 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17727 #include <sys/timeb.h>
17728 #endif
17729
17730 /* Get the current time as a TimeMark */
17731 void
17732 GetTimeMark (TimeMark *tm)
17733 {
17734 #if HAVE_GETTIMEOFDAY
17735
17736     struct timeval timeVal;
17737     struct timezone timeZone;
17738
17739     gettimeofday(&timeVal, &timeZone);
17740     tm->sec = (long) timeVal.tv_sec;
17741     tm->ms = (int) (timeVal.tv_usec / 1000L);
17742
17743 #else /*!HAVE_GETTIMEOFDAY*/
17744 #if HAVE_FTIME
17745
17746 // include <sys/timeb.h> / moved to just above start of function
17747     struct timeb timeB;
17748
17749     ftime(&timeB);
17750     tm->sec = (long) timeB.time;
17751     tm->ms = (int) timeB.millitm;
17752
17753 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17754     tm->sec = (long) time(NULL);
17755     tm->ms = 0;
17756 #endif
17757 #endif
17758 }
17759
17760 /* Return the difference in milliseconds between two
17761    time marks.  We assume the difference will fit in a long!
17762 */
17763 long
17764 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17765 {
17766     return 1000L*(tm2->sec - tm1->sec) +
17767            (long) (tm2->ms - tm1->ms);
17768 }
17769
17770
17771 /*
17772  * Code to manage the game clocks.
17773  *
17774  * In tournament play, black starts the clock and then white makes a move.
17775  * We give the human user a slight advantage if he is playing white---the
17776  * clocks don't run until he makes his first move, so it takes zero time.
17777  * Also, we don't account for network lag, so we could get out of sync
17778  * with GNU Chess's clock -- but then, referees are always right.
17779  */
17780
17781 static TimeMark tickStartTM;
17782 static long intendedTickLength;
17783
17784 long
17785 NextTickLength (long timeRemaining)
17786 {
17787     long nominalTickLength, nextTickLength;
17788
17789     if (timeRemaining > 0L && timeRemaining <= 10000L)
17790       nominalTickLength = 100L;
17791     else
17792       nominalTickLength = 1000L;
17793     nextTickLength = timeRemaining % nominalTickLength;
17794     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17795
17796     return nextTickLength;
17797 }
17798
17799 /* Adjust clock one minute up or down */
17800 void
17801 AdjustClock (Boolean which, int dir)
17802 {
17803     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17804     if(which) blackTimeRemaining += 60000*dir;
17805     else      whiteTimeRemaining += 60000*dir;
17806     DisplayBothClocks();
17807     adjustedClock = TRUE;
17808 }
17809
17810 /* Stop clocks and reset to a fresh time control */
17811 void
17812 ResetClocks ()
17813 {
17814     (void) StopClockTimer();
17815     if (appData.icsActive) {
17816         whiteTimeRemaining = blackTimeRemaining = 0;
17817     } else if (searchTime) {
17818         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17819         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17820     } else { /* [HGM] correct new time quote for time odds */
17821         whiteTC = blackTC = fullTimeControlString;
17822         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17823         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17824     }
17825     if (whiteFlag || blackFlag) {
17826         DisplayTitle("");
17827         whiteFlag = blackFlag = FALSE;
17828     }
17829     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17830     DisplayBothClocks();
17831     adjustedClock = FALSE;
17832 }
17833
17834 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17835
17836 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17837
17838 /* Decrement running clock by amount of time that has passed */
17839 void
17840 DecrementClocks ()
17841 {
17842     long tRemaining;
17843     long lastTickLength, fudge;
17844     TimeMark now;
17845
17846     if (!appData.clockMode) return;
17847     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17848
17849     GetTimeMark(&now);
17850
17851     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17852
17853     /* Fudge if we woke up a little too soon */
17854     fudge = intendedTickLength - lastTickLength;
17855     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17856
17857     if (WhiteOnMove(forwardMostMove)) {
17858         if(whiteNPS >= 0) lastTickLength = 0;
17859          tRemaining = whiteTimeRemaining -= lastTickLength;
17860         if( tRemaining < 0 && !appData.icsActive) {
17861             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17862             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17863                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17864                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17865             }
17866         }
17867         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17868         DisplayWhiteClock(whiteTimeRemaining - fudge,
17869                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17870         timeSuffix = 0;
17871     } else {
17872         if(blackNPS >= 0) lastTickLength = 0;
17873          tRemaining = blackTimeRemaining -= lastTickLength;
17874         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17875             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17876             if(suddenDeath) {
17877                 blackStartMove = forwardMostMove;
17878                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17879             }
17880         }
17881         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17882         DisplayBlackClock(blackTimeRemaining - fudge,
17883                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17884         timeSuffix = 0;
17885     }
17886     if (CheckFlags()) return;
17887
17888     if(twoBoards) { // count down secondary board's clocks as well
17889         activePartnerTime -= lastTickLength;
17890         partnerUp = 1;
17891         if(activePartner == 'W')
17892             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17893         else
17894             DisplayBlackClock(activePartnerTime, TRUE);
17895         partnerUp = 0;
17896     }
17897
17898     tickStartTM = now;
17899     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17900     StartClockTimer(intendedTickLength);
17901
17902     /* if the time remaining has fallen below the alarm threshold, sound the
17903      * alarm. if the alarm has sounded and (due to a takeback or time control
17904      * with increment) the time remaining has increased to a level above the
17905      * threshold, reset the alarm so it can sound again.
17906      */
17907
17908     if (appData.icsActive && appData.icsAlarm) {
17909
17910         /* make sure we are dealing with the user's clock */
17911         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17912                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17913            )) return;
17914
17915         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17916             alarmSounded = FALSE;
17917         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17918             PlayAlarmSound();
17919             alarmSounded = TRUE;
17920         }
17921     }
17922 }
17923
17924
17925 /* A player has just moved, so stop the previously running
17926    clock and (if in clock mode) start the other one.
17927    We redisplay both clocks in case we're in ICS mode, because
17928    ICS gives us an update to both clocks after every move.
17929    Note that this routine is called *after* forwardMostMove
17930    is updated, so the last fractional tick must be subtracted
17931    from the color that is *not* on move now.
17932 */
17933 void
17934 SwitchClocks (int newMoveNr)
17935 {
17936     long lastTickLength;
17937     TimeMark now;
17938     int flagged = FALSE;
17939
17940     GetTimeMark(&now);
17941
17942     if (StopClockTimer() && appData.clockMode) {
17943         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17944         if (!WhiteOnMove(forwardMostMove)) {
17945             if(blackNPS >= 0) lastTickLength = 0;
17946             blackTimeRemaining -= lastTickLength;
17947            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17948 //         if(pvInfoList[forwardMostMove].time == -1)
17949                  pvInfoList[forwardMostMove].time =               // use GUI time
17950                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17951         } else {
17952            if(whiteNPS >= 0) lastTickLength = 0;
17953            whiteTimeRemaining -= lastTickLength;
17954            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17955 //         if(pvInfoList[forwardMostMove].time == -1)
17956                  pvInfoList[forwardMostMove].time =
17957                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17958         }
17959         flagged = CheckFlags();
17960     }
17961     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17962     CheckTimeControl();
17963
17964     if (flagged || !appData.clockMode) return;
17965
17966     switch (gameMode) {
17967       case MachinePlaysBlack:
17968       case MachinePlaysWhite:
17969       case BeginningOfGame:
17970         if (pausing) return;
17971         break;
17972
17973       case EditGame:
17974       case PlayFromGameFile:
17975       case IcsExamining:
17976         return;
17977
17978       default:
17979         break;
17980     }
17981
17982     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17983         if(WhiteOnMove(forwardMostMove))
17984              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17985         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17986     }
17987
17988     tickStartTM = now;
17989     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17990       whiteTimeRemaining : blackTimeRemaining);
17991     StartClockTimer(intendedTickLength);
17992 }
17993
17994
17995 /* Stop both clocks */
17996 void
17997 StopClocks ()
17998 {
17999     long lastTickLength;
18000     TimeMark now;
18001
18002     if (!StopClockTimer()) return;
18003     if (!appData.clockMode) return;
18004
18005     GetTimeMark(&now);
18006
18007     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18008     if (WhiteOnMove(forwardMostMove)) {
18009         if(whiteNPS >= 0) lastTickLength = 0;
18010         whiteTimeRemaining -= lastTickLength;
18011         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18012     } else {
18013         if(blackNPS >= 0) lastTickLength = 0;
18014         blackTimeRemaining -= lastTickLength;
18015         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18016     }
18017     CheckFlags();
18018 }
18019
18020 /* Start clock of player on move.  Time may have been reset, so
18021    if clock is already running, stop and restart it. */
18022 void
18023 StartClocks ()
18024 {
18025     (void) StopClockTimer(); /* in case it was running already */
18026     DisplayBothClocks();
18027     if (CheckFlags()) return;
18028
18029     if (!appData.clockMode) return;
18030     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18031
18032     GetTimeMark(&tickStartTM);
18033     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18034       whiteTimeRemaining : blackTimeRemaining);
18035
18036    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18037     whiteNPS = blackNPS = -1;
18038     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18039        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18040         whiteNPS = first.nps;
18041     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18042        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18043         blackNPS = first.nps;
18044     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18045         whiteNPS = second.nps;
18046     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18047         blackNPS = second.nps;
18048     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18049
18050     StartClockTimer(intendedTickLength);
18051 }
18052
18053 char *
18054 TimeString (long ms)
18055 {
18056     long second, minute, hour, day;
18057     char *sign = "";
18058     static char buf[40], moveTime[8];
18059
18060     if (ms > 0 && ms <= 9900) {
18061       /* convert milliseconds to tenths, rounding up */
18062       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18063
18064       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18065       return buf;
18066     }
18067
18068     /* convert milliseconds to seconds, rounding up */
18069     /* use floating point to avoid strangeness of integer division
18070        with negative dividends on many machines */
18071     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18072
18073     if (second < 0) {
18074         sign = "-";
18075         second = -second;
18076     }
18077
18078     day = second / (60 * 60 * 24);
18079     second = second % (60 * 60 * 24);
18080     hour = second / (60 * 60);
18081     second = second % (60 * 60);
18082     minute = second / 60;
18083     second = second % 60;
18084
18085     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18086     else *moveTime = NULLCHAR;
18087
18088     if (day > 0)
18089       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18090               sign, day, hour, minute, second, moveTime);
18091     else if (hour > 0)
18092       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18093     else
18094       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18095
18096     return buf;
18097 }
18098
18099
18100 /*
18101  * This is necessary because some C libraries aren't ANSI C compliant yet.
18102  */
18103 char *
18104 StrStr (char *string, char *match)
18105 {
18106     int i, length;
18107
18108     length = strlen(match);
18109
18110     for (i = strlen(string) - length; i >= 0; i--, string++)
18111       if (!strncmp(match, string, length))
18112         return string;
18113
18114     return NULL;
18115 }
18116
18117 char *
18118 StrCaseStr (char *string, char *match)
18119 {
18120     int i, j, length;
18121
18122     length = strlen(match);
18123
18124     for (i = strlen(string) - length; i >= 0; i--, string++) {
18125         for (j = 0; j < length; j++) {
18126             if (ToLower(match[j]) != ToLower(string[j]))
18127               break;
18128         }
18129         if (j == length) return string;
18130     }
18131
18132     return NULL;
18133 }
18134
18135 #ifndef _amigados
18136 int
18137 StrCaseCmp (char *s1, char *s2)
18138 {
18139     char c1, c2;
18140
18141     for (;;) {
18142         c1 = ToLower(*s1++);
18143         c2 = ToLower(*s2++);
18144         if (c1 > c2) return 1;
18145         if (c1 < c2) return -1;
18146         if (c1 == NULLCHAR) return 0;
18147     }
18148 }
18149
18150
18151 int
18152 ToLower (int c)
18153 {
18154     return isupper(c) ? tolower(c) : c;
18155 }
18156
18157
18158 int
18159 ToUpper (int c)
18160 {
18161     return islower(c) ? toupper(c) : c;
18162 }
18163 #endif /* !_amigados    */
18164
18165 char *
18166 StrSave (char *s)
18167 {
18168   char *ret;
18169
18170   if ((ret = (char *) malloc(strlen(s) + 1)))
18171     {
18172       safeStrCpy(ret, s, strlen(s)+1);
18173     }
18174   return ret;
18175 }
18176
18177 char *
18178 StrSavePtr (char *s, char **savePtr)
18179 {
18180     if (*savePtr) {
18181         free(*savePtr);
18182     }
18183     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18184       safeStrCpy(*savePtr, s, strlen(s)+1);
18185     }
18186     return(*savePtr);
18187 }
18188
18189 char *
18190 PGNDate ()
18191 {
18192     time_t clock;
18193     struct tm *tm;
18194     char buf[MSG_SIZ];
18195
18196     clock = time((time_t *)NULL);
18197     tm = localtime(&clock);
18198     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18199             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18200     return StrSave(buf);
18201 }
18202
18203
18204 char *
18205 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18206 {
18207     int i, j, fromX, fromY, toX, toY;
18208     int whiteToPlay, haveRights = nrCastlingRights;
18209     char buf[MSG_SIZ];
18210     char *p, *q;
18211     int emptycount;
18212     ChessSquare piece;
18213
18214     whiteToPlay = (gameMode == EditPosition) ?
18215       !blackPlaysFirst : (move % 2 == 0);
18216     p = buf;
18217
18218     /* Piece placement data */
18219     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18220         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18221         emptycount = 0;
18222         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18223             if (boards[move][i][j] == EmptySquare) {
18224                 emptycount++;
18225             } else { ChessSquare piece = boards[move][i][j];
18226                 if (emptycount > 0) {
18227                     if(emptycount<10) /* [HGM] can be >= 10 */
18228                         *p++ = '0' + emptycount;
18229                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18230                     emptycount = 0;
18231                 }
18232                 if(PieceToChar(piece) == '+') {
18233                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18234                     *p++ = '+';
18235                     piece = (ChessSquare)(CHUDEMOTED(piece));
18236                 }
18237                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18238                 if(*p = PieceSuffix(piece)) p++;
18239                 if(p[-1] == '~') {
18240                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18241                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18242                     *p++ = '~';
18243                 }
18244             }
18245         }
18246         if (emptycount > 0) {
18247             if(emptycount<10) /* [HGM] can be >= 10 */
18248                 *p++ = '0' + emptycount;
18249             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18250             emptycount = 0;
18251         }
18252         *p++ = '/';
18253     }
18254     *(p - 1) = ' ';
18255
18256     /* [HGM] print Crazyhouse or Shogi holdings */
18257     if( gameInfo.holdingsWidth ) {
18258         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18259         q = p;
18260         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18261             piece = boards[move][i][BOARD_WIDTH-1];
18262             if( piece != EmptySquare )
18263               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18264                   *p++ = PieceToChar(piece);
18265         }
18266         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18267             piece = boards[move][BOARD_HEIGHT-i-1][0];
18268             if( piece != EmptySquare )
18269               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18270                   *p++ = PieceToChar(piece);
18271         }
18272
18273         if( q == p ) *p++ = '-';
18274         *p++ = ']';
18275         *p++ = ' ';
18276     }
18277
18278     /* Active color */
18279     *p++ = whiteToPlay ? 'w' : 'b';
18280     *p++ = ' ';
18281
18282   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18283     haveRights = 0; q = p;
18284     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18285       piece = boards[move][0][i];
18286       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18287         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18288       }
18289     }
18290     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18291       piece = boards[move][BOARD_HEIGHT-1][i];
18292       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18293         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18294       }
18295     }
18296     if(p == q) *p++ = '-';
18297     *p++ = ' ';
18298   }
18299
18300   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18301     while(*p++ = *q++)
18302                       ;
18303     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18304   } else {
18305   if(haveRights) {
18306      int handW=0, handB=0;
18307      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18308         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18309         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18310      }
18311      q = p;
18312      if(appData.fischerCastling) {
18313         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18314            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18315                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18316         } else {
18317        /* [HGM] write directly from rights */
18318            if(boards[move][CASTLING][2] != NoRights &&
18319               boards[move][CASTLING][0] != NoRights   )
18320                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18321            if(boards[move][CASTLING][2] != NoRights &&
18322               boards[move][CASTLING][1] != NoRights   )
18323                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18324         }
18325         if(handB) {
18326            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18327                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18328         } else {
18329            if(boards[move][CASTLING][5] != NoRights &&
18330               boards[move][CASTLING][3] != NoRights   )
18331                 *p++ = boards[move][CASTLING][3] + AAA;
18332            if(boards[move][CASTLING][5] != NoRights &&
18333               boards[move][CASTLING][4] != NoRights   )
18334                 *p++ = boards[move][CASTLING][4] + AAA;
18335         }
18336      } else {
18337
18338         /* [HGM] write true castling rights */
18339         if( nrCastlingRights == 6 ) {
18340             int q, k=0;
18341             if(boards[move][CASTLING][0] != NoRights &&
18342                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18343             q = (boards[move][CASTLING][1] != NoRights &&
18344                  boards[move][CASTLING][2] != NoRights  );
18345             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18346                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18347                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18348                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18349             }
18350             if(q) *p++ = 'Q';
18351             k = 0;
18352             if(boards[move][CASTLING][3] != NoRights &&
18353                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18354             q = (boards[move][CASTLING][4] != NoRights &&
18355                  boards[move][CASTLING][5] != NoRights  );
18356             if(handB) {
18357                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18358                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18359                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18360             }
18361             if(q) *p++ = 'q';
18362         }
18363      }
18364      if (q == p) *p++ = '-'; /* No castling rights */
18365      *p++ = ' ';
18366   }
18367
18368   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18369      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18370      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18371     /* En passant target square */
18372     if (move > backwardMostMove) {
18373         fromX = moveList[move - 1][0] - AAA;
18374         fromY = moveList[move - 1][1] - ONE;
18375         toX = moveList[move - 1][2] - AAA;
18376         toY = moveList[move - 1][3] - ONE;
18377         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18378             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18379             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18380             fromX == toX) {
18381             /* 2-square pawn move just happened */
18382             *p++ = toX + AAA;
18383             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18384         } else {
18385             *p++ = '-';
18386         }
18387     } else if(move == backwardMostMove) {
18388         // [HGM] perhaps we should always do it like this, and forget the above?
18389         if((signed char)boards[move][EP_STATUS] >= 0) {
18390             *p++ = boards[move][EP_STATUS] + AAA;
18391             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18392         } else {
18393             *p++ = '-';
18394         }
18395     } else {
18396         *p++ = '-';
18397     }
18398     *p++ = ' ';
18399   }
18400   }
18401
18402     if(moveCounts)
18403     {   int i = 0, j=move;
18404
18405         /* [HGM] find reversible plies */
18406         if (appData.debugMode) { int k;
18407             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18408             for(k=backwardMostMove; k<=forwardMostMove; k++)
18409                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18410
18411         }
18412
18413         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18414         if( j == backwardMostMove ) i += initialRulePlies;
18415         sprintf(p, "%d ", i);
18416         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18417
18418         /* Fullmove number */
18419         sprintf(p, "%d", (move / 2) + 1);
18420     } else *--p = NULLCHAR;
18421
18422     return StrSave(buf);
18423 }
18424
18425 Boolean
18426 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18427 {
18428     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18429     char *p, c;
18430     int emptycount, virgin[BOARD_FILES];
18431     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18432
18433     p = fen;
18434
18435     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18436
18437     /* Piece placement data */
18438     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18439         j = 0;
18440         for (;;) {
18441             if (*p == '/' || *p == ' ' || *p == '[' ) {
18442                 if(j > w) w = j;
18443                 emptycount = gameInfo.boardWidth - j;
18444                 while (emptycount--)
18445                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18446                 if (*p == '/') p++;
18447                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18448                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18449                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18450                     }
18451                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18452                 }
18453                 break;
18454 #if(BOARD_FILES >= 10)*0
18455             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18456                 p++; emptycount=10;
18457                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18458                 while (emptycount--)
18459                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18460 #endif
18461             } else if (*p == '*') {
18462                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18463             } else if (isdigit(*p)) {
18464                 emptycount = *p++ - '0';
18465                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18466                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18467                 while (emptycount--)
18468                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18469             } else if (*p == '<') {
18470                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18471                 else if (i != 0 || !shuffle) return FALSE;
18472                 p++;
18473             } else if (shuffle && *p == '>') {
18474                 p++; // for now ignore closing shuffle range, and assume rank-end
18475             } else if (*p == '?') {
18476                 if (j >= gameInfo.boardWidth) return FALSE;
18477                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18478                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18479             } else if (*p == '+' || isalpha(*p)) {
18480                 char *q, *s = SUFFIXES;
18481                 if (j >= gameInfo.boardWidth) return FALSE;
18482                 if(*p=='+') {
18483                     char c = *++p;
18484                     if(q = strchr(s, p[1])) p++;
18485                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18486                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18487                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18488                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18489                 } else {
18490                     char c = *p++;
18491                     if(q = strchr(s, *p)) p++;
18492                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18493                 }
18494
18495                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18496                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18497                     piece = (ChessSquare) (PROMOTED(piece));
18498                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18499                     p++;
18500                 }
18501                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18502                 if(piece == king) wKingRank = i;
18503                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18504             } else {
18505                 return FALSE;
18506             }
18507         }
18508     }
18509     while (*p == '/' || *p == ' ') p++;
18510
18511     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18512
18513     /* [HGM] by default clear Crazyhouse holdings, if present */
18514     if(gameInfo.holdingsWidth) {
18515        for(i=0; i<BOARD_HEIGHT; i++) {
18516            board[i][0]             = EmptySquare; /* black holdings */
18517            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18518            board[i][1]             = (ChessSquare) 0; /* black counts */
18519            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18520        }
18521     }
18522
18523     /* [HGM] look for Crazyhouse holdings here */
18524     while(*p==' ') p++;
18525     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18526         int swap=0, wcnt=0, bcnt=0;
18527         if(*p == '[') p++;
18528         if(*p == '<') swap++, p++;
18529         if(*p == '-' ) p++; /* empty holdings */ else {
18530             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18531             /* if we would allow FEN reading to set board size, we would   */
18532             /* have to add holdings and shift the board read so far here   */
18533             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18534                 p++;
18535                 if((int) piece >= (int) BlackPawn ) {
18536                     i = (int)piece - (int)BlackPawn;
18537                     i = PieceToNumber((ChessSquare)i);
18538                     if( i >= gameInfo.holdingsSize ) return FALSE;
18539                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18540                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18541                     bcnt++;
18542                 } else {
18543                     i = (int)piece - (int)WhitePawn;
18544                     i = PieceToNumber((ChessSquare)i);
18545                     if( i >= gameInfo.holdingsSize ) return FALSE;
18546                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18547                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18548                     wcnt++;
18549                 }
18550             }
18551             if(subst) { // substitute back-rank question marks by holdings pieces
18552                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18553                     int k, m, n = bcnt + 1;
18554                     if(board[0][j] == ClearBoard) {
18555                         if(!wcnt) return FALSE;
18556                         n = rand() % wcnt;
18557                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18558                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18559                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18560                             break;
18561                         }
18562                     }
18563                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18564                         if(!bcnt) return FALSE;
18565                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18566                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18567                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18568                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18569                             break;
18570                         }
18571                     }
18572                 }
18573                 subst = 0;
18574             }
18575         }
18576         if(*p == ']') p++;
18577     }
18578
18579     if(subst) return FALSE; // substitution requested, but no holdings
18580
18581     while(*p == ' ') p++;
18582
18583     /* Active color */
18584     c = *p++;
18585     if(appData.colorNickNames) {
18586       if( c == appData.colorNickNames[0] ) c = 'w'; else
18587       if( c == appData.colorNickNames[1] ) c = 'b';
18588     }
18589     switch (c) {
18590       case 'w':
18591         *blackPlaysFirst = FALSE;
18592         break;
18593       case 'b':
18594         *blackPlaysFirst = TRUE;
18595         break;
18596       default:
18597         return FALSE;
18598     }
18599
18600     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18601     /* return the extra info in global variiables             */
18602
18603     while(*p==' ') p++;
18604
18605     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18606         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18607         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18608     }
18609
18610     /* set defaults in case FEN is incomplete */
18611     board[EP_STATUS] = EP_UNKNOWN;
18612     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18613     for(i=0; i<nrCastlingRights; i++ ) {
18614         board[CASTLING][i] =
18615             appData.fischerCastling ? NoRights : initialRights[i];
18616     }   /* assume possible unless obviously impossible */
18617     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18618     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18619     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18620                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18621     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18622     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18623     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18624                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18625     FENrulePlies = 0;
18626
18627     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18628       char *q = p;
18629       int w=0, b=0;
18630       while(isalpha(*p)) {
18631         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18632         if(islower(*p)) b |= 1 << (*p++ - 'a');
18633       }
18634       if(*p == '-') p++;
18635       if(p != q) {
18636         board[TOUCHED_W] = ~w;
18637         board[TOUCHED_B] = ~b;
18638         while(*p == ' ') p++;
18639       }
18640     } else
18641
18642     if(nrCastlingRights) {
18643       int fischer = 0;
18644       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18645       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18646           /* castling indicator present, so default becomes no castlings */
18647           for(i=0; i<nrCastlingRights; i++ ) {
18648                  board[CASTLING][i] = NoRights;
18649           }
18650       }
18651       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18652              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18653              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18654              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18655         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18656
18657         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18658             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18659             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18660         }
18661         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18662             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18663         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18664                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18665         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18666                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18667         switch(c) {
18668           case'K':
18669               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18670               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18671               board[CASTLING][2] = whiteKingFile;
18672               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18673               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18674               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18675               break;
18676           case'Q':
18677               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18678               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18679               board[CASTLING][2] = whiteKingFile;
18680               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18681               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18682               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18683               break;
18684           case'k':
18685               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18686               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18687               board[CASTLING][5] = blackKingFile;
18688               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18689               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18690               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18691               break;
18692           case'q':
18693               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18694               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18695               board[CASTLING][5] = blackKingFile;
18696               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18697               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18698               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18699           case '-':
18700               break;
18701           default: /* FRC castlings */
18702               if(c >= 'a') { /* black rights */
18703                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18704                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18705                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18706                   if(i == BOARD_RGHT) break;
18707                   board[CASTLING][5] = i;
18708                   c -= AAA;
18709                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18710                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18711                   if(c > i)
18712                       board[CASTLING][3] = c;
18713                   else
18714                       board[CASTLING][4] = c;
18715               } else { /* white rights */
18716                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18717                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18718                     if(board[0][i] == WhiteKing) break;
18719                   if(i == BOARD_RGHT) break;
18720                   board[CASTLING][2] = i;
18721                   c -= AAA - 'a' + 'A';
18722                   if(board[0][c] >= WhiteKing) break;
18723                   if(c > i)
18724                       board[CASTLING][0] = c;
18725                   else
18726                       board[CASTLING][1] = c;
18727               }
18728         }
18729       }
18730       for(i=0; i<nrCastlingRights; i++)
18731         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18732       if(gameInfo.variant == VariantSChess)
18733         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18734       if(fischer && shuffle) appData.fischerCastling = TRUE;
18735     if (appData.debugMode) {
18736         fprintf(debugFP, "FEN castling rights:");
18737         for(i=0; i<nrCastlingRights; i++)
18738         fprintf(debugFP, " %d", board[CASTLING][i]);
18739         fprintf(debugFP, "\n");
18740     }
18741
18742       while(*p==' ') p++;
18743     }
18744
18745     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18746
18747     /* read e.p. field in games that know e.p. capture */
18748     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18749        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18750        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18751       if(*p=='-') {
18752         p++; board[EP_STATUS] = EP_NONE;
18753       } else {
18754          char c = *p++ - AAA;
18755
18756          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18757          if(*p >= '0' && *p <='9') p++;
18758          board[EP_STATUS] = c;
18759       }
18760     }
18761
18762
18763     if(sscanf(p, "%d", &i) == 1) {
18764         FENrulePlies = i; /* 50-move ply counter */
18765         /* (The move number is still ignored)    */
18766     }
18767
18768     return TRUE;
18769 }
18770
18771 void
18772 EditPositionPasteFEN (char *fen)
18773 {
18774   if (fen != NULL) {
18775     Board initial_position;
18776
18777     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18778       DisplayError(_("Bad FEN position in clipboard"), 0);
18779       return ;
18780     } else {
18781       int savedBlackPlaysFirst = blackPlaysFirst;
18782       EditPositionEvent();
18783       blackPlaysFirst = savedBlackPlaysFirst;
18784       CopyBoard(boards[0], initial_position);
18785       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18786       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18787       DisplayBothClocks();
18788       DrawPosition(FALSE, boards[currentMove]);
18789     }
18790   }
18791 }
18792
18793 static char cseq[12] = "\\   ";
18794
18795 Boolean
18796 set_cont_sequence (char *new_seq)
18797 {
18798     int len;
18799     Boolean ret;
18800
18801     // handle bad attempts to set the sequence
18802         if (!new_seq)
18803                 return 0; // acceptable error - no debug
18804
18805     len = strlen(new_seq);
18806     ret = (len > 0) && (len < sizeof(cseq));
18807     if (ret)
18808       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18809     else if (appData.debugMode)
18810       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18811     return ret;
18812 }
18813
18814 /*
18815     reformat a source message so words don't cross the width boundary.  internal
18816     newlines are not removed.  returns the wrapped size (no null character unless
18817     included in source message).  If dest is NULL, only calculate the size required
18818     for the dest buffer.  lp argument indicats line position upon entry, and it's
18819     passed back upon exit.
18820 */
18821 int
18822 wrap (char *dest, char *src, int count, int width, int *lp)
18823 {
18824     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18825
18826     cseq_len = strlen(cseq);
18827     old_line = line = *lp;
18828     ansi = len = clen = 0;
18829
18830     for (i=0; i < count; i++)
18831     {
18832         if (src[i] == '\033')
18833             ansi = 1;
18834
18835         // if we hit the width, back up
18836         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18837         {
18838             // store i & len in case the word is too long
18839             old_i = i, old_len = len;
18840
18841             // find the end of the last word
18842             while (i && src[i] != ' ' && src[i] != '\n')
18843             {
18844                 i--;
18845                 len--;
18846             }
18847
18848             // word too long?  restore i & len before splitting it
18849             if ((old_i-i+clen) >= width)
18850             {
18851                 i = old_i;
18852                 len = old_len;
18853             }
18854
18855             // extra space?
18856             if (i && src[i-1] == ' ')
18857                 len--;
18858
18859             if (src[i] != ' ' && src[i] != '\n')
18860             {
18861                 i--;
18862                 if (len)
18863                     len--;
18864             }
18865
18866             // now append the newline and continuation sequence
18867             if (dest)
18868                 dest[len] = '\n';
18869             len++;
18870             if (dest)
18871                 strncpy(dest+len, cseq, cseq_len);
18872             len += cseq_len;
18873             line = cseq_len;
18874             clen = cseq_len;
18875             continue;
18876         }
18877
18878         if (dest)
18879             dest[len] = src[i];
18880         len++;
18881         if (!ansi)
18882             line++;
18883         if (src[i] == '\n')
18884             line = 0;
18885         if (src[i] == 'm')
18886             ansi = 0;
18887     }
18888     if (dest && appData.debugMode)
18889     {
18890         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18891             count, width, line, len, *lp);
18892         show_bytes(debugFP, src, count);
18893         fprintf(debugFP, "\ndest: ");
18894         show_bytes(debugFP, dest, len);
18895         fprintf(debugFP, "\n");
18896     }
18897     *lp = dest ? line : old_line;
18898
18899     return len;
18900 }
18901
18902 // [HGM] vari: routines for shelving variations
18903 Boolean modeRestore = FALSE;
18904
18905 void
18906 PushInner (int firstMove, int lastMove)
18907 {
18908         int i, j, nrMoves = lastMove - firstMove;
18909
18910         // push current tail of game on stack
18911         savedResult[storedGames] = gameInfo.result;
18912         savedDetails[storedGames] = gameInfo.resultDetails;
18913         gameInfo.resultDetails = NULL;
18914         savedFirst[storedGames] = firstMove;
18915         savedLast [storedGames] = lastMove;
18916         savedFramePtr[storedGames] = framePtr;
18917         framePtr -= nrMoves; // reserve space for the boards
18918         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18919             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18920             for(j=0; j<MOVE_LEN; j++)
18921                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18922             for(j=0; j<2*MOVE_LEN; j++)
18923                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18924             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18925             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18926             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18927             pvInfoList[firstMove+i-1].depth = 0;
18928             commentList[framePtr+i] = commentList[firstMove+i];
18929             commentList[firstMove+i] = NULL;
18930         }
18931
18932         storedGames++;
18933         forwardMostMove = firstMove; // truncate game so we can start variation
18934 }
18935
18936 void
18937 PushTail (int firstMove, int lastMove)
18938 {
18939         if(appData.icsActive) { // only in local mode
18940                 forwardMostMove = currentMove; // mimic old ICS behavior
18941                 return;
18942         }
18943         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18944
18945         PushInner(firstMove, lastMove);
18946         if(storedGames == 1) GreyRevert(FALSE);
18947         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18948 }
18949
18950 void
18951 PopInner (Boolean annotate)
18952 {
18953         int i, j, nrMoves;
18954         char buf[8000], moveBuf[20];
18955
18956         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18957         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18958         nrMoves = savedLast[storedGames] - currentMove;
18959         if(annotate) {
18960                 int cnt = 10;
18961                 if(!WhiteOnMove(currentMove))
18962                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18963                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18964                 for(i=currentMove; i<forwardMostMove; i++) {
18965                         if(WhiteOnMove(i))
18966                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18967                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18968                         strcat(buf, moveBuf);
18969                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18970                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18971                 }
18972                 strcat(buf, ")");
18973         }
18974         for(i=1; i<=nrMoves; i++) { // copy last variation back
18975             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18976             for(j=0; j<MOVE_LEN; j++)
18977                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18978             for(j=0; j<2*MOVE_LEN; j++)
18979                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18980             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18981             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18982             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18983             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18984             commentList[currentMove+i] = commentList[framePtr+i];
18985             commentList[framePtr+i] = NULL;
18986         }
18987         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18988         framePtr = savedFramePtr[storedGames];
18989         gameInfo.result = savedResult[storedGames];
18990         if(gameInfo.resultDetails != NULL) {
18991             free(gameInfo.resultDetails);
18992       }
18993         gameInfo.resultDetails = savedDetails[storedGames];
18994         forwardMostMove = currentMove + nrMoves;
18995 }
18996
18997 Boolean
18998 PopTail (Boolean annotate)
18999 {
19000         if(appData.icsActive) return FALSE; // only in local mode
19001         if(!storedGames) return FALSE; // sanity
19002         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19003
19004         PopInner(annotate);
19005         if(currentMove < forwardMostMove) ForwardEvent(); else
19006         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19007
19008         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19009         return TRUE;
19010 }
19011
19012 void
19013 CleanupTail ()
19014 {       // remove all shelved variations
19015         int i;
19016         for(i=0; i<storedGames; i++) {
19017             if(savedDetails[i])
19018                 free(savedDetails[i]);
19019             savedDetails[i] = NULL;
19020         }
19021         for(i=framePtr; i<MAX_MOVES; i++) {
19022                 if(commentList[i]) free(commentList[i]);
19023                 commentList[i] = NULL;
19024         }
19025         framePtr = MAX_MOVES-1;
19026         storedGames = 0;
19027 }
19028
19029 void
19030 LoadVariation (int index, char *text)
19031 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19032         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19033         int level = 0, move;
19034
19035         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19036         // first find outermost bracketing variation
19037         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19038             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19039                 if(*p == '{') wait = '}'; else
19040                 if(*p == '[') wait = ']'; else
19041                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19042                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19043             }
19044             if(*p == wait) wait = NULLCHAR; // closing ]} found
19045             p++;
19046         }
19047         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19048         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19049         end[1] = NULLCHAR; // clip off comment beyond variation
19050         ToNrEvent(currentMove-1);
19051         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19052         // kludge: use ParsePV() to append variation to game
19053         move = currentMove;
19054         ParsePV(start, TRUE, TRUE);
19055         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19056         ClearPremoveHighlights();
19057         CommentPopDown();
19058         ToNrEvent(currentMove+1);
19059 }
19060
19061 int transparency[2];
19062
19063 void
19064 LoadTheme ()
19065 {
19066     char *p, *q, buf[MSG_SIZ];
19067     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19068         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19069         ParseArgsFromString(buf);
19070         ActivateTheme(TRUE); // also redo colors
19071         return;
19072     }
19073     p = nickName;
19074     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19075     {
19076         int len;
19077         q = appData.themeNames;
19078         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19079       if(appData.useBitmaps) {
19080         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19081                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19082                 appData.liteBackTextureMode,
19083                 appData.darkBackTextureMode );
19084       } else {
19085         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false");
19086       }
19087       if(!appData.useBitmaps || transparency[0]) {
19088         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19089       }
19090       if(!appData.useBitmaps || transparency[1]) {
19091         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19092       }
19093       if(appData.useBorder) {
19094         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19095                 appData.border);
19096       } else {
19097         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19098       }
19099       if(appData.useFont) {
19100         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19101                 appData.renderPiecesWithFont,
19102                 appData.fontToPieceTable,
19103                 Col2Text(9),    // appData.fontBackColorWhite
19104                 Col2Text(10) ); // appData.fontForeColorBlack
19105       } else {
19106         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false");
19107         if(appData.pieceDirectory[0]) {
19108           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
19109           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19110             snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19111         }
19112         if(!appData.pieceDirectory[0] && !appData.trueColors)
19113           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19114                 Col2Text(0),   // whitePieceColor
19115                 Col2Text(1) ); // blackPieceColor
19116       }
19117       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19118                 Col2Text(4),   // highlightSquareColor
19119                 Col2Text(5) ); // premoveHighlightColor
19120         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19121         if(insert != q) insert[-1] = NULLCHAR;
19122         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19123         if(q)   free(q);
19124     }
19125     ActivateTheme(FALSE);
19126 }