Fix recognition of variant seirawan in examined ICS games
[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, *currentEngine[2]; // 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         ASSIGN(currentEngine[i], engineLine);
992         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996         appData.firstProtocolVersion = PROTOVER;
997         ParseArgsFromString(buf);
998         SwapEngines(i);
999         ReplaceEngine(cps, i);
1000         FloatToFront(&appData.recentEngineList, engineLine);
1001         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1002         return;
1003     }
1004     p = engineName;
1005     while(q = strchr(p, SLASH)) p = q+1;
1006     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007     if(engineDir[0] != NULLCHAR) {
1008         ASSIGN(appData.directory[i], engineDir); p = engineName;
1009     } else if(p != engineName) { // derive directory from engine path, when not given
1010         p[-1] = 0;
1011         ASSIGN(appData.directory[i], engineName);
1012         p[-1] = SLASH;
1013         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014     } else { ASSIGN(appData.directory[i], "."); }
1015     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016     if(params[0]) {
1017         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018         snprintf(command, MSG_SIZ, "%s %s", p, params);
1019         p = command;
1020     }
1021     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022     ASSIGN(appData.chessProgram[i], p);
1023     appData.isUCI[i] = isUCI;
1024     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025     appData.hasOwnBookUCI[i] = hasBook;
1026     if(!nickName[0]) useNick = FALSE;
1027     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028     if(addToList) {
1029         int len;
1030         char quote;
1031         q = firstChessProgramNames;
1032         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035                         quote, p, quote, appData.directory[i],
1036                         useNick ? " -fn \"" : "",
1037                         useNick ? nickName : "",
1038                         useNick ? "\"" : "",
1039                         v1 ? " -firstProtocolVersion 1" : "",
1040                         hasBook ? "" : " -fNoOwnBookUCI",
1041                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042                         storeVariant ? " -variant " : "",
1043                         storeVariant ? VariantName(gameInfo.variant) : "");
1044         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046         if(insert != q) insert[-1] = NULLCHAR;
1047         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1048         if(q)   free(q);
1049         FloatToFront(&appData.recentEngineList, buf);
1050         ASSIGN(currentEngine[i], buf);
1051     }
1052     ReplaceEngine(cps, i);
1053 }
1054
1055 void
1056 InitTimeControls ()
1057 {
1058     int matched, min, sec;
1059     /*
1060      * Parse timeControl resource
1061      */
1062     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063                           appData.movesPerSession)) {
1064         char buf[MSG_SIZ];
1065         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066         DisplayFatalError(buf, 0, 2);
1067     }
1068
1069     /*
1070      * Parse searchTime resource
1071      */
1072     if (*appData.searchTime != NULLCHAR) {
1073         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1074         if (matched == 1) {
1075             searchTime = min * 60;
1076         } else if (matched == 2) {
1077             searchTime = min * 60 + sec;
1078         } else {
1079             char buf[MSG_SIZ];
1080             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081             DisplayFatalError(buf, 0, 2);
1082         }
1083     }
1084 }
1085
1086 void
1087 InitBackEnd1 ()
1088 {
1089
1090     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1092
1093     GetTimeMark(&programStartTime);
1094     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095     appData.seedBase = random() + (random()<<15);
1096     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1097
1098     ClearProgramStats();
1099     programStats.ok_to_send = 1;
1100     programStats.seen_stat = 0;
1101
1102     /*
1103      * Initialize game list
1104      */
1105     ListNew(&gameList);
1106
1107
1108     /*
1109      * Internet chess server status
1110      */
1111     if (appData.icsActive) {
1112         appData.matchMode = FALSE;
1113         appData.matchGames = 0;
1114 #if ZIPPY
1115         appData.noChessProgram = !appData.zippyPlay;
1116 #else
1117         appData.zippyPlay = FALSE;
1118         appData.zippyTalk = FALSE;
1119         appData.noChessProgram = TRUE;
1120 #endif
1121         if (*appData.icsHelper != NULLCHAR) {
1122             appData.useTelnet = TRUE;
1123             appData.telnetProgram = appData.icsHelper;
1124         }
1125     } else {
1126         appData.zippyTalk = appData.zippyPlay = FALSE;
1127     }
1128
1129     /* [AS] Initialize pv info list [HGM] and game state */
1130     {
1131         int i, j;
1132
1133         for( i=0; i<=framePtr; i++ ) {
1134             pvInfoList[i].depth = -1;
1135             boards[i][EP_STATUS] = EP_NONE;
1136             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137         }
1138     }
1139
1140     InitTimeControls();
1141
1142     /* [AS] Adjudication threshold */
1143     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1144
1145     InitEngine(&first, 0);
1146     InitEngine(&second, 1);
1147     CommonEngineInit();
1148
1149     pairing.which = "pairing"; // pairing engine
1150     pairing.pr = NoProc;
1151     pairing.isr = NULL;
1152     pairing.program = appData.pairingEngine;
1153     pairing.host = "localhost";
1154     pairing.dir = ".";
1155
1156     if (appData.icsActive) {
1157         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1158     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159         appData.clockMode = FALSE;
1160         first.sendTime = second.sendTime = 0;
1161     }
1162
1163 #if ZIPPY
1164     /* Override some settings from environment variables, for backward
1165        compatibility.  Unfortunately it's not feasible to have the env
1166        vars just set defaults, at least in xboard.  Ugh.
1167     */
1168     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169       ZippyInit();
1170     }
1171 #endif
1172
1173     if (!appData.icsActive) {
1174       char buf[MSG_SIZ];
1175       int len;
1176
1177       /* Check for variants that are supported only in ICS mode,
1178          or not at all.  Some that are accepted here nevertheless
1179          have bugs; see comments below.
1180       */
1181       VariantClass variant = StringToVariant(appData.variant);
1182       switch (variant) {
1183       case VariantBughouse:     /* need four players and two boards */
1184       case VariantKriegspiel:   /* need to hide pieces and move details */
1185         /* case VariantFischeRandom: (Fabien: moved below) */
1186         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187         if( (len >= MSG_SIZ) && appData.debugMode )
1188           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1189
1190         DisplayFatalError(buf, 0, 2);
1191         return;
1192
1193       case VariantUnknown:
1194       case VariantLoadable:
1195       case Variant29:
1196       case Variant30:
1197       case Variant31:
1198       case Variant32:
1199       case Variant33:
1200       case Variant34:
1201       case Variant35:
1202       case Variant36:
1203       default:
1204         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantNormal:     /* definitely works! */
1212         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214           return;
1215         }
1216       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1217       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1218       case VariantGothic:     /* [HGM] should work */
1219       case VariantCapablanca: /* [HGM] should work */
1220       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1221       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1222       case VariantChu:        /* [HGM] experimental */
1223       case VariantKnightmate: /* [HGM] should work */
1224       case VariantCylinder:   /* [HGM] untested */
1225       case VariantFalcon:     /* [HGM] untested */
1226       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227                                  offboard interposition not understood */
1228       case VariantWildCastle: /* pieces not automatically shuffled */
1229       case VariantNoCastle:   /* pieces not automatically shuffled */
1230       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231       case VariantLosers:     /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantSuicide:    /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantGiveaway:   /* should work except for win condition,
1236                                  and doesn't know captures are mandatory */
1237       case VariantTwoKings:   /* should work */
1238       case VariantAtomic:     /* should work except for win condition */
1239       case Variant3Check:     /* should work except for win condition */
1240       case VariantShatranj:   /* should work except for all win conditions */
1241       case VariantMakruk:     /* should work except for draw countdown */
1242       case VariantASEAN :     /* should work except for draw countdown */
1243       case VariantBerolina:   /* might work if TestLegality is off */
1244       case VariantCapaRandom: /* should work */
1245       case VariantJanus:      /* should work */
1246       case VariantSuper:      /* experimental */
1247       case VariantGreat:      /* experimental, requires legality testing to be off */
1248       case VariantSChess:     /* S-Chess, should work */
1249       case VariantGrand:      /* should work */
1250       case VariantSpartan:    /* should work */
1251       case VariantLion:       /* should work */
1252       case VariantChuChess:   /* should work */
1253         break;
1254       }
1255     }
1256
1257 }
1258
1259 int
1260 NextIntegerFromString (char ** str, long * value)
1261 {
1262     int result = -1;
1263     char * s = *str;
1264
1265     while( *s == ' ' || *s == '\t' ) {
1266         s++;
1267     }
1268
1269     *value = 0;
1270
1271     if( *s >= '0' && *s <= '9' ) {
1272         while( *s >= '0' && *s <= '9' ) {
1273             *value = *value * 10 + (*s - '0');
1274             s++;
1275         }
1276
1277         result = 0;
1278     }
1279
1280     *str = s;
1281
1282     return result;
1283 }
1284
1285 int
1286 NextTimeControlFromString (char ** str, long * value)
1287 {
1288     long temp;
1289     int result = NextIntegerFromString( str, &temp );
1290
1291     if( result == 0 ) {
1292         *value = temp * 60; /* Minutes */
1293         if( **str == ':' ) {
1294             (*str)++;
1295             result = NextIntegerFromString( str, &temp );
1296             *value += temp; /* Seconds */
1297         }
1298     }
1299
1300     return result;
1301 }
1302
1303 int
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306     int result = -1, type = 0; long temp, temp2;
1307
1308     if(**str != ':') return -1; // old params remain in force!
1309     (*str)++;
1310     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311     if( NextIntegerFromString( str, &temp ) ) return -1;
1312     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313
1314     if(**str != '/') {
1315         /* time only: incremental or sudden-death time control */
1316         if(**str == '+') { /* increment follows; read it */
1317             (*str)++;
1318             if(**str == '!') type = *(*str)++; // Bronstein TC
1319             if(result = NextIntegerFromString( str, &temp2)) return -1;
1320             *inc = temp2 * 1000;
1321             if(**str == '.') { // read fraction of increment
1322                 char *start = ++(*str);
1323                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1324                 temp2 *= 1000;
1325                 while(start++ < *str) temp2 /= 10;
1326                 *inc += temp2;
1327             }
1328         } else *inc = 0;
1329         *moves = 0; *tc = temp * 1000; *incType = type;
1330         return 0;
1331     }
1332
1333     (*str)++; /* classical time control */
1334     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335
1336     if(result == 0) {
1337         *moves = temp;
1338         *tc    = temp2 * 1000;
1339         *inc   = 0;
1340         *incType = type;
1341     }
1342     return result;
1343 }
1344
1345 int
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 {   /* [HGM] get time to add from the multi-session time-control string */
1348     int incType, moves=1; /* kludge to force reading of first session */
1349     long time, increment;
1350     char *s = tcString;
1351
1352     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1353     do {
1354         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356         if(movenr == -1) return time;    /* last move before new session     */
1357         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359         if(!moves) return increment;     /* current session is incremental   */
1360         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361     } while(movenr >= -1);               /* try again for next session       */
1362
1363     return 0; // no new time quota on this move
1364 }
1365
1366 int
1367 ParseTimeControl (char *tc, float ti, int mps)
1368 {
1369   long tc1;
1370   long tc2;
1371   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372   int min, sec=0;
1373
1374   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377   if(ti > 0) {
1378
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383   } else {
1384     if(mps)
1385       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1386     else
1387       snprintf(buf, MSG_SIZ, ":%s", mytc);
1388   }
1389   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1390
1391   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392     return FALSE;
1393   }
1394
1395   if( *tc == '/' ) {
1396     /* Parse second time control */
1397     tc++;
1398
1399     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1400       return FALSE;
1401     }
1402
1403     if( tc2 == 0 ) {
1404       return FALSE;
1405     }
1406
1407     timeControl_2 = tc2 * 1000;
1408   }
1409   else {
1410     timeControl_2 = 0;
1411   }
1412
1413   if( tc1 == 0 ) {
1414     return FALSE;
1415   }
1416
1417   timeControl = tc1 * 1000;
1418
1419   if (ti >= 0) {
1420     timeIncrement = ti * 1000;  /* convert to ms */
1421     movesPerSession = 0;
1422   } else {
1423     timeIncrement = 0;
1424     movesPerSession = mps;
1425   }
1426   return TRUE;
1427 }
1428
1429 void
1430 InitBackEnd2 ()
1431 {
1432     if (appData.debugMode) {
1433 #    ifdef __GIT_VERSION
1434       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1435 #    else
1436       fprintf(debugFP, "Version: %s\n", programVersion);
1437 #    endif
1438     }
1439     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1440
1441     set_cont_sequence(appData.wrapContSeq);
1442     if (appData.matchGames > 0) {
1443         appData.matchMode = TRUE;
1444     } else if (appData.matchMode) {
1445         appData.matchGames = 1;
1446     }
1447     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448         appData.matchGames = appData.sameColorGames;
1449     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452     }
1453     Reset(TRUE, FALSE);
1454     if (appData.noChessProgram || first.protocolVersion == 1) {
1455       InitBackEnd3();
1456     } else {
1457       /* kludge: allow timeout for initial "feature" commands */
1458       FreezeUI();
1459       DisplayMessage("", _("Starting chess program"));
1460       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461     }
1462 }
1463
1464 int
1465 CalculateIndex (int index, int gameNr)
1466 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1467     int res;
1468     if(index > 0) return index; // fixed nmber
1469     if(index == 0) return 1;
1470     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472     return res;
1473 }
1474
1475 int
1476 LoadGameOrPosition (int gameNr)
1477 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478     if (*appData.loadGameFile != NULLCHAR) {
1479         if (!LoadGameFromFile(appData.loadGameFile,
1480                 CalculateIndex(appData.loadGameIndex, gameNr),
1481                               appData.loadGameFile, FALSE)) {
1482             DisplayFatalError(_("Bad game file"), 0, 1);
1483             return 0;
1484         }
1485     } else if (*appData.loadPositionFile != NULLCHAR) {
1486         if (!LoadPositionFromFile(appData.loadPositionFile,
1487                 CalculateIndex(appData.loadPositionIndex, gameNr),
1488                                   appData.loadPositionFile)) {
1489             DisplayFatalError(_("Bad position file"), 0, 1);
1490             return 0;
1491         }
1492     }
1493     return 1;
1494 }
1495
1496 void
1497 ReserveGame (int gameNr, char resChar)
1498 {
1499     FILE *tf = fopen(appData.tourneyFile, "r+");
1500     char *p, *q, c, buf[MSG_SIZ];
1501     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502     safeStrCpy(buf, lastMsg, MSG_SIZ);
1503     DisplayMessage(_("Pick new game"), "");
1504     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505     ParseArgsFromFile(tf);
1506     p = q = appData.results;
1507     if(appData.debugMode) {
1508       char *r = appData.participants;
1509       fprintf(debugFP, "results = '%s'\n", p);
1510       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511       fprintf(debugFP, "\n");
1512     }
1513     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1514     nextGame = q - p;
1515     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516     safeStrCpy(q, p, strlen(p) + 2);
1517     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521         q[nextGame] = '*';
1522     }
1523     fseek(tf, -(strlen(p)+4), SEEK_END);
1524     c = fgetc(tf);
1525     if(c != '"') // depending on DOS or Unix line endings we can be one off
1526          fseek(tf, -(strlen(p)+2), SEEK_END);
1527     else fseek(tf, -(strlen(p)+3), SEEK_END);
1528     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529     DisplayMessage(buf, "");
1530     free(p); appData.results = q;
1531     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533       int round = appData.defaultMatchGames * appData.tourneyType;
1534       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1535          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536         UnloadEngine(&first);  // next game belongs to other pairing;
1537         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1538     }
1539     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 }
1541
1542 void
1543 MatchEvent (int mode)
1544 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1545         int dummy;
1546         if(matchMode) { // already in match mode: switch it off
1547             abortMatch = TRUE;
1548             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549             return;
1550         }
1551 //      if(gameMode != BeginningOfGame) {
1552 //          DisplayError(_("You can only start a match from the initial position."), 0);
1553 //          return;
1554 //      }
1555         abortMatch = FALSE;
1556         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557         /* Set up machine vs. machine match */
1558         nextGame = 0;
1559         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560         if(appData.tourneyFile[0]) {
1561             ReserveGame(-1, 0);
1562             if(nextGame > appData.matchGames) {
1563                 char buf[MSG_SIZ];
1564                 if(strchr(appData.results, '*') == NULL) {
1565                     FILE *f;
1566                     appData.tourneyCycles++;
1567                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1568                         fclose(f);
1569                         NextTourneyGame(-1, &dummy);
1570                         ReserveGame(-1, 0);
1571                         if(nextGame <= appData.matchGames) {
1572                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1573                             matchMode = mode;
1574                             ScheduleDelayedEvent(NextMatchGame, 10000);
1575                             return;
1576                         }
1577                     }
1578                 }
1579                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580                 DisplayError(buf, 0);
1581                 appData.tourneyFile[0] = 0;
1582                 return;
1583             }
1584         } else
1585         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1586             DisplayFatalError(_("Can't have a match with no chess programs"),
1587                               0, 2);
1588             return;
1589         }
1590         matchMode = mode;
1591         matchGame = roundNr = 1;
1592         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593         NextMatchGame();
1594 }
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     ParseFeatures(appData.features[0], &first);
1604     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1605        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1606         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1607        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1608        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609         char c, *q = first.variants, *p = strchr(q, ',');
1610         if(p) *p = NULLCHAR;
1611         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612             int w, h, s;
1613             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616             Reset(TRUE, FALSE);         // and re-initialize
1617         }
1618         if(p) *p = ',';
1619     }
1620
1621     InitChessProgram(&first, startedFromSetupPosition);
1622
1623     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1624         free(programVersion);
1625         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1628     }
1629
1630     if (appData.icsActive) {
1631 #ifdef WIN32
1632         /* [DM] Make a console window if needed [HGM] merged ifs */
1633         ConsoleCreate();
1634 #endif
1635         err = establish();
1636         if (err != 0)
1637           {
1638             if (*appData.icsCommPort != NULLCHAR)
1639               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640                              appData.icsCommPort);
1641             else
1642               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643                         appData.icsHost, appData.icsPort);
1644
1645             if( (len >= MSG_SIZ) && appData.debugMode )
1646               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648             DisplayFatalError(buf, err, 1);
1649             return;
1650         }
1651         SetICSMode();
1652         telnetISR =
1653           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654         fromUserISR =
1655           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658     } else if (appData.noChessProgram) {
1659         SetNCPMode();
1660     } else {
1661         SetGNUMode();
1662     }
1663
1664     if (*appData.cmailGameName != NULLCHAR) {
1665         SetCmailMode();
1666         OpenLoopback(&cmailPR);
1667         cmailISR =
1668           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669     }
1670
1671     ThawUI();
1672     DisplayMessage("", "");
1673     if (StrCaseCmp(appData.initialMode, "") == 0) {
1674       initialMode = BeginningOfGame;
1675       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679         ModeHighlight();
1680       }
1681     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682       initialMode = TwoMachinesPlay;
1683     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684       initialMode = AnalyzeFile;
1685     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686       initialMode = AnalyzeMode;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688       initialMode = MachinePlaysWhite;
1689     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690       initialMode = MachinePlaysBlack;
1691     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692       initialMode = EditGame;
1693     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694       initialMode = EditPosition;
1695     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696       initialMode = Training;
1697     } else {
1698       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699       if( (len >= MSG_SIZ) && appData.debugMode )
1700         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701
1702       DisplayFatalError(buf, 0, 2);
1703       return;
1704     }
1705
1706     if (appData.matchMode) {
1707         if(appData.tourneyFile[0]) { // start tourney from command line
1708             FILE *f;
1709             if(f = fopen(appData.tourneyFile, "r")) {
1710                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711                 fclose(f);
1712                 appData.clockMode = TRUE;
1713                 SetGNUMode();
1714             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715         }
1716         MatchEvent(TRUE);
1717     } else if (*appData.cmailGameName != NULLCHAR) {
1718         /* Set up cmail mode */
1719         ReloadCmailMsgEvent(TRUE);
1720     } else {
1721         /* Set up other modes */
1722         if (initialMode == AnalyzeFile) {
1723           if (*appData.loadGameFile == NULLCHAR) {
1724             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725             return;
1726           }
1727         }
1728         if (*appData.loadGameFile != NULLCHAR) {
1729             (void) LoadGameFromFile(appData.loadGameFile,
1730                                     appData.loadGameIndex,
1731                                     appData.loadGameFile, TRUE);
1732         } else if (*appData.loadPositionFile != NULLCHAR) {
1733             (void) LoadPositionFromFile(appData.loadPositionFile,
1734                                         appData.loadPositionIndex,
1735                                         appData.loadPositionFile);
1736             /* [HGM] try to make self-starting even after FEN load */
1737             /* to allow automatic setup of fairy variants with wtm */
1738             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739                 gameMode = BeginningOfGame;
1740                 setboardSpoiledMachineBlack = 1;
1741             }
1742             /* [HGM] loadPos: make that every new game uses the setup */
1743             /* from file as long as we do not switch variant          */
1744             if(!blackPlaysFirst) {
1745                 startedFromPositionFile = TRUE;
1746                 CopyBoard(filePosition, boards[0]);
1747                 CopyBoard(initialPosition, boards[0]);
1748             }
1749         } else if(*appData.fen != NULLCHAR) {
1750             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751                 startedFromPositionFile = TRUE;
1752                 Reset(TRUE, TRUE);
1753             }
1754         }
1755         if (initialMode == AnalyzeMode) {
1756           if (appData.noChessProgram) {
1757             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762             return;
1763           }
1764           AnalyzeModeEvent();
1765         } else if (initialMode == AnalyzeFile) {
1766           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767           ShowThinkingEvent();
1768           AnalyzeFileEvent();
1769           AnalysisPeriodicEvent(1);
1770         } else if (initialMode == MachinePlaysWhite) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineWhiteEvent();
1782         } else if (initialMode == MachinePlaysBlack) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           MachineBlackEvent();
1794         } else if (initialMode == TwoMachinesPlay) {
1795           if (appData.noChessProgram) {
1796             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797                               0, 2);
1798             return;
1799           }
1800           if (appData.icsActive) {
1801             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1802                               0, 2);
1803             return;
1804           }
1805           TwoMachinesEvent();
1806         } else if (initialMode == EditGame) {
1807           EditGameEvent();
1808         } else if (initialMode == EditPosition) {
1809           EditPositionEvent();
1810         } else if (initialMode == Training) {
1811           if (*appData.loadGameFile == NULLCHAR) {
1812             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1813             return;
1814           }
1815           TrainingEvent();
1816         }
1817     }
1818 }
1819
1820 void
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 {
1823     DisplayBook(current+1);
1824
1825     MoveHistorySet( movelist, first, last, current, pvInfoList );
1826
1827     EvalGraphSet( first, last, current, pvInfoList );
1828
1829     MakeEngineOutputTitle();
1830 }
1831
1832 /*
1833  * Establish will establish a contact to a remote host.port.
1834  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835  *  used to talk to the host.
1836  * Returns 0 if okay, error code if not.
1837  */
1838 int
1839 establish ()
1840 {
1841     char buf[MSG_SIZ];
1842
1843     if (*appData.icsCommPort != NULLCHAR) {
1844         /* Talk to the host through a serial comm port */
1845         return OpenCommPort(appData.icsCommPort, &icsPR);
1846
1847     } else if (*appData.gateway != NULLCHAR) {
1848         if (*appData.remoteShell == NULLCHAR) {
1849             /* Use the rcmd protocol to run telnet program on a gateway host */
1850             snprintf(buf, sizeof(buf), "%s %s %s",
1851                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1852             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853
1854         } else {
1855             /* Use the rsh program to run telnet program on a gateway host */
1856             if (*appData.remoteUser == NULLCHAR) {
1857                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858                         appData.gateway, appData.telnetProgram,
1859                         appData.icsHost, appData.icsPort);
1860             } else {
1861                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862                         appData.remoteShell, appData.gateway,
1863                         appData.remoteUser, appData.telnetProgram,
1864                         appData.icsHost, appData.icsPort);
1865             }
1866             return StartChildProcess(buf, "", &icsPR);
1867
1868         }
1869     } else if (appData.useTelnet) {
1870         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871
1872     } else {
1873         /* TCP socket interface differs somewhat between
1874            Unix and NT; handle details in the front end.
1875            */
1876         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1877     }
1878 }
1879
1880 void
1881 EscapeExpand (char *p, char *q)
1882 {       // [HGM] initstring: routine to shape up string arguments
1883         while(*p++ = *q++) if(p[-1] == '\\')
1884             switch(*q++) {
1885                 case 'n': p[-1] = '\n'; break;
1886                 case 'r': p[-1] = '\r'; break;
1887                 case 't': p[-1] = '\t'; break;
1888                 case '\\': p[-1] = '\\'; break;
1889                 case 0: *p = 0; return;
1890                 default: p[-1] = q[-1]; break;
1891             }
1892 }
1893
1894 void
1895 show_bytes (FILE *fp, char *buf, int count)
1896 {
1897     while (count--) {
1898         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899             fprintf(fp, "\\%03o", *buf & 0xff);
1900         } else {
1901             putc(*buf, fp);
1902         }
1903         buf++;
1904     }
1905     fflush(fp);
1906 }
1907
1908 /* Returns an errno value */
1909 int
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 {
1912     char buf[8192], *p, *q, *buflim;
1913     int left, newcount, outcount;
1914
1915     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916         *appData.gateway != NULLCHAR) {
1917         if (appData.debugMode) {
1918             fprintf(debugFP, ">ICS: ");
1919             show_bytes(debugFP, message, count);
1920             fprintf(debugFP, "\n");
1921         }
1922         return OutputToProcess(pr, message, count, outError);
1923     }
1924
1925     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1926     p = message;
1927     q = buf;
1928     left = count;
1929     newcount = 0;
1930     while (left) {
1931         if (q >= buflim) {
1932             if (appData.debugMode) {
1933                 fprintf(debugFP, ">ICS: ");
1934                 show_bytes(debugFP, buf, newcount);
1935                 fprintf(debugFP, "\n");
1936             }
1937             outcount = OutputToProcess(pr, buf, newcount, outError);
1938             if (outcount < newcount) return -1; /* to be sure */
1939             q = buf;
1940             newcount = 0;
1941         }
1942         if (*p == '\n') {
1943             *q++ = '\r';
1944             newcount++;
1945         } else if (((unsigned char) *p) == TN_IAC) {
1946             *q++ = (char) TN_IAC;
1947             newcount ++;
1948         }
1949         *q++ = *p++;
1950         newcount++;
1951         left--;
1952     }
1953     if (appData.debugMode) {
1954         fprintf(debugFP, ">ICS: ");
1955         show_bytes(debugFP, buf, newcount);
1956         fprintf(debugFP, "\n");
1957     }
1958     outcount = OutputToProcess(pr, buf, newcount, outError);
1959     if (outcount < newcount) return -1; /* to be sure */
1960     return count;
1961 }
1962
1963 void
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 {
1966     int outError, outCount;
1967     static int gotEof = 0;
1968     static FILE *ini;
1969
1970     /* Pass data read from player on to ICS */
1971     if (count > 0) {
1972         gotEof = 0;
1973         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974         if (outCount < count) {
1975             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976         }
1977         if(have_sent_ICS_logon == 2) {
1978           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979             fprintf(ini, "%s", message);
1980             have_sent_ICS_logon = 3;
1981           } else
1982             have_sent_ICS_logon = 1;
1983         } else if(have_sent_ICS_logon == 3) {
1984             fprintf(ini, "%s", message);
1985             fclose(ini);
1986           have_sent_ICS_logon = 1;
1987         }
1988     } else if (count < 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991     } else if (gotEof++ > 0) {
1992         RemoveInputSource(isr);
1993         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1994     }
1995 }
1996
1997 void
1998 KeepAlive ()
1999 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002     SendToICS("date\n");
2003     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 }
2005
2006 /* added routine for printf style output to ics */
2007 void
2008 ics_printf (char *format, ...)
2009 {
2010     char buffer[MSG_SIZ];
2011     va_list args;
2012
2013     va_start(args, format);
2014     vsnprintf(buffer, sizeof(buffer), format, args);
2015     buffer[sizeof(buffer)-1] = '\0';
2016     SendToICS(buffer);
2017     va_end(args);
2018 }
2019
2020 void
2021 SendToICS (char *s)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034 /* This is used for sending logon scripts to the ICS. Sending
2035    without a delay causes problems when using timestamp on ICC
2036    (at least on my machine). */
2037 void
2038 SendToICSDelayed (char *s, long msdelay)
2039 {
2040     int count, outCount, outError;
2041
2042     if (icsPR == NoProc) return;
2043
2044     count = strlen(s);
2045     if (appData.debugMode) {
2046         fprintf(debugFP, ">ICS: ");
2047         show_bytes(debugFP, s, count);
2048         fprintf(debugFP, "\n");
2049     }
2050     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051                                       msdelay);
2052     if (outCount < count) {
2053         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2054     }
2055 }
2056
2057
2058 /* Remove all highlighting escape sequences in s
2059    Also deletes any suffix starting with '('
2060    */
2061 char *
2062 StripHighlightAndTitle (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             if (*s == '(' || *s == '[') {
2074                 *p = NULLCHAR;
2075                 return retbuf;
2076             }
2077             *p++ = *s++;
2078         }
2079     }
2080     *p = NULLCHAR;
2081     return retbuf;
2082 }
2083
2084 /* Remove all highlighting escape sequences in s */
2085 char *
2086 StripHighlight (char *s)
2087 {
2088     static char retbuf[MSG_SIZ];
2089     char *p = retbuf;
2090
2091     while (*s != NULLCHAR) {
2092         while (*s == '\033') {
2093             while (*s != NULLCHAR && !isalpha(*s)) s++;
2094             if (*s != NULLCHAR) s++;
2095         }
2096         while (*s != NULLCHAR && *s != '\033') {
2097             *p++ = *s++;
2098         }
2099     }
2100     *p = NULLCHAR;
2101     return retbuf;
2102 }
2103
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2106 char *
2107 VariantName (VariantClass v)
2108 {
2109     if(v == VariantUnknown || *engineVariant) return engineVariant;
2110     return variantNames[v];
2111 }
2112
2113
2114 /* Identify a variant from the strings the chess servers use or the
2115    PGN Variant tag names we use. */
2116 VariantClass
2117 StringToVariant (char *e)
2118 {
2119     char *p;
2120     int wnum = -1;
2121     VariantClass v = VariantNormal;
2122     int i, found = FALSE;
2123     char buf[MSG_SIZ], c;
2124     int len;
2125
2126     if (!e) return v;
2127
2128     /* [HGM] skip over optional board-size prefixes */
2129     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131         while( *e++ != '_');
2132     }
2133
2134     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135         v = VariantNormal;
2136         found = TRUE;
2137     } else
2138     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139       if (p = StrCaseStr(e, variantNames[i])) {
2140         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141         v = (VariantClass) i;
2142         found = TRUE;
2143         break;
2144       }
2145     }
2146
2147     if (!found) {
2148       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149           || StrCaseStr(e, "wild/fr")
2150           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151         v = VariantFischeRandom;
2152       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153                  (i = 1, p = StrCaseStr(e, "w"))) {
2154         p += i;
2155         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2156         if (isdigit(*p)) {
2157           wnum = atoi(p);
2158         } else {
2159           wnum = -1;
2160         }
2161         switch (wnum) {
2162         case 0: /* FICS only, actually */
2163         case 1:
2164           /* Castling legal even if K starts on d-file */
2165           v = VariantWildCastle;
2166           break;
2167         case 2:
2168         case 3:
2169         case 4:
2170           /* Castling illegal even if K & R happen to start in
2171              normal positions. */
2172           v = VariantNoCastle;
2173           break;
2174         case 5:
2175         case 7:
2176         case 8:
2177         case 10:
2178         case 11:
2179         case 12:
2180         case 13:
2181         case 14:
2182         case 15:
2183         case 18:
2184         case 19:
2185           /* Castling legal iff K & R start in normal positions */
2186           v = VariantNormal;
2187           break;
2188         case 6:
2189         case 20:
2190         case 21:
2191           /* Special wilds for position setup; unclear what to do here */
2192           v = VariantLoadable;
2193           break;
2194         case 9:
2195           /* Bizarre ICC game */
2196           v = VariantTwoKings;
2197           break;
2198         case 16:
2199           v = VariantKriegspiel;
2200           break;
2201         case 17:
2202           v = VariantLosers;
2203           break;
2204         case 22:
2205           v = VariantFischeRandom;
2206           break;
2207         case 23:
2208           v = VariantCrazyhouse;
2209           break;
2210         case 24:
2211           v = VariantBughouse;
2212           break;
2213         case 25:
2214           v = Variant3Check;
2215           break;
2216         case 26:
2217           /* Not quite the same as FICS suicide! */
2218           v = VariantGiveaway;
2219           break;
2220         case 27:
2221           v = VariantAtomic;
2222           break;
2223         case 28:
2224           v = VariantShatranj;
2225           break;
2226
2227         /* Temporary names for future ICC types.  The name *will* change in
2228            the next xboard/WinBoard release after ICC defines it. */
2229         case 29:
2230           v = Variant29;
2231           break;
2232         case 30:
2233           v = Variant30;
2234           break;
2235         case 31:
2236           v = Variant31;
2237           break;
2238         case 32:
2239           v = Variant32;
2240           break;
2241         case 33:
2242           v = Variant33;
2243           break;
2244         case 34:
2245           v = Variant34;
2246           break;
2247         case 35:
2248           v = Variant35;
2249           break;
2250         case 36:
2251           v = Variant36;
2252           break;
2253         case 37:
2254           v = VariantShogi;
2255           break;
2256         case 38:
2257           v = VariantXiangqi;
2258           break;
2259         case 39:
2260           v = VariantCourier;
2261           break;
2262         case 40:
2263           v = VariantGothic;
2264           break;
2265         case 41:
2266           v = VariantCapablanca;
2267           break;
2268         case 42:
2269           v = VariantKnightmate;
2270           break;
2271         case 43:
2272           v = VariantFairy;
2273           break;
2274         case 44:
2275           v = VariantCylinder;
2276           break;
2277         case 45:
2278           v = VariantFalcon;
2279           break;
2280         case 46:
2281           v = VariantCapaRandom;
2282           break;
2283         case 47:
2284           v = VariantBerolina;
2285           break;
2286         case 48:
2287           v = VariantJanus;
2288           break;
2289         case 49:
2290           v = VariantSuper;
2291           break;
2292         case 50:
2293           v = VariantGreat;
2294           break;
2295         case -1:
2296           /* Found "wild" or "w" in the string but no number;
2297              must assume it's normal chess. */
2298           v = VariantNormal;
2299           break;
2300         default:
2301           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302           if( (len >= MSG_SIZ) && appData.debugMode )
2303             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304
2305           DisplayError(buf, 0);
2306           v = VariantUnknown;
2307           break;
2308         }
2309       }
2310     }
2311     if (appData.debugMode) {
2312       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313               e, wnum, VariantName(v));
2314     }
2315     return v;
2316 }
2317
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2320
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322    advance *index beyond it, and set leftover_start to the new value of
2323    *index; else return FALSE.  If pattern contains the character '*', it
2324    matches any sequence of characters not containing '\r', '\n', or the
2325    character following the '*' (if any), and the matched sequence(s) are
2326    copied into star_match.
2327    */
2328 int
2329 looking_at ( char *buf, int *index, char *pattern)
2330 {
2331     char *bufp = &buf[*index], *patternp = pattern;
2332     int star_count = 0;
2333     char *matchp = star_match[0];
2334
2335     for (;;) {
2336         if (*patternp == NULLCHAR) {
2337             *index = leftover_start = bufp - buf;
2338             *matchp = NULLCHAR;
2339             return TRUE;
2340         }
2341         if (*bufp == NULLCHAR) return FALSE;
2342         if (*patternp == '*') {
2343             if (*bufp == *(patternp + 1)) {
2344                 *matchp = NULLCHAR;
2345                 matchp = star_match[++star_count];
2346                 patternp += 2;
2347                 bufp++;
2348                 continue;
2349             } else if (*bufp == '\n' || *bufp == '\r') {
2350                 patternp++;
2351                 if (*patternp == NULLCHAR)
2352                   continue;
2353                 else
2354                   return FALSE;
2355             } else {
2356                 *matchp++ = *bufp++;
2357                 continue;
2358             }
2359         }
2360         if (*patternp != *bufp) return FALSE;
2361         patternp++;
2362         bufp++;
2363     }
2364 }
2365
2366 void
2367 SendToPlayer (char *data, int length)
2368 {
2369     int error, outCount;
2370     outCount = OutputToProcess(NoProc, data, length, &error);
2371     if (outCount < length) {
2372         DisplayFatalError(_("Error writing to display"), error, 1);
2373     }
2374 }
2375
2376 void
2377 PackHolding (char packed[], char *holding)
2378 {
2379     char *p = holding;
2380     char *q = packed;
2381     int runlength = 0;
2382     int curr = 9999;
2383     do {
2384         if (*p == curr) {
2385             runlength++;
2386         } else {
2387             switch (runlength) {
2388               case 0:
2389                 break;
2390               case 1:
2391                 *q++ = curr;
2392                 break;
2393               case 2:
2394                 *q++ = curr;
2395                 *q++ = curr;
2396                 break;
2397               default:
2398                 sprintf(q, "%d", runlength);
2399                 while (*q) q++;
2400                 *q++ = curr;
2401                 break;
2402             }
2403             runlength = 1;
2404             curr = *p;
2405         }
2406     } while (*p++);
2407     *q = NULLCHAR;
2408 }
2409
2410 /* Telnet protocol requests from the front end */
2411 void
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2413 {
2414     unsigned char msg[3];
2415     int outCount, outError;
2416
2417     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418
2419     if (appData.debugMode) {
2420         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2421         switch (ddww) {
2422           case TN_DO:
2423             ddwwStr = "DO";
2424             break;
2425           case TN_DONT:
2426             ddwwStr = "DONT";
2427             break;
2428           case TN_WILL:
2429             ddwwStr = "WILL";
2430             break;
2431           case TN_WONT:
2432             ddwwStr = "WONT";
2433             break;
2434           default:
2435             ddwwStr = buf1;
2436             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2437             break;
2438         }
2439         switch (option) {
2440           case TN_ECHO:
2441             optionStr = "ECHO";
2442             break;
2443           default:
2444             optionStr = buf2;
2445             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446             break;
2447         }
2448         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2449     }
2450     msg[0] = TN_IAC;
2451     msg[1] = ddww;
2452     msg[2] = option;
2453     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454     if (outCount < 3) {
2455         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2456     }
2457 }
2458
2459 void
2460 DoEcho ()
2461 {
2462     if (!appData.icsActive) return;
2463     TelnetRequest(TN_DO, TN_ECHO);
2464 }
2465
2466 void
2467 DontEcho ()
2468 {
2469     if (!appData.icsActive) return;
2470     TelnetRequest(TN_DONT, TN_ECHO);
2471 }
2472
2473 void
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 {
2476     /* put the holdings sent to us by the server on the board holdings area */
2477     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478     char p;
2479     ChessSquare piece;
2480
2481     if(gameInfo.holdingsWidth < 2)  return;
2482     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483         return; // prevent overwriting by pre-board holdings
2484
2485     if( (int)lowestPiece >= BlackPawn ) {
2486         holdingsColumn = 0;
2487         countsColumn = 1;
2488         holdingsStartRow = BOARD_HEIGHT-1;
2489         direction = -1;
2490     } else {
2491         holdingsColumn = BOARD_WIDTH-1;
2492         countsColumn = BOARD_WIDTH-2;
2493         holdingsStartRow = 0;
2494         direction = 1;
2495     }
2496
2497     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2498         board[i][holdingsColumn] = EmptySquare;
2499         board[i][countsColumn]   = (ChessSquare) 0;
2500     }
2501     while( (p=*holdings++) != NULLCHAR ) {
2502         piece = CharToPiece( ToUpper(p) );
2503         if(piece == EmptySquare) continue;
2504         /*j = (int) piece - (int) WhitePawn;*/
2505         j = PieceToNumber(piece);
2506         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507         if(j < 0) continue;               /* should not happen */
2508         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510         board[holdingsStartRow+j*direction][countsColumn]++;
2511     }
2512 }
2513
2514
2515 void
2516 VariantSwitch (Board board, VariantClass newVariant)
2517 {
2518    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519    static Board oldBoard;
2520
2521    startedFromPositionFile = FALSE;
2522    if(gameInfo.variant == newVariant) return;
2523
2524    /* [HGM] This routine is called each time an assignment is made to
2525     * gameInfo.variant during a game, to make sure the board sizes
2526     * are set to match the new variant. If that means adding or deleting
2527     * holdings, we shift the playing board accordingly
2528     * This kludge is needed because in ICS observe mode, we get boards
2529     * of an ongoing game without knowing the variant, and learn about the
2530     * latter only later. This can be because of the move list we requested,
2531     * in which case the game history is refilled from the beginning anyway,
2532     * but also when receiving holdings of a crazyhouse game. In the latter
2533     * case we want to add those holdings to the already received position.
2534     */
2535
2536
2537    if (appData.debugMode) {
2538      fprintf(debugFP, "Switch board from %s to %s\n",
2539              VariantName(gameInfo.variant), VariantName(newVariant));
2540      setbuf(debugFP, NULL);
2541    }
2542    shuffleOpenings = 0;       /* [HGM] shuffle */
2543    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544    switch(newVariant)
2545      {
2546      case VariantShogi:
2547        newWidth = 9;  newHeight = 9;
2548        gameInfo.holdingsSize = 7;
2549      case VariantBughouse:
2550      case VariantCrazyhouse:
2551        newHoldingsWidth = 2; break;
2552      case VariantGreat:
2553        newWidth = 10;
2554      case VariantSuper:
2555        newHoldingsWidth = 2;
2556        gameInfo.holdingsSize = 8;
2557        break;
2558      case VariantGothic:
2559      case VariantCapablanca:
2560      case VariantCapaRandom:
2561        newWidth = 10;
2562      default:
2563        newHoldingsWidth = gameInfo.holdingsSize = 0;
2564      };
2565
2566    if(newWidth  != gameInfo.boardWidth  ||
2567       newHeight != gameInfo.boardHeight ||
2568       newHoldingsWidth != gameInfo.holdingsWidth ) {
2569
2570      /* shift position to new playing area, if needed */
2571      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576        for(i=0; i<newHeight; i++) {
2577          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579        }
2580      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581        for(i=0; i<BOARD_HEIGHT; i++)
2582          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584              board[i][j];
2585      }
2586      board[HOLDINGS_SET] = 0;
2587      gameInfo.boardWidth  = newWidth;
2588      gameInfo.boardHeight = newHeight;
2589      gameInfo.holdingsWidth = newHoldingsWidth;
2590      gameInfo.variant = newVariant;
2591      InitDrawingSizes(-2, 0);
2592    } else gameInfo.variant = newVariant;
2593    CopyBoard(oldBoard, board);   // remember correctly formatted board
2594      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2595    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 }
2597
2598 static int loggedOn = FALSE;
2599
2600 /*-- Game start info cache: --*/
2601 int gs_gamenum;
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\   ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2609
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2612
2613 // [HGM] seekgraph
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2617 #define SQUARE 0x80
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2626
2627 void
2628 PlotSeekAd (int i)
2629 {
2630         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632         if(r < minRating+100 && r >=0 ) r = minRating+100;
2633         if(r > maxRating) r = maxRating;
2634         if(tc < 1.f) tc = 1.f;
2635         if(tc > 95.f) tc = 95.f;
2636         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637         y = ((double)r - minRating)/(maxRating - minRating)
2638             * (h-vMargin-squareSize/8-1) + vMargin;
2639         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640         if(strstr(seekAdList[i], " u ")) color = 1;
2641         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642            !strstr(seekAdList[i], "bullet") &&
2643            !strstr(seekAdList[i], "blitz") &&
2644            !strstr(seekAdList[i], "standard") ) color = 2;
2645         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 }
2648
2649 void
2650 PlotSingleSeekAd (int i)
2651 {
2652         PlotSeekAd(i);
2653 }
2654
2655 void
2656 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2657 {
2658         char buf[MSG_SIZ], *ext = "";
2659         VariantClass v = StringToVariant(type);
2660         if(strstr(type, "wild")) {
2661             ext = type + 4; // append wild number
2662             if(v == VariantFischeRandom) type = "chess960"; else
2663             if(v == VariantLoadable) type = "setup"; else
2664             type = VariantName(v);
2665         }
2666         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672             seekNrList[nrOfSeekAds] = nr;
2673             zList[nrOfSeekAds] = 0;
2674             seekAdList[nrOfSeekAds++] = StrSave(buf);
2675             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2676         }
2677 }
2678
2679 void
2680 EraseSeekDot (int i)
2681 {
2682     int x = xList[i], y = yList[i], d=squareSize/4, k;
2683     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685     // now replot every dot that overlapped
2686     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687         int xx = xList[k], yy = yList[k];
2688         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689             DrawSeekDot(xx, yy, colorList[k]);
2690     }
2691 }
2692
2693 void
2694 RemoveSeekAd (int nr)
2695 {
2696         int i;
2697         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698             EraseSeekDot(i);
2699             if(seekAdList[i]) free(seekAdList[i]);
2700             seekAdList[i] = seekAdList[--nrOfSeekAds];
2701             seekNrList[i] = seekNrList[nrOfSeekAds];
2702             ratingList[i] = ratingList[nrOfSeekAds];
2703             colorList[i]  = colorList[nrOfSeekAds];
2704             tcList[i] = tcList[nrOfSeekAds];
2705             xList[i]  = xList[nrOfSeekAds];
2706             yList[i]  = yList[nrOfSeekAds];
2707             zList[i]  = zList[nrOfSeekAds];
2708             seekAdList[nrOfSeekAds] = NULL;
2709             break;
2710         }
2711 }
2712
2713 Boolean
2714 MatchSoughtLine (char *line)
2715 {
2716     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717     int nr, base, inc, u=0; char dummy;
2718
2719     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721        (u=1) &&
2722        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2724         // match: compact and save the line
2725         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2726         return TRUE;
2727     }
2728     return FALSE;
2729 }
2730
2731 int
2732 DrawSeekGraph ()
2733 {
2734     int i;
2735     if(!seekGraphUp) return FALSE;
2736     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2738
2739     DrawSeekBackground(0, 0, w, h);
2740     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744         yy = h-1-yy;
2745         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746         if(i%500 == 0) {
2747             char buf[MSG_SIZ];
2748             snprintf(buf, MSG_SIZ, "%d", i);
2749             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750         }
2751     }
2752     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753     for(i=1; i<100; i+=(i<10?1:5)) {
2754         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757             char buf[MSG_SIZ];
2758             snprintf(buf, MSG_SIZ, "%d", i);
2759             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760         }
2761     }
2762     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2763     return TRUE;
2764 }
2765
2766 int
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 {
2769     static int lastDown = 0, displayed = 0, lastSecond;
2770     if(y < 0) return FALSE;
2771     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773         if(!seekGraphUp) return FALSE;
2774         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775         DrawPosition(TRUE, NULL);
2776         return TRUE;
2777     }
2778     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779         if(click == Release || moving) return FALSE;
2780         nrOfSeekAds = 0;
2781         soughtPending = TRUE;
2782         SendToICS(ics_prefix);
2783         SendToICS("sought\n"); // should this be "sought all"?
2784     } else { // issue challenge based on clicked ad
2785         int dist = 10000; int i, closest = 0, second = 0;
2786         for(i=0; i<nrOfSeekAds; i++) {
2787             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2788             if(d < dist) { dist = d; closest = i; }
2789             second += (d - zList[i] < 120); // count in-range ads
2790             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791         }
2792         if(dist < 120) {
2793             char buf[MSG_SIZ];
2794             second = (second > 1);
2795             if(displayed != closest || second != lastSecond) {
2796                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797                 lastSecond = second; displayed = closest;
2798             }
2799             if(click == Press) {
2800                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801                 lastDown = closest;
2802                 return TRUE;
2803             } // on press 'hit', only show info
2804             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806             SendToICS(ics_prefix);
2807             SendToICS(buf);
2808             return TRUE; // let incoming board of started game pop down the graph
2809         } else if(click == Release) { // release 'miss' is ignored
2810             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811             if(moving == 2) { // right up-click
2812                 nrOfSeekAds = 0; // refresh graph
2813                 soughtPending = TRUE;
2814                 SendToICS(ics_prefix);
2815                 SendToICS("sought\n"); // should this be "sought all"?
2816             }
2817             return TRUE;
2818         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819         // press miss or release hit 'pop down' seek graph
2820         seekGraphUp = FALSE;
2821         DrawPosition(TRUE, NULL);
2822     }
2823     return TRUE;
2824 }
2825
2826 void
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 {
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2838
2839     static int started = STARTED_NONE;
2840     static char parse[20000];
2841     static int parse_pos = 0;
2842     static char buf[BUF_SIZE + 1];
2843     static int firstTime = TRUE, intfSet = FALSE;
2844     static ColorClass prevColor = ColorNormal;
2845     static int savingComment = FALSE;
2846     static int cmatch = 0; // continuation sequence match
2847     char *bp;
2848     char str[MSG_SIZ];
2849     int i, oldi;
2850     int buf_len;
2851     int next_out;
2852     int tkind;
2853     int backup;    /* [DM] For zippy color lines */
2854     char *p;
2855     char talker[MSG_SIZ]; // [HGM] chat
2856     int channel, collective=0;
2857
2858     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859
2860     if (appData.debugMode) {
2861       if (!error) {
2862         fprintf(debugFP, "<ICS: ");
2863         show_bytes(debugFP, data, count);
2864         fprintf(debugFP, "\n");
2865       }
2866     }
2867
2868     if (appData.debugMode) { int f = forwardMostMove;
2869         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872     }
2873     if (count > 0) {
2874         /* If last read ended with a partial line that we couldn't parse,
2875            prepend it to the new read and try again. */
2876         if (leftover_len > 0) {
2877             for (i=0; i<leftover_len; i++)
2878               buf[i] = buf[leftover_start + i];
2879         }
2880
2881     /* copy new characters into the buffer */
2882     bp = buf + leftover_len;
2883     buf_len=leftover_len;
2884     for (i=0; i<count; i++)
2885     {
2886         // ignore these
2887         if (data[i] == '\r')
2888             continue;
2889
2890         // join lines split by ICS?
2891         if (!appData.noJoin)
2892         {
2893             /*
2894                 Joining just consists of finding matches against the
2895                 continuation sequence, and discarding that sequence
2896                 if found instead of copying it.  So, until a match
2897                 fails, there's nothing to do since it might be the
2898                 complete sequence, and thus, something we don't want
2899                 copied.
2900             */
2901             if (data[i] == cont_seq[cmatch])
2902             {
2903                 cmatch++;
2904                 if (cmatch == strlen(cont_seq))
2905                 {
2906                     cmatch = 0; // complete match.  just reset the counter
2907
2908                     /*
2909                         it's possible for the ICS to not include the space
2910                         at the end of the last word, making our [correct]
2911                         join operation fuse two separate words.  the server
2912                         does this when the space occurs at the width setting.
2913                     */
2914                     if (!buf_len || buf[buf_len-1] != ' ')
2915                     {
2916                         *bp++ = ' ';
2917                         buf_len++;
2918                     }
2919                 }
2920                 continue;
2921             }
2922             else if (cmatch)
2923             {
2924                 /*
2925                     match failed, so we have to copy what matched before
2926                     falling through and copying this character.  In reality,
2927                     this will only ever be just the newline character, but
2928                     it doesn't hurt to be precise.
2929                 */
2930                 strncpy(bp, cont_seq, cmatch);
2931                 bp += cmatch;
2932                 buf_len += cmatch;
2933                 cmatch = 0;
2934             }
2935         }
2936
2937         // copy this char
2938         *bp++ = data[i];
2939         buf_len++;
2940     }
2941
2942         buf[buf_len] = NULLCHAR;
2943 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2944         next_out = 0;
2945         leftover_start = 0;
2946
2947         i = 0;
2948         while (i < buf_len) {
2949             /* Deal with part of the TELNET option negotiation
2950                protocol.  We refuse to do anything beyond the
2951                defaults, except that we allow the WILL ECHO option,
2952                which ICS uses to turn off password echoing when we are
2953                directly connected to it.  We reject this option
2954                if localLineEditing mode is on (always on in xboard)
2955                and we are talking to port 23, which might be a real
2956                telnet server that will try to keep WILL ECHO on permanently.
2957              */
2958             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960                 unsigned char option;
2961                 oldi = i;
2962                 switch ((unsigned char) buf[++i]) {
2963                   case TN_WILL:
2964                     if (appData.debugMode)
2965                       fprintf(debugFP, "\n<WILL ");
2966                     switch (option = (unsigned char) buf[++i]) {
2967                       case TN_ECHO:
2968                         if (appData.debugMode)
2969                           fprintf(debugFP, "ECHO ");
2970                         /* Reply only if this is a change, according
2971                            to the protocol rules. */
2972                         if (remoteEchoOption) break;
2973                         if (appData.localLineEditing &&
2974                             atoi(appData.icsPort) == TN_PORT) {
2975                             TelnetRequest(TN_DONT, TN_ECHO);
2976                         } else {
2977                             EchoOff();
2978                             TelnetRequest(TN_DO, TN_ECHO);
2979                             remoteEchoOption = TRUE;
2980                         }
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", option);
2985                         /* Whatever this is, we don't want it. */
2986                         TelnetRequest(TN_DONT, option);
2987                         break;
2988                     }
2989                     break;
2990                   case TN_WONT:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<WONT ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       case TN_ECHO:
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "ECHO ");
2997                         /* Reply only if this is a change, according
2998                            to the protocol rules. */
2999                         if (!remoteEchoOption) break;
3000                         EchoOn();
3001                         TelnetRequest(TN_DONT, TN_ECHO);
3002                         remoteEchoOption = FALSE;
3003                         break;
3004                       default:
3005                         if (appData.debugMode)
3006                           fprintf(debugFP, "%d ", (unsigned char) option);
3007                         /* Whatever this is, it must already be turned
3008                            off, because we never agree to turn on
3009                            anything non-default, so according to the
3010                            protocol rules, we don't reply. */
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DO:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DO ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         /* Whatever this is, we refuse to do it. */
3020                         if (appData.debugMode)
3021                           fprintf(debugFP, "%d ", option);
3022                         TelnetRequest(TN_WONT, option);
3023                         break;
3024                     }
3025                     break;
3026                   case TN_DONT:
3027                     if (appData.debugMode)
3028                       fprintf(debugFP, "\n<DONT ");
3029                     switch (option = (unsigned char) buf[++i]) {
3030                       default:
3031                         if (appData.debugMode)
3032                           fprintf(debugFP, "%d ", option);
3033                         /* Whatever this is, we are already not doing
3034                            it, because we never agree to do anything
3035                            non-default, so according to the protocol
3036                            rules, we don't reply. */
3037                         break;
3038                     }
3039                     break;
3040                   case TN_IAC:
3041                     if (appData.debugMode)
3042                       fprintf(debugFP, "\n<IAC ");
3043                     /* Doubled IAC; pass it through */
3044                     i--;
3045                     break;
3046                   default:
3047                     if (appData.debugMode)
3048                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049                     /* Drop all other telnet commands on the floor */
3050                     break;
3051                 }
3052                 if (oldi > next_out)
3053                   SendToPlayer(&buf[next_out], oldi - next_out);
3054                 if (++i > next_out)
3055                   next_out = i;
3056                 continue;
3057             }
3058
3059             /* OK, this at least will *usually* work */
3060             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061                 loggedOn = TRUE;
3062             }
3063
3064             if (loggedOn && !intfSet) {
3065                 if (ics_type == ICS_ICC) {
3066                   snprintf(str, MSG_SIZ,
3067                           "/set-quietly interface %s\n/set-quietly style 12\n",
3068                           programVersion);
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3071                 } else if (ics_type == ICS_CHESSNET) {
3072                   snprintf(str, MSG_SIZ, "/style 12\n");
3073                 } else {
3074                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075                   strcat(str, programVersion);
3076                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 #ifdef WIN32
3080                   strcat(str, "$iset nohighlight 1\n");
3081 #endif
3082                   strcat(str, "$iset lock 1\n$style 12\n");
3083                 }
3084                 SendToICS(str);
3085                 NotifyFrontendLogin();
3086                 intfSet = TRUE;
3087             }
3088
3089             if (started == STARTED_COMMENT) {
3090                 /* Accumulate characters in comment */
3091                 parse[parse_pos++] = buf[i];
3092                 if (buf[i] == '\n') {
3093                     parse[parse_pos] = NULLCHAR;
3094                     if(chattingPartner>=0) {
3095                         char mess[MSG_SIZ];
3096                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097                         OutputChatMessage(chattingPartner, mess);
3098                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099                             int p;
3100                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103                                 OutputChatMessage(p, mess);
3104                                 break;
3105                             }
3106                         }
3107                         chattingPartner = -1;
3108                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109                         collective = 0;
3110                     } else
3111                     if(!suppressKibitz) // [HGM] kibitz
3112                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114                         int nrDigit = 0, nrAlph = 0, j;
3115                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117                         parse[parse_pos] = NULLCHAR;
3118                         // try to be smart: if it does not look like search info, it should go to
3119                         // ICS interaction window after all, not to engine-output window.
3120                         for(j=0; j<parse_pos; j++) { // count letters and digits
3121                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3123                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3124                         }
3125                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126                             int depth=0; float score;
3127                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129                                 pvInfoList[forwardMostMove-1].depth = depth;
3130                                 pvInfoList[forwardMostMove-1].score = 100*score;
3131                             }
3132                             OutputKibitz(suppressKibitz, parse);
3133                         } else {
3134                             char tmp[MSG_SIZ];
3135                             if(gameMode == IcsObserving) // restore original ICS messages
3136                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138                             else
3139                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141                             SendToPlayer(tmp, strlen(tmp));
3142                         }
3143                         next_out = i+1; // [HGM] suppress printing in ICS window
3144                     }
3145                     started = STARTED_NONE;
3146                 } else {
3147                     /* Don't match patterns against characters in comment */
3148                     i++;
3149                     continue;
3150                 }
3151             }
3152             if (started == STARTED_CHATTER) {
3153                 if (buf[i] != '\n') {
3154                     /* Don't match patterns against characters in chatter */
3155                     i++;
3156                     continue;
3157                 }
3158                 started = STARTED_NONE;
3159                 if(suppressKibitz) next_out = i+1;
3160             }
3161
3162             /* Kludge to deal with rcmd protocol */
3163             if (firstTime && looking_at(buf, &i, "\001*")) {
3164                 DisplayFatalError(&buf[1], 0, 1);
3165                 continue;
3166             } else {
3167                 firstTime = FALSE;
3168             }
3169
3170             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171                 ics_type = ICS_ICC;
3172                 ics_prefix = "/";
3173                 if (appData.debugMode)
3174                   fprintf(debugFP, "ics_type %d\n", ics_type);
3175                 continue;
3176             }
3177             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178                 ics_type = ICS_FICS;
3179                 ics_prefix = "$";
3180                 if (appData.debugMode)
3181                   fprintf(debugFP, "ics_type %d\n", ics_type);
3182                 continue;
3183             }
3184             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185                 ics_type = ICS_CHESSNET;
3186                 ics_prefix = "/";
3187                 if (appData.debugMode)
3188                   fprintf(debugFP, "ics_type %d\n", ics_type);
3189                 continue;
3190             }
3191
3192             if (!loggedOn &&
3193                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3195                  looking_at(buf, &i, "will be \"*\""))) {
3196               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197               continue;
3198             }
3199
3200             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201               char buf[MSG_SIZ];
3202               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203               DisplayIcsInteractionTitle(buf);
3204               have_set_title = TRUE;
3205             }
3206
3207             /* skip finger notes */
3208             if (started == STARTED_NONE &&
3209                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210                  (buf[i] == '1' && buf[i+1] == '0')) &&
3211                 buf[i+2] == ':' && buf[i+3] == ' ') {
3212               started = STARTED_CHATTER;
3213               i += 3;
3214               continue;
3215             }
3216
3217             oldi = i;
3218             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219             if(appData.seekGraph) {
3220                 if(soughtPending && MatchSoughtLine(buf+i)) {
3221                     i = strstr(buf+i, "rated") - buf;
3222                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223                     next_out = leftover_start = i;
3224                     started = STARTED_CHATTER;
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229                         && looking_at(buf, &i, "* ads displayed")) {
3230                     soughtPending = FALSE;
3231                     seekGraphUp = TRUE;
3232                     DrawSeekGraph();
3233                     continue;
3234                 }
3235                 if(appData.autoRefresh) {
3236                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237                         int s = (ics_type == ICS_ICC); // ICC format differs
3238                         if(seekGraphUp)
3239                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i; // suppress
3245                         continue;
3246                     }
3247                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248                         char *p = star_match[0];
3249                         while(*p) {
3250                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3251                             while(*p && *p++ != ' '); // next
3252                         }
3253                         looking_at(buf, &i, "*% "); // eat prompt
3254                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                         next_out = i;
3256                         continue;
3257                     }
3258                 }
3259             }
3260
3261             /* skip formula vars */
3262             if (started == STARTED_NONE &&
3263                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264               started = STARTED_CHATTER;
3265               i += 3;
3266               continue;
3267             }
3268
3269             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270             if (appData.autoKibitz && started == STARTED_NONE &&
3271                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3272                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3277                         suppressKibitz = TRUE;
3278                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279                         next_out = i;
3280                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281                                 && (gameMode == IcsPlayingWhite)) ||
3282                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3284                             started = STARTED_CHATTER; // own kibitz we simply discard
3285                         else {
3286                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287                             parse_pos = 0; parse[0] = NULLCHAR;
3288                             savingComment = TRUE;
3289                             suppressKibitz = gameMode != IcsObserving ? 2 :
3290                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291                         }
3292                         continue;
3293                 } else
3294                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296                          && atoi(star_match[0])) {
3297                     // suppress the acknowledgements of our own autoKibitz
3298                     char *p;
3299                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301                     SendToPlayer(star_match[0], strlen(star_match[0]));
3302                     if(looking_at(buf, &i, "*% ")) // eat prompt
3303                         suppressKibitz = FALSE;
3304                     next_out = i;
3305                     continue;
3306                 }
3307             } // [HGM] kibitz: end of patch
3308
3309             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310
3311             // [HGM] chat: intercept tells by users for which we have an open chat window
3312             channel = -1;
3313             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314                                            looking_at(buf, &i, "* whispers:") ||
3315                                            looking_at(buf, &i, "* kibitzes:") ||
3316                                            looking_at(buf, &i, "* shouts:") ||
3317                                            looking_at(buf, &i, "* c-shouts:") ||
3318                                            looking_at(buf, &i, "--> * ") ||
3319                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323                 int p;
3324                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325                 chattingPartner = -1; collective = 0;
3326
3327                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331                     talker[0] = '['; strcat(talker, "] ");
3332                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333                     chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("kibitzes", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345                 for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("whispers", chatPartner[p])) {
3348                         talker[0] = '['; strcat(talker, "] ");
3349                         chattingPartner = p; break;
3350                     }
3351                 } else
3352                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353                   if(buf[i-8] == '-' && buf[i-3] == 't')
3354                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355                     collective = 1;
3356                     if(!strcmp("c-shouts", chatPartner[p])) {
3357                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358                         chattingPartner = p; break;
3359                     }
3360                   }
3361                   if(chattingPartner < 0)
3362                   for(p=0; p<MAX_CHAT; p++) {
3363                     collective = 1;
3364                     if(!strcmp("shouts", chatPartner[p])) {
3365                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368                         chattingPartner = p; break;
3369                     }
3370                   }
3371                 }
3372                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374                     talker[0] = 0;
3375                     Colorize(ColorTell, FALSE);
3376                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377                     collective |= 2;
3378                     chattingPartner = p; break;
3379                 }
3380                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382                     started = STARTED_COMMENT;
3383                     parse_pos = 0; parse[0] = NULLCHAR;
3384                     savingComment = 3 + chattingPartner; // counts as TRUE
3385                     if(collective == 3) i = oldi; else {
3386                         suppressKibitz = TRUE;
3387                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389                         continue;
3390                     }
3391                 }
3392             } // [HGM] chat: end of patch
3393
3394           backup = i;
3395             if (appData.zippyTalk || appData.zippyPlay) {
3396                 /* [DM] Backup address for color zippy lines */
3397 #if ZIPPY
3398                if (loggedOn == TRUE)
3399                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3401                        ;
3402 #endif
3403             } // [DM] 'else { ' deleted
3404                 if (
3405                     /* Regular tells and says */
3406                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3408                     looking_at(buf, &i, "* says: ") ||
3409                     /* Don't color "message" or "messages" output */
3410                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411                     looking_at(buf, &i, "*. * at *:*: ") ||
3412                     looking_at(buf, &i, "--* (*:*): ") ||
3413                     /* Message notifications (same color as tells) */
3414                     looking_at(buf, &i, "* has left a message ") ||
3415                     looking_at(buf, &i, "* just sent you a message:\n") ||
3416                     /* Whispers and kibitzes */
3417                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418                     looking_at(buf, &i, "* kibitzes: ") ||
3419                     /* Channel tells */
3420                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421
3422                   if (tkind == 1 && strchr(star_match[0], ':')) {
3423                       /* Avoid "tells you:" spoofs in channels */
3424                      tkind = 3;
3425                   }
3426                   if (star_match[0][0] == NULLCHAR ||
3427                       strchr(star_match[0], ' ') ||
3428                       (tkind == 3 && strchr(star_match[1], ' '))) {
3429                     /* Reject bogus matches */
3430                     i = oldi;
3431                   } else {
3432                     if (appData.colorize) {
3433                       if (oldi > next_out) {
3434                         SendToPlayer(&buf[next_out], oldi - next_out);
3435                         next_out = oldi;
3436                       }
3437                       switch (tkind) {
3438                       case 1:
3439                         Colorize(ColorTell, FALSE);
3440                         curColor = ColorTell;
3441                         break;
3442                       case 2:
3443                         Colorize(ColorKibitz, FALSE);
3444                         curColor = ColorKibitz;
3445                         break;
3446                       case 3:
3447                         p = strrchr(star_match[1], '(');
3448                         if (p == NULL) {
3449                           p = star_match[1];
3450                         } else {
3451                           p++;
3452                         }
3453                         if (atoi(p) == 1) {
3454                           Colorize(ColorChannel1, FALSE);
3455                           curColor = ColorChannel1;
3456                         } else {
3457                           Colorize(ColorChannel, FALSE);
3458                           curColor = ColorChannel;
3459                         }
3460                         break;
3461                       case 5:
3462                         curColor = ColorNormal;
3463                         break;
3464                       }
3465                     }
3466                     if (started == STARTED_NONE && appData.autoComment &&
3467                         (gameMode == IcsObserving ||
3468                          gameMode == IcsPlayingWhite ||
3469                          gameMode == IcsPlayingBlack)) {
3470                       parse_pos = i - oldi;
3471                       memcpy(parse, &buf[oldi], parse_pos);
3472                       parse[parse_pos] = NULLCHAR;
3473                       started = STARTED_COMMENT;
3474                       savingComment = TRUE;
3475                     } else if(collective != 3) {
3476                       started = STARTED_CHATTER;
3477                       savingComment = FALSE;
3478                     }
3479                     loggedOn = TRUE;
3480                     continue;
3481                   }
3482                 }
3483
3484                 if (looking_at(buf, &i, "* s-shouts: ") ||
3485                     looking_at(buf, &i, "* c-shouts: ")) {
3486                     if (appData.colorize) {
3487                         if (oldi > next_out) {
3488                             SendToPlayer(&buf[next_out], oldi - next_out);
3489                             next_out = oldi;
3490                         }
3491                         Colorize(ColorSShout, FALSE);
3492                         curColor = ColorSShout;
3493                     }
3494                     loggedOn = TRUE;
3495                     started = STARTED_CHATTER;
3496                     continue;
3497                 }
3498
3499                 if (looking_at(buf, &i, "--->")) {
3500                     loggedOn = TRUE;
3501                     continue;
3502                 }
3503
3504                 if (looking_at(buf, &i, "* shouts: ") ||
3505                     looking_at(buf, &i, "--> ")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorShout, FALSE);
3512                         curColor = ColorShout;
3513                     }
3514                     loggedOn = TRUE;
3515                     started = STARTED_CHATTER;
3516                     continue;
3517                 }
3518
3519                 if (looking_at( buf, &i, "Challenge:")) {
3520                     if (appData.colorize) {
3521                         if (oldi > next_out) {
3522                             SendToPlayer(&buf[next_out], oldi - next_out);
3523                             next_out = oldi;
3524                         }
3525                         Colorize(ColorChallenge, FALSE);
3526                         curColor = ColorChallenge;
3527                     }
3528                     loggedOn = TRUE;
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* offers you") ||
3533                     looking_at(buf, &i, "* offers to be") ||
3534                     looking_at(buf, &i, "* would like to") ||
3535                     looking_at(buf, &i, "* requests to") ||
3536                     looking_at(buf, &i, "Your opponent offers") ||
3537                     looking_at(buf, &i, "Your opponent requests")) {
3538
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorRequest, FALSE);
3545                         curColor = ColorRequest;
3546                     }
3547                     continue;
3548                 }
3549
3550                 if (looking_at(buf, &i, "* (*) seeking")) {
3551                     if (appData.colorize) {
3552                         if (oldi > next_out) {
3553                             SendToPlayer(&buf[next_out], oldi - next_out);
3554                             next_out = oldi;
3555                         }
3556                         Colorize(ColorSeek, FALSE);
3557                         curColor = ColorSeek;
3558                     }
3559                     continue;
3560             }
3561
3562           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3563
3564             if (looking_at(buf, &i, "\\   ")) {
3565                 if (prevColor != ColorNormal) {
3566                     if (oldi > next_out) {
3567                         SendToPlayer(&buf[next_out], oldi - next_out);
3568                         next_out = oldi;
3569                     }
3570                     Colorize(prevColor, TRUE);
3571                     curColor = prevColor;
3572                 }
3573                 if (savingComment) {
3574                     parse_pos = i - oldi;
3575                     memcpy(parse, &buf[oldi], parse_pos);
3576                     parse[parse_pos] = NULLCHAR;
3577                     started = STARTED_COMMENT;
3578                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579                         chattingPartner = savingComment - 3; // kludge to remember the box
3580                 } else {
3581                     started = STARTED_CHATTER;
3582                 }
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "Black Strength :") ||
3587                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588                 looking_at(buf, &i, "<10>") ||
3589                 looking_at(buf, &i, "#@#")) {
3590                 /* Wrong board style */
3591                 loggedOn = TRUE;
3592                 SendToICS(ics_prefix);
3593                 SendToICS("set style 12\n");
3594                 SendToICS(ics_prefix);
3595                 SendToICS("refresh\n");
3596                 continue;
3597             }
3598
3599             if (looking_at(buf, &i, "login:")) {
3600               if (!have_sent_ICS_logon) {
3601                 if(ICSInitScript())
3602                   have_sent_ICS_logon = 1;
3603                 else // no init script was found
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607               }
3608                 continue;
3609             }
3610
3611             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612                 (looking_at(buf, &i, "\n<12> ") ||
3613                  looking_at(buf, &i, "<12> "))) {
3614                 loggedOn = TRUE;
3615                 if (oldi > next_out) {
3616                     SendToPlayer(&buf[next_out], oldi - next_out);
3617                 }
3618                 next_out = i;
3619                 started = STARTED_BOARD;
3620                 parse_pos = 0;
3621                 continue;
3622             }
3623
3624             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625                 looking_at(buf, &i, "<b1> ")) {
3626                 if (oldi > next_out) {
3627                     SendToPlayer(&buf[next_out], oldi - next_out);
3628                 }
3629                 next_out = i;
3630                 started = STARTED_HOLDINGS;
3631                 parse_pos = 0;
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3636                 loggedOn = TRUE;
3637                 /* Header for a move list -- first line */
3638
3639                 switch (ics_getting_history) {
3640                   case H_FALSE:
3641                     switch (gameMode) {
3642                       case IcsIdle:
3643                       case BeginningOfGame:
3644                         /* User typed "moves" or "oldmoves" while we
3645                            were idle.  Pretend we asked for these
3646                            moves and soak them up so user can step
3647                            through them and/or save them.
3648                            */
3649                         Reset(FALSE, TRUE);
3650                         gameMode = IcsObserving;
3651                         ModeHighlight();
3652                         ics_gamenum = -1;
3653                         ics_getting_history = H_GOT_UNREQ_HEADER;
3654                         break;
3655                       case EditGame: /*?*/
3656                       case EditPosition: /*?*/
3657                         /* Should above feature work in these modes too? */
3658                         /* For now it doesn't */
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                       default:
3662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3663                         break;
3664                     }
3665                     break;
3666                   case H_REQUESTED:
3667                     /* Is this the right one? */
3668                     if (gameInfo.white && gameInfo.black &&
3669                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3670                         strcmp(gameInfo.black, star_match[2]) == 0) {
3671                         /* All is well */
3672                         ics_getting_history = H_GOT_REQ_HEADER;
3673                     }
3674                     break;
3675                   case H_GOT_REQ_HEADER:
3676                   case H_GOT_UNREQ_HEADER:
3677                   case H_GOT_UNWANTED_HEADER:
3678                   case H_GETTING_MOVES:
3679                     /* Should not happen */
3680                     DisplayError(_("Error gathering move list: two headers"), 0);
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683                 }
3684
3685                 /* Save player ratings into gameInfo if needed */
3686                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688                     (gameInfo.whiteRating == -1 ||
3689                      gameInfo.blackRating == -1)) {
3690
3691                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3692                     gameInfo.blackRating = string_to_rating(star_match[3]);
3693                     if (appData.debugMode)
3694                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695                               gameInfo.whiteRating, gameInfo.blackRating);
3696                 }
3697                 continue;
3698             }
3699
3700             if (looking_at(buf, &i,
3701               "* * match, initial time: * minute*, increment: * second")) {
3702                 /* Header for a move list -- second line */
3703                 /* Initial board will follow if this is a wild game */
3704                 if (gameInfo.event != NULL) free(gameInfo.event);
3705                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706                 gameInfo.event = StrSave(str);
3707                 /* [HGM] we switched variant. Translate boards if needed. */
3708                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Move  ")) {
3713                 /* Beginning of a move list */
3714                 switch (ics_getting_history) {
3715                   case H_FALSE:
3716                     /* Normally should not happen */
3717                     /* Maybe user hit reset while we were parsing */
3718                     break;
3719                   case H_REQUESTED:
3720                     /* Happens if we are ignoring a move list that is not
3721                      * the one we just requested.  Common if the user
3722                      * tries to observe two games without turning off
3723                      * getMoveList */
3724                     break;
3725                   case H_GETTING_MOVES:
3726                     /* Should not happen */
3727                     DisplayError(_("Error gathering move list: nested"), 0);
3728                     ics_getting_history = H_FALSE;
3729                     break;
3730                   case H_GOT_REQ_HEADER:
3731                     ics_getting_history = H_GETTING_MOVES;
3732                     started = STARTED_MOVES;
3733                     parse_pos = 0;
3734                     if (oldi > next_out) {
3735                         SendToPlayer(&buf[next_out], oldi - next_out);
3736                     }
3737                     break;
3738                   case H_GOT_UNREQ_HEADER:
3739                     ics_getting_history = H_GETTING_MOVES;
3740                     started = STARTED_MOVES_NOHIDE;
3741                     parse_pos = 0;
3742                     break;
3743                   case H_GOT_UNWANTED_HEADER:
3744                     ics_getting_history = H_FALSE;
3745                     break;
3746                 }
3747                 continue;
3748             }
3749
3750             if (looking_at(buf, &i, "% ") ||
3751                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754                     soughtPending = FALSE;
3755                     seekGraphUp = TRUE;
3756                     DrawSeekGraph();
3757                 }
3758                 if(suppressKibitz) next_out = i;
3759                 savingComment = FALSE;
3760                 suppressKibitz = 0;
3761                 switch (started) {
3762                   case STARTED_MOVES:
3763                   case STARTED_MOVES_NOHIDE:
3764                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765                     parse[parse_pos + i - oldi] = NULLCHAR;
3766                     ParseGameHistory(parse);
3767 #if ZIPPY
3768                     if (appData.zippyPlay && first.initDone) {
3769                         FeedMovesToProgram(&first, forwardMostMove);
3770                         if (gameMode == IcsPlayingWhite) {
3771                             if (WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("black\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, TRUE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, TRUE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         } else if (gameMode == IcsPlayingBlack) {
3795                             if (!WhiteOnMove(forwardMostMove)) {
3796                                 if (first.sendTime) {
3797                                   if (first.useColors) {
3798                                     SendToProgram("white\n", &first);
3799                                   }
3800                                   SendTimeRemaining(&first, FALSE);
3801                                 }
3802                                 if (first.useColors) {
3803                                   SendToProgram("black\n", &first);
3804                                 }
3805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806                                 first.maybeThinking = TRUE;
3807                             } else {
3808                                 if (first.usePlayother) {
3809                                   if (first.sendTime) {
3810                                     SendTimeRemaining(&first, FALSE);
3811                                   }
3812                                   SendToProgram("playother\n", &first);
3813                                   firstMove = FALSE;
3814                                 } else {
3815                                   firstMove = TRUE;
3816                                 }
3817                             }
3818                         }
3819                     }
3820 #endif
3821                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3822                         /* Moves came from oldmoves or moves command
3823                            while we weren't doing anything else.
3824                            */
3825                         currentMove = forwardMostMove;
3826                         ClearHighlights();/*!!could figure this out*/
3827                         flipView = appData.flipView;
3828                         DrawPosition(TRUE, boards[currentMove]);
3829                         DisplayBothClocks();
3830                         snprintf(str, MSG_SIZ, "%s %s %s",
3831                                 gameInfo.white, _("vs."),  gameInfo.black);
3832                         DisplayTitle(str);
3833                         gameMode = IcsIdle;
3834                     } else {
3835                         /* Moves were history of an active game */
3836                         if (gameInfo.resultDetails != NULL) {
3837                             free(gameInfo.resultDetails);
3838                             gameInfo.resultDetails = NULL;
3839                         }
3840                     }
3841                     HistorySet(parseList, backwardMostMove,
3842                                forwardMostMove, currentMove-1);
3843                     DisplayMove(currentMove - 1);
3844                     if (started == STARTED_MOVES) next_out = i;
3845                     started = STARTED_NONE;
3846                     ics_getting_history = H_FALSE;
3847                     break;
3848
3849                   case STARTED_OBSERVE:
3850                     started = STARTED_NONE;
3851                     SendToICS(ics_prefix);
3852                     SendToICS("refresh\n");
3853                     break;
3854
3855                   default:
3856                     break;
3857                 }
3858                 if(bookHit) { // [HGM] book: simulate book reply
3859                     static char bookMove[MSG_SIZ]; // a bit generous?
3860
3861                     programStats.nodes = programStats.depth = programStats.time =
3862                     programStats.score = programStats.got_only_move = 0;
3863                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3864
3865                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866                     strcat(bookMove, bookHit);
3867                     HandleMachineMove(bookMove, &first);
3868                 }
3869                 continue;
3870             }
3871
3872             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873                  started == STARTED_HOLDINGS ||
3874                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875                 /* Accumulate characters in move list or board */
3876                 parse[parse_pos++] = buf[i];
3877             }
3878
3879             /* Start of game messages.  Mostly we detect start of game
3880                when the first board image arrives.  On some versions
3881                of the ICS, though, we need to do a "refresh" after starting
3882                to observe in order to get the current board right away. */
3883             if (looking_at(buf, &i, "Adding game * to observation list")) {
3884                 started = STARTED_OBSERVE;
3885                 continue;
3886             }
3887
3888             /* Handle auto-observe */
3889             if (appData.autoObserve &&
3890                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3892                 char *player;
3893                 /* Choose the player that was highlighted, if any. */
3894                 if (star_match[0][0] == '\033' ||
3895                     star_match[1][0] != '\033') {
3896                     player = star_match[0];
3897                 } else {
3898                     player = star_match[2];
3899                 }
3900                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901                         ics_prefix, StripHighlightAndTitle(player));
3902                 SendToICS(str);
3903
3904                 /* Save ratings from notify string */
3905                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906                 player1Rating = string_to_rating(star_match[1]);
3907                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908                 player2Rating = string_to_rating(star_match[3]);
3909
3910                 if (appData.debugMode)
3911                   fprintf(debugFP,
3912                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3913                           player1Name, player1Rating,
3914                           player2Name, player2Rating);
3915
3916                 continue;
3917             }
3918
3919             /* Deal with automatic examine mode after a game,
3920                and with IcsObserving -> IcsExamining transition */
3921             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922                 looking_at(buf, &i, "has made you an examiner of game *")) {
3923
3924                 int gamenum = atoi(star_match[0]);
3925                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926                     gamenum == ics_gamenum) {
3927                     /* We were already playing or observing this game;
3928                        no need to refetch history */
3929                     gameMode = IcsExamining;
3930                     if (pausing) {
3931                         pauseExamForwardMostMove = forwardMostMove;
3932                     } else if (currentMove < forwardMostMove) {
3933                         ForwardInner(forwardMostMove);
3934                     }
3935                 } else {
3936                     /* I don't think this case really can happen */
3937                     SendToICS(ics_prefix);
3938                     SendToICS("refresh\n");
3939                 }
3940                 continue;
3941             }
3942
3943             /* Error messages */
3944 //          if (ics_user_moved) {
3945             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946                 if (looking_at(buf, &i, "Illegal move") ||
3947                     looking_at(buf, &i, "Not a legal move") ||
3948                     looking_at(buf, &i, "Your king is in check") ||
3949                     looking_at(buf, &i, "It isn't your turn") ||
3950                     looking_at(buf, &i, "It is not your move")) {
3951                     /* Illegal move */
3952                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953                         currentMove = forwardMostMove-1;
3954                         DisplayMove(currentMove - 1); /* before DMError */
3955                         DrawPosition(FALSE, boards[currentMove]);
3956                         SwitchClocks(forwardMostMove-1); // [HGM] race
3957                         DisplayBothClocks();
3958                     }
3959                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3960                     ics_user_moved = 0;
3961                     continue;
3962                 }
3963             }
3964
3965             if (looking_at(buf, &i, "still have time") ||
3966                 looking_at(buf, &i, "not out of time") ||
3967                 looking_at(buf, &i, "either player is out of time") ||
3968                 looking_at(buf, &i, "has timeseal; checking")) {
3969                 /* We must have called his flag a little too soon */
3970                 whiteFlag = blackFlag = FALSE;
3971                 continue;
3972             }
3973
3974             if (looking_at(buf, &i, "added * seconds to") ||
3975                 looking_at(buf, &i, "seconds were added to")) {
3976                 /* Update the clocks */
3977                 SendToICS(ics_prefix);
3978                 SendToICS("refresh\n");
3979                 continue;
3980             }
3981
3982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983                 ics_clock_paused = TRUE;
3984                 StopClocks();
3985                 continue;
3986             }
3987
3988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989                 ics_clock_paused = FALSE;
3990                 StartClocks();
3991                 continue;
3992             }
3993
3994             /* Grab player ratings from the Creating: message.
3995                Note we have to check for the special case when
3996                the ICS inserts things like [white] or [black]. */
3997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3999                 /* star_matches:
4000                    0    player 1 name (not necessarily white)
4001                    1    player 1 rating
4002                    2    empty, white, or black (IGNORED)
4003                    3    player 2 name (not necessarily black)
4004                    4    player 2 rating
4005
4006                    The names/ratings are sorted out when the game
4007                    actually starts (below).
4008                 */
4009                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010                 player1Rating = string_to_rating(star_match[1]);
4011                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012                 player2Rating = string_to_rating(star_match[4]);
4013
4014                 if (appData.debugMode)
4015                   fprintf(debugFP,
4016                           "Ratings from 'Creating:' %s %d, %s %d\n",
4017                           player1Name, player1Rating,
4018                           player2Name, player2Rating);
4019
4020                 continue;
4021             }
4022
4023             /* Improved generic start/end-of-game messages */
4024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026                 /* If tkind == 0: */
4027                 /* star_match[0] is the game number */
4028                 /*           [1] is the white player's name */
4029                 /*           [2] is the black player's name */
4030                 /* For end-of-game: */
4031                 /*           [3] is the reason for the game end */
4032                 /*           [4] is a PGN end game-token, preceded by " " */
4033                 /* For start-of-game: */
4034                 /*           [3] begins with "Creating" or "Continuing" */
4035                 /*           [4] is " *" or empty (don't care). */
4036                 int gamenum = atoi(star_match[0]);
4037                 char *whitename, *blackname, *why, *endtoken;
4038                 ChessMove endtype = EndOfFile;
4039
4040                 if (tkind == 0) {
4041                   whitename = star_match[1];
4042                   blackname = star_match[2];
4043                   why = star_match[3];
4044                   endtoken = star_match[4];
4045                 } else {
4046                   whitename = star_match[1];
4047                   blackname = star_match[3];
4048                   why = star_match[5];
4049                   endtoken = star_match[6];
4050                 }
4051
4052                 /* Game start messages */
4053                 if (strncmp(why, "Creating ", 9) == 0 ||
4054                     strncmp(why, "Continuing ", 11) == 0) {
4055                     gs_gamenum = gamenum;
4056                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4059 #if ZIPPY
4060                     if (appData.zippyPlay) {
4061                         ZippyGameStart(whitename, blackname);
4062                     }
4063 #endif /*ZIPPY*/
4064                     partnerBoardValid = FALSE; // [HGM] bughouse
4065                     continue;
4066                 }
4067
4068                 /* Game end messages */
4069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070                     ics_gamenum != gamenum) {
4071                     continue;
4072                 }
4073                 while (endtoken[0] == ' ') endtoken++;
4074                 switch (endtoken[0]) {
4075                   case '*':
4076                   default:
4077                     endtype = GameUnfinished;
4078                     break;
4079                   case '0':
4080                     endtype = BlackWins;
4081                     break;
4082                   case '1':
4083                     if (endtoken[1] == '/')
4084                       endtype = GameIsDrawn;
4085                     else
4086                       endtype = WhiteWins;
4087                     break;
4088                 }
4089                 GameEnds(endtype, why, GE_ICS);
4090 #if ZIPPY
4091                 if (appData.zippyPlay && first.initDone) {
4092                     ZippyGameEnd(endtype, why);
4093                     if (first.pr == NoProc) {
4094                       /* Start the next process early so that we'll
4095                          be ready for the next challenge */
4096                       StartChessProgram(&first);
4097                     }
4098                     /* Send "new" early, in case this command takes
4099                        a long time to finish, so that we'll be ready
4100                        for the next challenge. */
4101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4102                     Reset(TRUE, TRUE);
4103                 }
4104 #endif /*ZIPPY*/
4105                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4106                 continue;
4107             }
4108
4109             if (looking_at(buf, &i, "Removing game * from observation") ||
4110                 looking_at(buf, &i, "no longer observing game *") ||
4111                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112                 if (gameMode == IcsObserving &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       /* icsEngineAnalyze */
4116                       if (appData.icsEngineAnalyze) {
4117                             ExitAnalyzeMode();
4118                             ModeHighlight();
4119                       }
4120                       StopClocks();
4121                       gameMode = IcsIdle;
4122                       ics_gamenum = -1;
4123                       ics_user_moved = FALSE;
4124                   }
4125                 continue;
4126             }
4127
4128             if (looking_at(buf, &i, "no longer examining game *")) {
4129                 if (gameMode == IcsExamining &&
4130                     atoi(star_match[0]) == ics_gamenum)
4131                   {
4132                       gameMode = IcsIdle;
4133                       ics_gamenum = -1;
4134                       ics_user_moved = FALSE;
4135                   }
4136                 continue;
4137             }
4138
4139             /* Advance leftover_start past any newlines we find,
4140                so only partial lines can get reparsed */
4141             if (looking_at(buf, &i, "\n")) {
4142                 prevColor = curColor;
4143                 if (curColor != ColorNormal) {
4144                     if (oldi > next_out) {
4145                         SendToPlayer(&buf[next_out], oldi - next_out);
4146                         next_out = oldi;
4147                     }
4148                     Colorize(ColorNormal, FALSE);
4149                     curColor = ColorNormal;
4150                 }
4151                 if (started == STARTED_BOARD) {
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     ParseBoard12(parse);
4155                     ics_user_moved = 0;
4156
4157                     /* Send premove here */
4158                     if (appData.premove) {
4159                       char str[MSG_SIZ];
4160                       if (currentMove == 0 &&
4161                           gameMode == IcsPlayingWhite &&
4162                           appData.premoveWhite) {
4163                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                         SendToICS(str);
4167                       } else if (currentMove == 1 &&
4168                                  gameMode == IcsPlayingBlack &&
4169                                  appData.premoveBlack) {
4170                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171                         if (appData.debugMode)
4172                           fprintf(debugFP, "Sending premove:\n");
4173                         SendToICS(str);
4174                       } else if (gotPremove) {
4175                         int oldFMM = forwardMostMove;
4176                         gotPremove = 0;
4177                         ClearPremoveHighlights();
4178                         if (appData.debugMode)
4179                           fprintf(debugFP, "Sending premove:\n");
4180                           UserMoveEvent(premoveFromX, premoveFromY,
4181                                         premoveToX, premoveToY,
4182                                         premovePromoChar);
4183                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184                           if(moveList[oldFMM-1][1] != '@')
4185                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                           else // (drop)
4188                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189                         }
4190                       }
4191                     }
4192
4193                     /* Usually suppress following prompt */
4194                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196                         if (looking_at(buf, &i, "*% ")) {
4197                             savingComment = FALSE;
4198                             suppressKibitz = 0;
4199                         }
4200                     }
4201                     next_out = i;
4202                 } else if (started == STARTED_HOLDINGS) {
4203                     int gamenum;
4204                     char new_piece[MSG_SIZ];
4205                     started = STARTED_NONE;
4206                     parse[parse_pos] = NULLCHAR;
4207                     if (appData.debugMode)
4208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209                                                         parse, currentMove);
4210                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4211                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         if (gameInfo.variant == VariantNormal) {
4219                           /* [HGM] We seem to switch variant during a game!
4220                            * Presumably no holdings were displayed, so we have
4221                            * to move the position two files to the right to
4222                            * create room for them!
4223                            */
4224                           VariantClass newVariant;
4225                           switch(gameInfo.boardWidth) { // base guess on board width
4226                                 case 9:  newVariant = VariantShogi; break;
4227                                 case 10: newVariant = VariantGreat; break;
4228                                 default: newVariant = VariantCrazyhouse;
4229                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4230                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4231                                          newVariant = VariantSChess;
4232                           }
4233                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234                           /* Get a move list just to see the header, which
4235                              will tell us whether this is really bug or zh */
4236                           if (ics_getting_history == H_FALSE) {
4237                             ics_getting_history = H_REQUESTED;
4238                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239                             SendToICS(str);
4240                           }
4241                         }
4242                         /* [HGM] copy holdings to board holdings area */
4243                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4246 #if ZIPPY
4247                         if (appData.zippyPlay && first.initDone) {
4248                             ZippyHoldings(white_holding, black_holding,
4249                                           new_piece);
4250                         }
4251 #endif /*ZIPPY*/
4252                         if (tinyLayout || smallLayout) {
4253                             char wh[16], bh[16];
4254                             PackHolding(wh, white_holding);
4255                             PackHolding(bh, black_holding);
4256                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257                                     gameInfo.white, gameInfo.black);
4258                         } else {
4259                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260                                     gameInfo.white, white_holding, _("vs."),
4261                                     gameInfo.black, black_holding);
4262                         }
4263                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264                         DrawPosition(FALSE, boards[currentMove]);
4265                         DisplayTitle(str);
4266                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267                         sscanf(parse, "game %d white [%s black [%s <- %s",
4268                                &gamenum, white_holding, black_holding,
4269                                new_piece);
4270                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4271                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4272                         /* [HGM] copy holdings to partner-board holdings area */
4273                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4278                       }
4279                     }
4280                     /* Suppress following prompt */
4281                     if (looking_at(buf, &i, "*% ")) {
4282                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283                         savingComment = FALSE;
4284                         suppressKibitz = 0;
4285                     }
4286                     next_out = i;
4287                 }
4288                 continue;
4289             }
4290
4291             i++;                /* skip unparsed character and loop back */
4292         }
4293
4294         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 //          SendToPlayer(&buf[next_out], i - next_out);
4297             started != STARTED_HOLDINGS && leftover_start > next_out) {
4298             SendToPlayer(&buf[next_out], leftover_start - next_out);
4299             next_out = i;
4300         }
4301
4302         leftover_len = buf_len - leftover_start;
4303         /* if buffer ends with something we couldn't parse,
4304            reparse it after appending the next read */
4305
4306     } else if (count == 0) {
4307         RemoveInputSource(isr);
4308         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4309     } else {
4310         DisplayFatalError(_("Error reading from ICS"), error, 1);
4311     }
4312 }
4313
4314
4315 /* Board style 12 looks like this:
4316
4317    <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
4318
4319  * The "<12> " is stripped before it gets to this routine.  The two
4320  * trailing 0's (flip state and clock ticking) are later addition, and
4321  * some chess servers may not have them, or may have only the first.
4322  * Additional trailing fields may be added in the future.
4323  */
4324
4325 #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"
4326
4327 #define RELATION_OBSERVING_PLAYED    0
4328 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE      1
4330 #define RELATION_PLAYING_NOTMYMOVE  -1
4331 #define RELATION_EXAMINING           2
4332 #define RELATION_ISOLATED_BOARD     -3
4333 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4334
4335 void
4336 ParseBoard12 (char *string)
4337 {
4338 #if ZIPPY
4339     int i, takeback;
4340     char *bookHit = NULL; // [HGM] book
4341 #endif
4342     GameMode newGameMode;
4343     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346     char to_play, board_chars[200];
4347     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348     char black[32], white[32];
4349     Board board;
4350     int prevMove = currentMove;
4351     int ticking = 2;
4352     ChessMove moveType;
4353     int fromX, fromY, toX, toY;
4354     char promoChar;
4355     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356     Boolean weird = FALSE, reqFlag = FALSE;
4357
4358     fromX = fromY = toX = toY = -1;
4359
4360     newGame = FALSE;
4361
4362     if (appData.debugMode)
4363       fprintf(debugFP, "Parsing board: %s\n", string);
4364
4365     move_str[0] = NULLCHAR;
4366     elapsed_time[0] = NULLCHAR;
4367     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4368         int  i = 0, j;
4369         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370             if(string[i] == ' ') { ranks++; files = 0; }
4371             else files++;
4372             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4373             i++;
4374         }
4375         for(j = 0; j <i; j++) board_chars[j] = string[j];
4376         board_chars[i] = '\0';
4377         string += i + 1;
4378     }
4379     n = sscanf(string, PATTERN, &to_play, &double_push,
4380                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381                &gamenum, white, black, &relation, &basetime, &increment,
4382                &white_stren, &black_stren, &white_time, &black_time,
4383                &moveNum, str, elapsed_time, move_str, &ics_flip,
4384                &ticking);
4385
4386     if (n < 21) {
4387         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388         DisplayError(str, 0);
4389         return;
4390     }
4391
4392     /* Convert the move number to internal form */
4393     moveNum = (moveNum - 1) * 2;
4394     if (to_play == 'B') moveNum++;
4395     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4397                         0, 1);
4398       return;
4399     }
4400
4401     switch (relation) {
4402       case RELATION_OBSERVING_PLAYED:
4403       case RELATION_OBSERVING_STATIC:
4404         if (gamenum == -1) {
4405             /* Old ICC buglet */
4406             relation = RELATION_OBSERVING_STATIC;
4407         }
4408         newGameMode = IcsObserving;
4409         break;
4410       case RELATION_PLAYING_MYMOVE:
4411       case RELATION_PLAYING_NOTMYMOVE:
4412         newGameMode =
4413           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414             IcsPlayingWhite : IcsPlayingBlack;
4415         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4416         break;
4417       case RELATION_EXAMINING:
4418         newGameMode = IcsExamining;
4419         break;
4420       case RELATION_ISOLATED_BOARD:
4421       default:
4422         /* Just display this board.  If user was doing something else,
4423            we will forget about it until the next board comes. */
4424         newGameMode = IcsIdle;
4425         break;
4426       case RELATION_STARTING_POSITION:
4427         newGameMode = gameMode;
4428         break;
4429     }
4430
4431     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436       static int lastBgGame = -1;
4437       char *toSqr;
4438       for (k = 0; k < ranks; k++) {
4439         for (j = 0; j < files; j++)
4440           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441         if(gameInfo.holdingsWidth > 1) {
4442              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4444         }
4445       }
4446       CopyBoard(partnerBoard, board);
4447       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451       if(toSqr = strchr(str, '-')) {
4452         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4458       if(twoBoards) {
4459           DisplayWhiteClock(white_time*fac, to_play == 'W');
4460           DisplayBlackClock(black_time*fac, to_play != 'W');
4461           activePartner = to_play;
4462           if(gamenum != lastBgGame) {
4463               char buf[MSG_SIZ];
4464               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4465               DisplayTitle(buf);
4466           }
4467           lastBgGame = gamenum;
4468           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472       if(!twoBoards) DisplayMessage(partnerStatus, "");
4473         partnerBoardValid = TRUE;
4474       return;
4475     }
4476
4477     if(appData.dualBoard && appData.bgObserve) {
4478         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479             SendToICS(ics_prefix), SendToICS("pobserve\n");
4480         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4481             char buf[MSG_SIZ];
4482             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4483             SendToICS(buf);
4484         }
4485     }
4486
4487     /* Modify behavior for initial board display on move listing
4488        of wild games.
4489        */
4490     switch (ics_getting_history) {
4491       case H_FALSE:
4492       case H_REQUESTED:
4493         break;
4494       case H_GOT_REQ_HEADER:
4495       case H_GOT_UNREQ_HEADER:
4496         /* This is the initial position of the current game */
4497         gamenum = ics_gamenum;
4498         moveNum = 0;            /* old ICS bug workaround */
4499         if (to_play == 'B') {
4500           startedFromSetupPosition = TRUE;
4501           blackPlaysFirst = TRUE;
4502           moveNum = 1;
4503           if (forwardMostMove == 0) forwardMostMove = 1;
4504           if (backwardMostMove == 0) backwardMostMove = 1;
4505           if (currentMove == 0) currentMove = 1;
4506         }
4507         newGameMode = gameMode;
4508         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4509         break;
4510       case H_GOT_UNWANTED_HEADER:
4511         /* This is an initial board that we don't want */
4512         return;
4513       case H_GETTING_MOVES:
4514         /* Should not happen */
4515         DisplayError(_("Error gathering move list: extra board"), 0);
4516         ics_getting_history = H_FALSE;
4517         return;
4518     }
4519
4520    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4523      /* [HGM] We seem to have switched variant unexpectedly
4524       * Try to guess new variant from board size
4525       */
4526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4531           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534           /* Get a move list just to see the header, which
4535              will tell us whether this is really bug or zh */
4536           if (ics_getting_history == H_FALSE) {
4537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540           }
4541     }
4542
4543     /* Take action if this is the first board of a new game, or of a
4544        different game than is currently being displayed.  */
4545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546         relation == RELATION_ISOLATED_BOARD) {
4547
4548         /* Forget the old game and get the history (if any) of the new one */
4549         if (gameMode != BeginningOfGame) {
4550           Reset(TRUE, TRUE);
4551         }
4552         newGame = TRUE;
4553         if (appData.autoRaiseBoard) BoardToTop();
4554         prevMove = -3;
4555         if (gamenum == -1) {
4556             newGameMode = IcsIdle;
4557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558                    appData.getMoveList && !reqFlag) {
4559             /* Need to get game history */
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564
4565         /* Initially flip the board to have black on the bottom if playing
4566            black or if the ICS flip flag is set, but let the user change
4567            it with the Flip View button. */
4568         flipView = appData.autoFlipView ?
4569           (newGameMode == IcsPlayingBlack) || ics_flip :
4570           appData.flipView;
4571
4572         /* Done with values from previous mode; copy in new ones */
4573         gameMode = newGameMode;
4574         ModeHighlight();
4575         ics_gamenum = gamenum;
4576         if (gamenum == gs_gamenum) {
4577             int klen = strlen(gs_kind);
4578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580             gameInfo.event = StrSave(str);
4581         } else {
4582             gameInfo.event = StrSave("ICS game");
4583         }
4584         gameInfo.site = StrSave(appData.icsHost);
4585         gameInfo.date = PGNDate();
4586         gameInfo.round = StrSave("-");
4587         gameInfo.white = StrSave(white);
4588         gameInfo.black = StrSave(black);
4589         timeControl = basetime * 60 * 1000;
4590         timeControl_2 = 0;
4591         timeIncrement = increment * 1000;
4592         movesPerSession = 0;
4593         gameInfo.timeControl = TimeControlTagValue();
4594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595   if (appData.debugMode) {
4596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598     setbuf(debugFP, NULL);
4599   }
4600
4601         gameInfo.outOfBook = NULL;
4602
4603         /* Do we have the ratings? */
4604         if (strcmp(player1Name, white) == 0 &&
4605             strcmp(player2Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player1Rating, player2Rating);
4609             gameInfo.whiteRating = player1Rating;
4610             gameInfo.blackRating = player2Rating;
4611         } else if (strcmp(player2Name, white) == 0 &&
4612                    strcmp(player1Name, black) == 0) {
4613             if (appData.debugMode)
4614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615                       player2Rating, player1Rating);
4616             gameInfo.whiteRating = player2Rating;
4617             gameInfo.blackRating = player1Rating;
4618         }
4619         player1Name[0] = player2Name[0] = NULLCHAR;
4620
4621         /* Silence shouts if requested */
4622         if (appData.quietPlay &&
4623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624             SendToICS(ics_prefix);
4625             SendToICS("set shout 0\n");
4626         }
4627     }
4628
4629     /* Deal with midgame name changes */
4630     if (!newGame) {
4631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632             if (gameInfo.white) free(gameInfo.white);
4633             gameInfo.white = StrSave(white);
4634         }
4635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636             if (gameInfo.black) free(gameInfo.black);
4637             gameInfo.black = StrSave(black);
4638         }
4639     }
4640
4641     /* Throw away game result if anything actually changes in examine mode */
4642     if (gameMode == IcsExamining && !newGame) {
4643         gameInfo.result = GameUnfinished;
4644         if (gameInfo.resultDetails != NULL) {
4645             free(gameInfo.resultDetails);
4646             gameInfo.resultDetails = NULL;
4647         }
4648     }
4649
4650     /* In pausing && IcsExamining mode, we ignore boards coming
4651        in if they are in a different variation than we are. */
4652     if (pauseExamInvalid) return;
4653     if (pausing && gameMode == IcsExamining) {
4654         if (moveNum <= pauseExamForwardMostMove) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659     }
4660
4661   if (appData.debugMode) {
4662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4663   }
4664     /* Parse the board */
4665     for (k = 0; k < ranks; k++) {
4666       for (j = 0; j < files; j++)
4667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668       if(gameInfo.holdingsWidth > 1) {
4669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4671       }
4672     }
4673     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674       board[5][BOARD_RGHT+1] = WhiteAngel;
4675       board[6][BOARD_RGHT+1] = WhiteMarshall;
4676       board[1][0] = BlackMarshall;
4677       board[2][0] = BlackAngel;
4678       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4679     }
4680     CopyBoard(boards[moveNum], board);
4681     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4682     if (moveNum == 0) {
4683         startedFromSetupPosition =
4684           !CompareBoards(board, initialPosition);
4685         if(startedFromSetupPosition)
4686             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4687     }
4688
4689     /* [HGM] Set castling rights. Take the outermost Rooks,
4690        to make it also work for FRC opening positions. Note that board12
4691        is really defective for later FRC positions, as it has no way to
4692        indicate which Rook can castle if they are on the same side of King.
4693        For the initial position we grant rights to the outermost Rooks,
4694        and remember thos rights, and we then copy them on positions
4695        later in an FRC game. This means WB might not recognize castlings with
4696        Rooks that have moved back to their original position as illegal,
4697        but in ICS mode that is not its job anyway.
4698     */
4699     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4701
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[0][i] == WhiteRook) j = i;
4704         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[0][i] == WhiteRook) j = i;
4707         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4714
4715         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720             if(board[BOARD_HEIGHT-1][k] == bKing)
4721                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722         if(gameInfo.variant == VariantTwoKings) {
4723             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4726         }
4727     } else { int r;
4728         r = boards[moveNum][CASTLING][0] = initialRights[0];
4729         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730         r = boards[moveNum][CASTLING][1] = initialRights[1];
4731         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732         r = boards[moveNum][CASTLING][3] = initialRights[3];
4733         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734         r = boards[moveNum][CASTLING][4] = initialRights[4];
4735         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736         /* wildcastle kludge: always assume King has rights */
4737         r = boards[moveNum][CASTLING][2] = initialRights[2];
4738         r = boards[moveNum][CASTLING][5] = initialRights[5];
4739     }
4740     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741     boards[moveNum][EP_STATUS] = EP_NONE;
4742     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4745
4746
4747     if (ics_getting_history == H_GOT_REQ_HEADER ||
4748         ics_getting_history == H_GOT_UNREQ_HEADER) {
4749         /* This was an initial position from a move list, not
4750            the current position */
4751         return;
4752     }
4753
4754     /* Update currentMove and known move number limits */
4755     newMove = newGame || moveNum > forwardMostMove;
4756
4757     if (newGame) {
4758         forwardMostMove = backwardMostMove = currentMove = moveNum;
4759         if (gameMode == IcsExamining && moveNum == 0) {
4760           /* Workaround for ICS limitation: we are not told the wild
4761              type when starting to examine a game.  But if we ask for
4762              the move list, the move list header will tell us */
4763             ics_getting_history = H_REQUESTED;
4764             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4765             SendToICS(str);
4766         }
4767     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4768                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4769 #if ZIPPY
4770         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4771         /* [HGM] applied this also to an engine that is silently watching        */
4772         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4773             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4774             gameInfo.variant == currentlyInitializedVariant) {
4775           takeback = forwardMostMove - moveNum;
4776           for (i = 0; i < takeback; i++) {
4777             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4778             SendToProgram("undo\n", &first);
4779           }
4780         }
4781 #endif
4782
4783         forwardMostMove = moveNum;
4784         if (!pausing || currentMove > forwardMostMove)
4785           currentMove = forwardMostMove;
4786     } else {
4787         /* New part of history that is not contiguous with old part */
4788         if (pausing && gameMode == IcsExamining) {
4789             pauseExamInvalid = TRUE;
4790             forwardMostMove = pauseExamForwardMostMove;
4791             return;
4792         }
4793         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4794 #if ZIPPY
4795             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4796                 // [HGM] when we will receive the move list we now request, it will be
4797                 // fed to the engine from the first move on. So if the engine is not
4798                 // in the initial position now, bring it there.
4799                 InitChessProgram(&first, 0);
4800             }
4801 #endif
4802             ics_getting_history = H_REQUESTED;
4803             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4804             SendToICS(str);
4805         }
4806         forwardMostMove = backwardMostMove = currentMove = moveNum;
4807     }
4808
4809     /* Update the clocks */
4810     if (strchr(elapsed_time, '.')) {
4811       /* Time is in ms */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4814     } else {
4815       /* Time is in seconds */
4816       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4817       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4818     }
4819
4820
4821 #if ZIPPY
4822     if (appData.zippyPlay && newGame &&
4823         gameMode != IcsObserving && gameMode != IcsIdle &&
4824         gameMode != IcsExamining)
4825       ZippyFirstBoard(moveNum, basetime, increment);
4826 #endif
4827
4828     /* Put the move on the move list, first converting
4829        to canonical algebraic form. */
4830     if (moveNum > 0) {
4831   if (appData.debugMode) {
4832     int f = forwardMostMove;
4833     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4834             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4835             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4836     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4837     fprintf(debugFP, "moveNum = %d\n", moveNum);
4838     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4839     setbuf(debugFP, NULL);
4840   }
4841         if (moveNum <= backwardMostMove) {
4842             /* We don't know what the board looked like before
4843                this move.  Punt. */
4844           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4845             strcat(parseList[moveNum - 1], " ");
4846             strcat(parseList[moveNum - 1], elapsed_time);
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848         } else if (strcmp(move_str, "none") == 0) {
4849             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4850             /* Again, we don't know what the board looked like;
4851                this is really the start of the game. */
4852             parseList[moveNum - 1][0] = NULLCHAR;
4853             moveList[moveNum - 1][0] = NULLCHAR;
4854             backwardMostMove = moveNum;
4855             startedFromSetupPosition = TRUE;
4856             fromX = fromY = toX = toY = -1;
4857         } else {
4858           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4859           //                 So we parse the long-algebraic move string in stead of the SAN move
4860           int valid; char buf[MSG_SIZ], *prom;
4861
4862           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4863                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4864           // str looks something like "Q/a1-a2"; kill the slash
4865           if(str[1] == '/')
4866             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4867           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4868           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4869                 strcat(buf, prom); // long move lacks promo specification!
4870           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4871                 if(appData.debugMode)
4872                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4873                 safeStrCpy(move_str, buf, MSG_SIZ);
4874           }
4875           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4876                                 &fromX, &fromY, &toX, &toY, &promoChar)
4877                || ParseOneMove(buf, moveNum - 1, &moveType,
4878                                 &fromX, &fromY, &toX, &toY, &promoChar);
4879           // end of long SAN patch
4880           if (valid) {
4881             (void) CoordsToAlgebraic(boards[moveNum - 1],
4882                                      PosFlags(moveNum - 1),
4883                                      fromY, fromX, toY, toX, promoChar,
4884                                      parseList[moveNum-1]);
4885             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4886               case MT_NONE:
4887               case MT_STALEMATE:
4888               default:
4889                 break;
4890               case MT_CHECK:
4891                 if(!IS_SHOGI(gameInfo.variant))
4892                     strcat(parseList[moveNum - 1], "+");
4893                 break;
4894               case MT_CHECKMATE:
4895               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4896                 strcat(parseList[moveNum - 1], "#");
4897                 break;
4898             }
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             /* currentMoveString is set as a side-effect of ParseOneMove */
4902             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4903             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4904             strcat(moveList[moveNum - 1], "\n");
4905
4906             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4907                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4908               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4909                 ChessSquare old, new = boards[moveNum][k][j];
4910                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4911                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4912                   if(old == new) continue;
4913                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4914                   else if(new == WhiteWazir || new == BlackWazir) {
4915                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4916                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4917                       else boards[moveNum][k][j] = old; // preserve type of Gold
4918                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4919                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4920               }
4921           } else {
4922             /* Move from ICS was illegal!?  Punt. */
4923             if (appData.debugMode) {
4924               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4925               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4926             }
4927             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4928             strcat(parseList[moveNum - 1], " ");
4929             strcat(parseList[moveNum - 1], elapsed_time);
4930             moveList[moveNum - 1][0] = NULLCHAR;
4931             fromX = fromY = toX = toY = -1;
4932           }
4933         }
4934   if (appData.debugMode) {
4935     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4936     setbuf(debugFP, NULL);
4937   }
4938
4939 #if ZIPPY
4940         /* Send move to chess program (BEFORE animating it). */
4941         if (appData.zippyPlay && !newGame && newMove &&
4942            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4943
4944             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4945                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4946                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4947                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4948                             move_str);
4949                     DisplayError(str, 0);
4950                 } else {
4951                     if (first.sendTime) {
4952                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4953                     }
4954                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4955                     if (firstMove && !bookHit) {
4956                         firstMove = FALSE;
4957                         if (first.useColors) {
4958                           SendToProgram(gameMode == IcsPlayingWhite ?
4959                                         "white\ngo\n" :
4960                                         "black\ngo\n", &first);
4961                         } else {
4962                           SendToProgram("go\n", &first);
4963                         }
4964                         first.maybeThinking = TRUE;
4965                     }
4966                 }
4967             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4968               if (moveList[moveNum - 1][0] == NULLCHAR) {
4969                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4970                 DisplayError(str, 0);
4971               } else {
4972                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4973                 SendMoveToProgram(moveNum - 1, &first);
4974               }
4975             }
4976         }
4977 #endif
4978     }
4979
4980     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4981         /* If move comes from a remote source, animate it.  If it
4982            isn't remote, it will have already been animated. */
4983         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4984             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4985         }
4986         if (!pausing && appData.highlightLastMove) {
4987             SetHighlights(fromX, fromY, toX, toY);
4988         }
4989     }
4990
4991     /* Start the clocks */
4992     whiteFlag = blackFlag = FALSE;
4993     appData.clockMode = !(basetime == 0 && increment == 0);
4994     if (ticking == 0) {
4995       ics_clock_paused = TRUE;
4996       StopClocks();
4997     } else if (ticking == 1) {
4998       ics_clock_paused = FALSE;
4999     }
5000     if (gameMode == IcsIdle ||
5001         relation == RELATION_OBSERVING_STATIC ||
5002         relation == RELATION_EXAMINING ||
5003         ics_clock_paused)
5004       DisplayBothClocks();
5005     else
5006       StartClocks();
5007
5008     /* Display opponents and material strengths */
5009     if (gameInfo.variant != VariantBughouse &&
5010         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5011         if (tinyLayout || smallLayout) {
5012             if(gameInfo.variant == VariantNormal)
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment);
5016             else
5017               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5018                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5019                     basetime, increment, (int) gameInfo.variant);
5020         } else {
5021             if(gameInfo.variant == VariantNormal)
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment);
5025             else
5026               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5027                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5028                     basetime, increment, VariantName(gameInfo.variant));
5029         }
5030         DisplayTitle(str);
5031   if (appData.debugMode) {
5032     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5033   }
5034     }
5035
5036
5037     /* Display the board */
5038     if (!pausing && !appData.noGUI) {
5039
5040       if (appData.premove)
5041           if (!gotPremove ||
5042              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5043              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5044               ClearPremoveHighlights();
5045
5046       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5047         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5048       DrawPosition(j, boards[currentMove]);
5049
5050       DisplayMove(moveNum - 1);
5051       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5052             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5053               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5054         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5055       }
5056     }
5057
5058     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5059 #if ZIPPY
5060     if(bookHit) { // [HGM] book: simulate book reply
5061         static char bookMove[MSG_SIZ]; // a bit generous?
5062
5063         programStats.nodes = programStats.depth = programStats.time =
5064         programStats.score = programStats.got_only_move = 0;
5065         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5066
5067         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5068         strcat(bookMove, bookHit);
5069         HandleMachineMove(bookMove, &first);
5070     }
5071 #endif
5072 }
5073
5074 void
5075 GetMoveListEvent ()
5076 {
5077     char buf[MSG_SIZ];
5078     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5079         ics_getting_history = H_REQUESTED;
5080         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5081         SendToICS(buf);
5082     }
5083 }
5084
5085 void
5086 SendToBoth (char *msg)
5087 {   // to make it easy to keep two engines in step in dual analysis
5088     SendToProgram(msg, &first);
5089     if(second.analyzing) SendToProgram(msg, &second);
5090 }
5091
5092 void
5093 AnalysisPeriodicEvent (int force)
5094 {
5095     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5096          && !force) || !appData.periodicUpdates)
5097       return;
5098
5099     /* Send . command to Crafty to collect stats */
5100     SendToBoth(".\n");
5101
5102     /* Don't send another until we get a response (this makes
5103        us stop sending to old Crafty's which don't understand
5104        the "." command (sending illegal cmds resets node count & time,
5105        which looks bad)) */
5106     programStats.ok_to_send = 0;
5107 }
5108
5109 void
5110 ics_update_width (int new_width)
5111 {
5112         ics_printf("set width %d\n", new_width);
5113 }
5114
5115 void
5116 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5121         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5122             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5123             SendToProgram(buf, cps);
5124             return;
5125         }
5126         // null move in variant where engine does not understand it (for analysis purposes)
5127         SendBoard(cps, moveNum + 1); // send position after move in stead.
5128         return;
5129     }
5130     if (cps->useUsermove) {
5131       SendToProgram("usermove ", cps);
5132     }
5133     if (cps->useSAN) {
5134       char *space;
5135       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5136         int len = space - parseList[moveNum];
5137         memcpy(buf, parseList[moveNum], len);
5138         buf[len++] = '\n';
5139         buf[len] = NULLCHAR;
5140       } else {
5141         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5142       }
5143       SendToProgram(buf, cps);
5144     } else {
5145       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5146         AlphaRank(moveList[moveNum], 4);
5147         SendToProgram(moveList[moveNum], cps);
5148         AlphaRank(moveList[moveNum], 4); // and back
5149       } else
5150       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5151        * the engine. It would be nice to have a better way to identify castle
5152        * moves here. */
5153       if(appData.fischerCastling && cps->useOOCastle) {
5154         int fromX = moveList[moveNum][0] - AAA;
5155         int fromY = moveList[moveNum][1] - ONE;
5156         int toX = moveList[moveNum][2] - AAA;
5157         int toY = moveList[moveNum][3] - ONE;
5158         if((boards[moveNum][fromY][fromX] == WhiteKing
5159             && boards[moveNum][toY][toX] == WhiteRook)
5160            || (boards[moveNum][fromY][fromX] == BlackKing
5161                && boards[moveNum][toY][toX] == BlackRook)) {
5162           if(toX > fromX) SendToProgram("O-O\n", cps);
5163           else SendToProgram("O-O-O\n", cps);
5164         }
5165         else SendToProgram(moveList[moveNum], cps);
5166       } else
5167       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5168         char *m = moveList[moveNum];
5169         static char c[2];
5170         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5171         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
5172           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5173                                                m[2], m[3] - '0',
5174                                                m[5], m[6] - '0',
5175                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5176         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5177           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5178           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
5179                                                m[7], m[8] - '0',
5180                                                m[7], m[8] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5186                                                m[5], m[6] - '0',
5187                                                m[5], m[6] - '0',
5188                                                m[2], m[3] - '0', c);
5189           SendToProgram(buf, cps);
5190       } else
5191       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5192         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5193           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5194           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5195                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5196         } else
5197           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5198                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5199         SendToProgram(buf, cps);
5200       }
5201       else SendToProgram(moveList[moveNum], cps);
5202       /* End of additions by Tord */
5203     }
5204
5205     /* [HGM] setting up the opening has brought engine in force mode! */
5206     /*       Send 'go' if we are in a mode where machine should play. */
5207     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5208         (gameMode == TwoMachinesPlay   ||
5209 #if ZIPPY
5210          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5211 #endif
5212          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5213         SendToProgram("go\n", cps);
5214   if (appData.debugMode) {
5215     fprintf(debugFP, "(extra)\n");
5216   }
5217     }
5218     setboardSpoiledMachineBlack = 0;
5219 }
5220
5221 void
5222 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5223 {
5224     char user_move[MSG_SIZ];
5225     char suffix[4];
5226
5227     if(gameInfo.variant == VariantSChess && promoChar) {
5228         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5229         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5230     } else suffix[0] = NULLCHAR;
5231
5232     switch (moveType) {
5233       default:
5234         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5235                 (int)moveType, fromX, fromY, toX, toY);
5236         DisplayError(user_move + strlen("say "), 0);
5237         break;
5238       case WhiteKingSideCastle:
5239       case BlackKingSideCastle:
5240       case WhiteQueenSideCastleWild:
5241       case BlackQueenSideCastleWild:
5242       /* PUSH Fabien */
5243       case WhiteHSideCastleFR:
5244       case BlackHSideCastleFR:
5245       /* POP Fabien */
5246         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5247         break;
5248       case WhiteQueenSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case BlackKingSideCastleWild:
5252       /* PUSH Fabien */
5253       case WhiteASideCastleFR:
5254       case BlackASideCastleFR:
5255       /* POP Fabien */
5256         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5257         break;
5258       case WhiteNonPromotion:
5259       case BlackNonPromotion:
5260         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262       case WhitePromotion:
5263       case BlackPromotion:
5264         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5265            gameInfo.variant == VariantMakruk)
5266           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteFerz));
5269         else if(gameInfo.variant == VariantGreat)
5270           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 PieceToChar(WhiteMan));
5273         else
5274           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5275                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5276                 promoChar);
5277         break;
5278       case WhiteDrop:
5279       case BlackDrop:
5280       drop:
5281         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5282                  ToUpper(PieceToChar((ChessSquare) fromX)),
5283                  AAA + toX, ONE + toY);
5284         break;
5285       case IllegalMove:  /* could be a variant we don't quite understand */
5286         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5287       case NormalMove:
5288       case WhiteCapturesEnPassant:
5289       case BlackCapturesEnPassant:
5290         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5291                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5292         break;
5293     }
5294     SendToICS(user_move);
5295     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5296         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5297 }
5298
5299 void
5300 UploadGameEvent ()
5301 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5302     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5303     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5304     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5305       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5306       return;
5307     }
5308     if(gameMode != IcsExamining) { // is this ever not the case?
5309         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5310
5311         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5312           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5313         } else { // on FICS we must first go to general examine mode
5314           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5315         }
5316         if(gameInfo.variant != VariantNormal) {
5317             // try figure out wild number, as xboard names are not always valid on ICS
5318             for(i=1; i<=36; i++) {
5319               snprintf(buf, MSG_SIZ, "wild/%d", i);
5320                 if(StringToVariant(buf) == gameInfo.variant) break;
5321             }
5322             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5323             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5324             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5325         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5326         SendToICS(ics_prefix);
5327         SendToICS(buf);
5328         if(startedFromSetupPosition || backwardMostMove != 0) {
5329           fen = PositionToFEN(backwardMostMove, NULL, 1);
5330           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5331             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5332             SendToICS(buf);
5333           } else { // FICS: everything has to set by separate bsetup commands
5334             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5335             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5336             SendToICS(buf);
5337             if(!WhiteOnMove(backwardMostMove)) {
5338                 SendToICS("bsetup tomove black\n");
5339             }
5340             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5341             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5342             SendToICS(buf);
5343             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5344             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5345             SendToICS(buf);
5346             i = boards[backwardMostMove][EP_STATUS];
5347             if(i >= 0) { // set e.p.
5348               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5349                 SendToICS(buf);
5350             }
5351             bsetup++;
5352           }
5353         }
5354       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5355             SendToICS("bsetup done\n"); // switch to normal examining.
5356     }
5357     for(i = backwardMostMove; i<last; i++) {
5358         char buf[20];
5359         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5360         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5361             int len = strlen(moveList[i]);
5362             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5363             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5364         }
5365         SendToICS(buf);
5366     }
5367     SendToICS(ics_prefix);
5368     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5369 }
5370
5371 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5372 int legNr = 1;
5373
5374 void
5375 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5376 {
5377     if (rf == DROP_RANK) {
5378       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5379       sprintf(move, "%c@%c%c\n",
5380                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5381     } else {
5382         if (promoChar == 'x' || promoChar == NULLCHAR) {
5383           sprintf(move, "%c%c%c%c\n",
5384                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5385           if(killX >= 0 && killY >= 0) {
5386             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5388           }
5389         } else {
5390             sprintf(move, "%c%c%c%c%c\n",
5391                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5392           if(killX >= 0 && killY >= 0) {
5393             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5394             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5395           }
5396         }
5397     }
5398 }
5399
5400 void
5401 ProcessICSInitScript (FILE *f)
5402 {
5403     char buf[MSG_SIZ];
5404
5405     while (fgets(buf, MSG_SIZ, f)) {
5406         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5407     }
5408
5409     fclose(f);
5410 }
5411
5412
5413 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5414 int dragging;
5415 static ClickType lastClickType;
5416
5417 int
5418 PieceInString (char *s, ChessSquare piece)
5419 {
5420   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5421   while((p = strchr(s, ID))) {
5422     if(!suffix || p[1] == suffix) return TRUE;
5423     s = p;
5424   }
5425   return FALSE;
5426 }
5427
5428 int
5429 Partner (ChessSquare *p)
5430 { // change piece into promotion partner if one shogi-promotes to the other
5431   ChessSquare partner = promoPartner[*p];
5432   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5433   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5434   *p = partner;
5435   return 1;
5436 }
5437
5438 void
5439 Sweep (int step)
5440 {
5441     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5442     static int toggleFlag;
5443     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5444     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5445     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5446     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5447     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5448     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5449     do {
5450         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5451         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5452         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5453         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5454         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5455         if(!step) step = -1;
5456     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5457             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5458             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5459             promoSweep == pawn ||
5460             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5461             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5462     if(toX >= 0) {
5463         int victim = boards[currentMove][toY][toX];
5464         boards[currentMove][toY][toX] = promoSweep;
5465         DrawPosition(FALSE, boards[currentMove]);
5466         boards[currentMove][toY][toX] = victim;
5467     } else
5468     ChangeDragPiece(promoSweep);
5469 }
5470
5471 int
5472 PromoScroll (int x, int y)
5473 {
5474   int step = 0;
5475
5476   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5477   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5478   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5479   if(!step) return FALSE;
5480   lastX = x; lastY = y;
5481   if((promoSweep < BlackPawn) == flipView) step = -step;
5482   if(step > 0) selectFlag = 1;
5483   if(!selectFlag) Sweep(step);
5484   return FALSE;
5485 }
5486
5487 void
5488 NextPiece (int step)
5489 {
5490     ChessSquare piece = boards[currentMove][toY][toX];
5491     do {
5492         pieceSweep -= step;
5493         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5494         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5495         if(!step) step = -1;
5496     } while(PieceToChar(pieceSweep) == '.');
5497     boards[currentMove][toY][toX] = pieceSweep;
5498     DrawPosition(FALSE, boards[currentMove]);
5499     boards[currentMove][toY][toX] = piece;
5500 }
5501 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5502 void
5503 AlphaRank (char *move, int n)
5504 {
5505 //    char *p = move, c; int x, y;
5506
5507     if (appData.debugMode) {
5508         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5509     }
5510
5511     if(move[1]=='*' &&
5512        move[2]>='0' && move[2]<='9' &&
5513        move[3]>='a' && move[3]<='x'    ) {
5514         move[1] = '@';
5515         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5516         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5517     } else
5518     if(move[0]>='0' && move[0]<='9' &&
5519        move[1]>='a' && move[1]<='x' &&
5520        move[2]>='0' && move[2]<='9' &&
5521        move[3]>='a' && move[3]<='x'    ) {
5522         /* input move, Shogi -> normal */
5523         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5524         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5525         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5526         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5527     } else
5528     if(move[1]=='@' &&
5529        move[3]>='0' && move[3]<='9' &&
5530        move[2]>='a' && move[2]<='x'    ) {
5531         move[1] = '*';
5532         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5533         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5534     } else
5535     if(
5536        move[0]>='a' && move[0]<='x' &&
5537        move[3]>='0' && move[3]<='9' &&
5538        move[2]>='a' && move[2]<='x'    ) {
5539          /* output move, normal -> Shogi */
5540         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5541         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5542         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5543         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5544         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5545     }
5546     if (appData.debugMode) {
5547         fprintf(debugFP, "   out = '%s'\n", move);
5548     }
5549 }
5550
5551 char yy_textstr[8000];
5552
5553 /* Parser for moves from gnuchess, ICS, or user typein box */
5554 Boolean
5555 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5556 {
5557     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5558
5559     switch (*moveType) {
5560       case WhitePromotion:
5561       case BlackPromotion:
5562       case WhiteNonPromotion:
5563       case BlackNonPromotion:
5564       case NormalMove:
5565       case FirstLeg:
5566       case WhiteCapturesEnPassant:
5567       case BlackCapturesEnPassant:
5568       case WhiteKingSideCastle:
5569       case WhiteQueenSideCastle:
5570       case BlackKingSideCastle:
5571       case BlackQueenSideCastle:
5572       case WhiteKingSideCastleWild:
5573       case WhiteQueenSideCastleWild:
5574       case BlackKingSideCastleWild:
5575       case BlackQueenSideCastleWild:
5576       /* Code added by Tord: */
5577       case WhiteHSideCastleFR:
5578       case WhiteASideCastleFR:
5579       case BlackHSideCastleFR:
5580       case BlackASideCastleFR:
5581       /* End of code added by Tord */
5582       case IllegalMove:         /* bug or odd chess variant */
5583         if(currentMoveString[1] == '@') { // illegal drop
5584           *fromX = WhiteOnMove(moveNum) ?
5585             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5586             (int) CharToPiece(ToLower(currentMoveString[0]));
5587           goto drop;
5588         }
5589         *fromX = currentMoveString[0] - AAA;
5590         *fromY = currentMoveString[1] - ONE;
5591         *toX = currentMoveString[2] - AAA;
5592         *toY = currentMoveString[3] - ONE;
5593         *promoChar = currentMoveString[4];
5594         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5595         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5596             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5597     if (appData.debugMode) {
5598         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5599     }
5600             *fromX = *fromY = *toX = *toY = 0;
5601             return FALSE;
5602         }
5603         if (appData.testLegality) {
5604           return (*moveType != IllegalMove);
5605         } else {
5606           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5607                          // [HGM] lion: if this is a double move we are less critical
5608                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5609         }
5610
5611       case WhiteDrop:
5612       case BlackDrop:
5613         *fromX = *moveType == WhiteDrop ?
5614           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5615           (int) CharToPiece(ToLower(currentMoveString[0]));
5616       drop:
5617         *fromY = DROP_RANK;
5618         *toX = currentMoveString[2] - AAA;
5619         *toY = currentMoveString[3] - ONE;
5620         *promoChar = NULLCHAR;
5621         return TRUE;
5622
5623       case AmbiguousMove:
5624       case ImpossibleMove:
5625       case EndOfFile:
5626       case ElapsedTime:
5627       case Comment:
5628       case PGNTag:
5629       case NAG:
5630       case WhiteWins:
5631       case BlackWins:
5632       case GameIsDrawn:
5633       default:
5634     if (appData.debugMode) {
5635         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5636     }
5637         /* bug? */
5638         *fromX = *fromY = *toX = *toY = 0;
5639         *promoChar = NULLCHAR;
5640         return FALSE;
5641     }
5642 }
5643
5644 Boolean pushed = FALSE;
5645 char *lastParseAttempt;
5646
5647 void
5648 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5649 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5650   int fromX, fromY, toX, toY; char promoChar;
5651   ChessMove moveType;
5652   Boolean valid;
5653   int nr = 0;
5654
5655   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5656   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5657     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5658     pushed = TRUE;
5659   }
5660   endPV = forwardMostMove;
5661   do {
5662     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5663     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5664     lastParseAttempt = pv;
5665     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5666     if(!valid && nr == 0 &&
5667        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5668         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5669         // Hande case where played move is different from leading PV move
5670         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5671         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5672         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5673         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5674           endPV += 2; // if position different, keep this
5675           moveList[endPV-1][0] = fromX + AAA;
5676           moveList[endPV-1][1] = fromY + ONE;
5677           moveList[endPV-1][2] = toX + AAA;
5678           moveList[endPV-1][3] = toY + ONE;
5679           parseList[endPV-1][0] = NULLCHAR;
5680           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5681         }
5682       }
5683     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5684     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5685     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5686     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5687         valid++; // allow comments in PV
5688         continue;
5689     }
5690     nr++;
5691     if(endPV+1 > framePtr) break; // no space, truncate
5692     if(!valid) break;
5693     endPV++;
5694     CopyBoard(boards[endPV], boards[endPV-1]);
5695     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5696     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5697     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5698     CoordsToAlgebraic(boards[endPV - 1],
5699                              PosFlags(endPV - 1),
5700                              fromY, fromX, toY, toX, promoChar,
5701                              parseList[endPV - 1]);
5702   } while(valid);
5703   if(atEnd == 2) return; // used hidden, for PV conversion
5704   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5705   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5706   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5707                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5708   DrawPosition(TRUE, boards[currentMove]);
5709 }
5710
5711 int
5712 MultiPV (ChessProgramState *cps, int kind)
5713 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5714         int i;
5715         for(i=0; i<cps->nrOptions; i++) {
5716             char *s = cps->option[i].name;
5717             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5718             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5719                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5720         }
5721         return -1;
5722 }
5723
5724 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5725 static int multi, pv_margin;
5726 static ChessProgramState *activeCps;
5727
5728 Boolean
5729 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5730 {
5731         int startPV, lineStart, origIndex = index;
5732         char *p, buf2[MSG_SIZ];
5733         ChessProgramState *cps = (pane ? &second : &first);
5734
5735         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5736         lastX = x; lastY = y;
5737         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5738         lineStart = startPV = index;
5739         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5740         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5741         index = startPV;
5742         do{ while(buf[index] && buf[index] != '\n') index++;
5743         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5744         buf[index] = 0;
5745         if(lineStart == 0 && gameMode == AnalyzeMode) {
5746             int n = 0;
5747             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5748             if(n == 0) { // click not on "fewer" or "more"
5749                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5750                     pv_margin = cps->option[multi].value;
5751                     activeCps = cps; // non-null signals margin adjustment
5752                 }
5753             } else if((multi = MultiPV(cps, 1)) >= 0) {
5754                 n += cps->option[multi].value; if(n < 1) n = 1;
5755                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5756                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5757                 cps->option[multi].value = n;
5758                 *start = *end = 0;
5759                 return FALSE;
5760             }
5761         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5762                 ExcludeClick(origIndex - lineStart);
5763                 return FALSE;
5764         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5765                 Collapse(origIndex - lineStart);
5766                 return FALSE;
5767         }
5768         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5769         *start = startPV; *end = index-1;
5770         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5771         return TRUE;
5772 }
5773
5774 char *
5775 PvToSAN (char *pv)
5776 {
5777         static char buf[10*MSG_SIZ];
5778         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5779         *buf = NULLCHAR;
5780         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5781         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5782         for(i = forwardMostMove; i<endPV; i++){
5783             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5784             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5785             k += strlen(buf+k);
5786         }
5787         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5788         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5789         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5790         endPV = savedEnd;
5791         return buf;
5792 }
5793
5794 Boolean
5795 LoadPV (int x, int y)
5796 { // called on right mouse click to load PV
5797   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5798   lastX = x; lastY = y;
5799   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5800   extendGame = FALSE;
5801   return TRUE;
5802 }
5803
5804 void
5805 UnLoadPV ()
5806 {
5807   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5808   if(activeCps) {
5809     if(pv_margin != activeCps->option[multi].value) {
5810       char buf[MSG_SIZ];
5811       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5812       SendToProgram(buf, activeCps);
5813       activeCps->option[multi].value = pv_margin;
5814     }
5815     activeCps = NULL;
5816     return;
5817   }
5818   if(endPV < 0) return;
5819   if(appData.autoCopyPV) CopyFENToClipboard();
5820   endPV = -1;
5821   if(extendGame && currentMove > forwardMostMove) {
5822         Boolean saveAnimate = appData.animate;
5823         if(pushed) {
5824             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5825                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5826             } else storedGames--; // abandon shelved tail of original game
5827         }
5828         pushed = FALSE;
5829         forwardMostMove = currentMove;
5830         currentMove = oldFMM;
5831         appData.animate = FALSE;
5832         ToNrEvent(forwardMostMove);
5833         appData.animate = saveAnimate;
5834   }
5835   currentMove = forwardMostMove;
5836   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5837   ClearPremoveHighlights();
5838   DrawPosition(TRUE, boards[currentMove]);
5839 }
5840
5841 void
5842 MovePV (int x, int y, int h)
5843 { // step through PV based on mouse coordinates (called on mouse move)
5844   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5845
5846   if(activeCps) { // adjusting engine's multi-pv margin
5847     if(x > lastX) pv_margin++; else
5848     if(x < lastX) pv_margin -= (pv_margin > 0);
5849     if(x != lastX) {
5850       char buf[MSG_SIZ];
5851       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5852       DisplayMessage(buf, "");
5853     }
5854     lastX = x;
5855     return;
5856   }
5857   // we must somehow check if right button is still down (might be released off board!)
5858   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5859   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5860   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5861   if(!step) return;
5862   lastX = x; lastY = y;
5863
5864   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5865   if(endPV < 0) return;
5866   if(y < margin) step = 1; else
5867   if(y > h - margin) step = -1;
5868   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5869   currentMove += step;
5870   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5871   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5872                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5873   DrawPosition(FALSE, boards[currentMove]);
5874 }
5875
5876
5877 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5878 // All positions will have equal probability, but the current method will not provide a unique
5879 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5880 #define DARK 1
5881 #define LITE 2
5882 #define ANY 3
5883
5884 int squaresLeft[4];
5885 int piecesLeft[(int)BlackPawn];
5886 int seed, nrOfShuffles;
5887
5888 void
5889 GetPositionNumber ()
5890 {       // sets global variable seed
5891         int i;
5892
5893         seed = appData.defaultFrcPosition;
5894         if(seed < 0) { // randomize based on time for negative FRC position numbers
5895                 for(i=0; i<50; i++) seed += random();
5896                 seed = random() ^ random() >> 8 ^ random() << 8;
5897                 if(seed<0) seed = -seed;
5898         }
5899 }
5900
5901 int
5902 put (Board board, int pieceType, int rank, int n, int shade)
5903 // put the piece on the (n-1)-th empty squares of the given shade
5904 {
5905         int i;
5906
5907         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5908                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5909                         board[rank][i] = (ChessSquare) pieceType;
5910                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5911                         squaresLeft[ANY]--;
5912                         piecesLeft[pieceType]--;
5913                         return i;
5914                 }
5915         }
5916         return -1;
5917 }
5918
5919
5920 void
5921 AddOnePiece (Board board, int pieceType, int rank, int shade)
5922 // calculate where the next piece goes, (any empty square), and put it there
5923 {
5924         int i;
5925
5926         i = seed % squaresLeft[shade];
5927         nrOfShuffles *= squaresLeft[shade];
5928         seed /= squaresLeft[shade];
5929         put(board, pieceType, rank, i, shade);
5930 }
5931
5932 void
5933 AddTwoPieces (Board board, int pieceType, int rank)
5934 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5935 {
5936         int i, n=squaresLeft[ANY], j=n-1, k;
5937
5938         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5939         i = seed % k;  // pick one
5940         nrOfShuffles *= k;
5941         seed /= k;
5942         while(i >= j) i -= j--;
5943         j = n - 1 - j; i += j;
5944         put(board, pieceType, rank, j, ANY);
5945         put(board, pieceType, rank, i, ANY);
5946 }
5947
5948 void
5949 SetUpShuffle (Board board, int number)
5950 {
5951         int i, p, first=1;
5952
5953         GetPositionNumber(); nrOfShuffles = 1;
5954
5955         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5956         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5957         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5958
5959         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5960
5961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5962             p = (int) board[0][i];
5963             if(p < (int) BlackPawn) piecesLeft[p] ++;
5964             board[0][i] = EmptySquare;
5965         }
5966
5967         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5968             // shuffles restricted to allow normal castling put KRR first
5969             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5970                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5971             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5972                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5973             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5974                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5975             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5976                 put(board, WhiteRook, 0, 0, ANY);
5977             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5978         }
5979
5980         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5981             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5982             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5983                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5984                 while(piecesLeft[p] >= 2) {
5985                     AddOnePiece(board, p, 0, LITE);
5986                     AddOnePiece(board, p, 0, DARK);
5987                 }
5988                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5989             }
5990
5991         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5992             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5993             // but we leave King and Rooks for last, to possibly obey FRC restriction
5994             if(p == (int)WhiteRook) continue;
5995             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5996             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5997         }
5998
5999         // now everything is placed, except perhaps King (Unicorn) and Rooks
6000
6001         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6002             // Last King gets castling rights
6003             while(piecesLeft[(int)WhiteUnicorn]) {
6004                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008             while(piecesLeft[(int)WhiteKing]) {
6009                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6010                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6011             }
6012
6013
6014         } else {
6015             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6016             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6017         }
6018
6019         // Only Rooks can be left; simply place them all
6020         while(piecesLeft[(int)WhiteRook]) {
6021                 i = put(board, WhiteRook, 0, 0, ANY);
6022                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6023                         if(first) {
6024                                 first=0;
6025                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6026                         }
6027                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6028                 }
6029         }
6030         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6031             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6032         }
6033
6034         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6035 }
6036
6037 int
6038 ptclen (const char *s, char *escapes)
6039 {
6040     int n = 0;
6041     if(!*escapes) return strlen(s);
6042     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6043     return n;
6044 }
6045
6046 int
6047 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6048 /* [HGM] moved here from winboard.c because of its general usefulness */
6049 /*       Basically a safe strcpy that uses the last character as King */
6050 {
6051     int result = FALSE; int NrPieces;
6052     unsigned char partner[EmptySquare];
6053
6054     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6055                     && NrPieces >= 12 && !(NrPieces&1)) {
6056         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6057
6058         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6059         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6060             char *p, c=0;
6061             if(map[j] == '/') offs = WhitePBishop - i, j++;
6062             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6063             table[i+offs] = map[j++];
6064             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6065             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6066             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6067         }
6068         table[(int) WhiteKing]  = map[j++];
6069         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6070             char *p, c=0;
6071             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6072             i = WHITE_TO_BLACK ii;
6073             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6074             table[i+offs] = map[j++];
6075             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6076             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6077             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6078         }
6079         table[(int) BlackKing]  = map[j++];
6080
6081
6082         if(*escapes) { // set up promotion pairing
6083             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6084             // pieceToChar entirely filled, so we can look up specified partners
6085             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6086                 int c = table[i];
6087                 if(c == '^' || c == '-') { // has specified partner
6088                     int p;
6089                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6090                     if(c == '^') table[i] = '+';
6091                     if(p < EmptySquare) {
6092                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6093                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6094                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6095                     }
6096                 } else if(c == '*') {
6097                     table[i] = partner[i];
6098                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6099                 }
6100             }
6101         }
6102
6103         result = TRUE;
6104     }
6105
6106     return result;
6107 }
6108
6109 int
6110 SetCharTable (unsigned char *table, const char * map)
6111 {
6112     return SetCharTableEsc(table, map, "");
6113 }
6114
6115 void
6116 Prelude (Board board)
6117 {       // [HGM] superchess: random selection of exo-pieces
6118         int i, j, k; ChessSquare p;
6119         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6120
6121         GetPositionNumber(); // use FRC position number
6122
6123         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6124             SetCharTable(pieceToChar, appData.pieceToCharTable);
6125             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6126                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6127         }
6128
6129         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
6134         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
6142         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6143         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6144         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6145         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6146         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6147         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6148         put(board, exoPieces[0],    0, 0, ANY);
6149         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6150 }
6151
6152 void
6153 InitPosition (int redraw)
6154 {
6155     ChessSquare (* pieces)[BOARD_FILES];
6156     int i, j, pawnRow=1, pieceRows=1, overrule,
6157     oldx = gameInfo.boardWidth,
6158     oldy = gameInfo.boardHeight,
6159     oldh = gameInfo.holdingsWidth;
6160     static int oldv;
6161
6162     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6163
6164     /* [AS] Initialize pv info list [HGM] and game status */
6165     {
6166         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6167             pvInfoList[i].depth = 0;
6168             boards[i][EP_STATUS] = EP_NONE;
6169             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6170         }
6171
6172         initialRulePlies = 0; /* 50-move counter start */
6173
6174         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6175         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6176     }
6177
6178
6179     /* [HGM] logic here is completely changed. In stead of full positions */
6180     /* the initialized data only consist of the two backranks. The switch */
6181     /* selects which one we will use, which is than copied to the Board   */
6182     /* initialPosition, which for the rest is initialized by Pawns and    */
6183     /* empty squares. This initial position is then copied to boards[0],  */
6184     /* possibly after shuffling, so that it remains available.            */
6185
6186     gameInfo.holdingsWidth = 0; /* default board sizes */
6187     gameInfo.boardWidth    = 8;
6188     gameInfo.boardHeight   = 8;
6189     gameInfo.holdingsSize  = 0;
6190     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6191     for(i=0; i<BOARD_FILES-6; i++)
6192       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6193     initialPosition[EP_STATUS] = EP_NONE;
6194     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6195     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6196     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6197          SetCharTable(pieceNickName, appData.pieceNickNames);
6198     else SetCharTable(pieceNickName, "............");
6199     pieces = FIDEArray;
6200
6201     switch (gameInfo.variant) {
6202     case VariantFischeRandom:
6203       shuffleOpenings = TRUE;
6204       appData.fischerCastling = TRUE;
6205     default:
6206       break;
6207     case VariantShatranj:
6208       pieces = ShatranjArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6211       break;
6212     case VariantMakruk:
6213       pieces = makrukArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6216       break;
6217     case VariantASEAN:
6218       pieces = aseanArray;
6219       nrCastlingRights = 0;
6220       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6221       break;
6222     case VariantTwoKings:
6223       pieces = twoKingsArray;
6224       break;
6225     case VariantGrand:
6226       pieces = GrandArray;
6227       nrCastlingRights = 0;
6228       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6229       gameInfo.boardWidth = 10;
6230       gameInfo.boardHeight = 10;
6231       gameInfo.holdingsSize = 7;
6232       break;
6233     case VariantCapaRandom:
6234       shuffleOpenings = TRUE;
6235       appData.fischerCastling = TRUE;
6236     case VariantCapablanca:
6237       pieces = CapablancaArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantGothic:
6242       pieces = GothicArray;
6243       gameInfo.boardWidth = 10;
6244       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6245       break;
6246     case VariantSChess:
6247       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6248       gameInfo.holdingsSize = 7;
6249       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6250       break;
6251     case VariantJanus:
6252       pieces = JanusArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6255       nrCastlingRights = 6;
6256         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6257         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6258         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6259         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6260         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6261         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6262       break;
6263     case VariantFalcon:
6264       pieces = FalconArray;
6265       gameInfo.boardWidth = 10;
6266       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6267       break;
6268     case VariantXiangqi:
6269       pieces = XiangqiArray;
6270       gameInfo.boardWidth  = 9;
6271       gameInfo.boardHeight = 10;
6272       nrCastlingRights = 0;
6273       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6274       break;
6275     case VariantShogi:
6276       pieces = ShogiArray;
6277       gameInfo.boardWidth  = 9;
6278       gameInfo.boardHeight = 9;
6279       gameInfo.holdingsSize = 7;
6280       nrCastlingRights = 0;
6281       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6282       break;
6283     case VariantChu:
6284       pieces = ChuArray; pieceRows = 3;
6285       gameInfo.boardWidth  = 12;
6286       gameInfo.boardHeight = 12;
6287       nrCastlingRights = 0;
6288 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6289   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6290       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"
6291                                    "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);
6292       break;
6293     case VariantCourier:
6294       pieces = CourierArray;
6295       gameInfo.boardWidth  = 12;
6296       nrCastlingRights = 0;
6297       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6298       break;
6299     case VariantKnightmate:
6300       pieces = KnightmateArray;
6301       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6302       break;
6303     case VariantSpartan:
6304       pieces = SpartanArray;
6305       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6306       break;
6307     case VariantLion:
6308       pieces = lionArray;
6309       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6310       break;
6311     case VariantChuChess:
6312       pieces = ChuChessArray;
6313       gameInfo.boardWidth = 10;
6314       gameInfo.boardHeight = 10;
6315       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6316       break;
6317     case VariantFairy:
6318       pieces = fairyArray;
6319       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6320       break;
6321     case VariantGreat:
6322       pieces = GreatArray;
6323       gameInfo.boardWidth = 10;
6324       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6325       gameInfo.holdingsSize = 8;
6326       break;
6327     case VariantSuper:
6328       pieces = FIDEArray;
6329       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6330       gameInfo.holdingsSize = 8;
6331       startedFromSetupPosition = TRUE;
6332       break;
6333     case VariantCrazyhouse:
6334     case VariantBughouse:
6335       pieces = FIDEArray;
6336       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6337       gameInfo.holdingsSize = 5;
6338       break;
6339     case VariantWildCastle:
6340       pieces = FIDEArray;
6341       /* !!?shuffle with kings guaranteed to be on d or e file */
6342       shuffleOpenings = 1;
6343       break;
6344     case VariantNoCastle:
6345       pieces = FIDEArray;
6346       nrCastlingRights = 0;
6347       /* !!?unconstrained back-rank shuffle */
6348       shuffleOpenings = 1;
6349       break;
6350     }
6351
6352     overrule = 0;
6353     if(appData.NrFiles >= 0) {
6354         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6355         gameInfo.boardWidth = appData.NrFiles;
6356     }
6357     if(appData.NrRanks >= 0) {
6358         gameInfo.boardHeight = appData.NrRanks;
6359     }
6360     if(appData.holdingsSize >= 0) {
6361         i = appData.holdingsSize;
6362         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6363         gameInfo.holdingsSize = i;
6364     }
6365     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6366     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6367         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6368
6369     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6370     if(pawnRow < 1) pawnRow = 1;
6371     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6372        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6373     if(gameInfo.variant == VariantChu) pawnRow = 3;
6374
6375     /* User pieceToChar list overrules defaults */
6376     if(appData.pieceToCharTable != NULL)
6377         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6378
6379     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6380
6381         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6382             s = (ChessSquare) 0; /* account holding counts in guard band */
6383         for( i=0; i<BOARD_HEIGHT; i++ )
6384             initialPosition[i][j] = s;
6385
6386         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6387         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6388         initialPosition[pawnRow][j] = WhitePawn;
6389         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6390         if(gameInfo.variant == VariantXiangqi) {
6391             if(j&1) {
6392                 initialPosition[pawnRow][j] =
6393                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6394                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6395                    initialPosition[2][j] = WhiteCannon;
6396                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6397                 }
6398             }
6399         }
6400         if(gameInfo.variant == VariantChu) {
6401              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6402                initialPosition[pawnRow+1][j] = WhiteCobra,
6403                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6404              for(i=1; i<pieceRows; i++) {
6405                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6406                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6407              }
6408         }
6409         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6410             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6411                initialPosition[0][j] = WhiteRook;
6412                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6413             }
6414         }
6415         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6416     }
6417     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6418     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6419
6420             j=BOARD_LEFT+1;
6421             initialPosition[1][j] = WhiteBishop;
6422             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6423             j=BOARD_RGHT-2;
6424             initialPosition[1][j] = WhiteRook;
6425             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6426     }
6427
6428     if( nrCastlingRights == -1) {
6429         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6430         /*       This sets default castling rights from none to normal corners   */
6431         /* Variants with other castling rights must set them themselves above    */
6432         nrCastlingRights = 6;
6433
6434         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6435         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6436         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6437         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6438         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6439         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6440      }
6441
6442      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6443      if(gameInfo.variant == VariantGreat) { // promotion commoners
6444         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6445         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6446         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6447         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6448      }
6449      if( gameInfo.variant == VariantSChess ) {
6450       initialPosition[1][0] = BlackMarshall;
6451       initialPosition[2][0] = BlackAngel;
6452       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6453       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6454       initialPosition[1][1] = initialPosition[2][1] =
6455       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6456      }
6457      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6458   if (appData.debugMode) {
6459     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6460   }
6461     if(shuffleOpenings) {
6462         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6463         startedFromSetupPosition = TRUE;
6464     }
6465     if(startedFromPositionFile) {
6466       /* [HGM] loadPos: use PositionFile for every new game */
6467       CopyBoard(initialPosition, filePosition);
6468       for(i=0; i<nrCastlingRights; i++)
6469           initialRights[i] = filePosition[CASTLING][i];
6470       startedFromSetupPosition = TRUE;
6471     }
6472     if(*appData.men) LoadPieceDesc(appData.men);
6473
6474     CopyBoard(boards[0], initialPosition);
6475
6476     if(oldx != gameInfo.boardWidth ||
6477        oldy != gameInfo.boardHeight ||
6478        oldv != gameInfo.variant ||
6479        oldh != gameInfo.holdingsWidth
6480                                          )
6481             InitDrawingSizes(-2 ,0);
6482
6483     oldv = gameInfo.variant;
6484     if (redraw)
6485       DrawPosition(TRUE, boards[currentMove]);
6486 }
6487
6488 void
6489 SendBoard (ChessProgramState *cps, int moveNum)
6490 {
6491     char message[MSG_SIZ];
6492
6493     if (cps->useSetboard) {
6494       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6495       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6496       SendToProgram(message, cps);
6497       free(fen);
6498
6499     } else {
6500       ChessSquare *bp;
6501       int i, j, left=0, right=BOARD_WIDTH;
6502       /* Kludge to set black to move, avoiding the troublesome and now
6503        * deprecated "black" command.
6504        */
6505       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6506         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6507
6508       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6509
6510       SendToProgram("edit\n", cps);
6511       SendToProgram("#\n", cps);
6512       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6513         bp = &boards[moveNum][i][left];
6514         for (j = left; j < right; j++, bp++) {
6515           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6516           if ((int) *bp < (int) BlackPawn) {
6517             if(j == BOARD_RGHT+1)
6518                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6519             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6520             if(message[0] == '+' || message[0] == '~') {
6521               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6522                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6523                         AAA + j, ONE + i - '0');
6524             }
6525             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6526                 message[1] = BOARD_RGHT   - 1 - j + '1';
6527                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6528             }
6529             SendToProgram(message, cps);
6530           }
6531         }
6532       }
6533
6534       SendToProgram("c\n", cps);
6535       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6536         bp = &boards[moveNum][i][left];
6537         for (j = left; j < right; j++, bp++) {
6538           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6539           if (((int) *bp != (int) EmptySquare)
6540               && ((int) *bp >= (int) BlackPawn)) {
6541             if(j == BOARD_LEFT-2)
6542                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6543             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6544                     AAA + j, ONE + i - '0');
6545             if(message[0] == '+' || message[0] == '~') {
6546               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6547                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6548                         AAA + j, ONE + i - '0');
6549             }
6550             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6551                 message[1] = BOARD_RGHT   - 1 - j + '1';
6552                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6553             }
6554             SendToProgram(message, cps);
6555           }
6556         }
6557       }
6558
6559       SendToProgram(".\n", cps);
6560     }
6561     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6562 }
6563
6564 char exclusionHeader[MSG_SIZ];
6565 int exCnt, excludePtr;
6566 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6567 static Exclusion excluTab[200];
6568 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6569
6570 static void
6571 WriteMap (int s)
6572 {
6573     int j;
6574     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6575     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6576 }
6577
6578 static void
6579 ClearMap ()
6580 {
6581     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6582     excludePtr = 24; exCnt = 0;
6583     WriteMap(0);
6584 }
6585
6586 static void
6587 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6588 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6589     char buf[2*MOVE_LEN], *p;
6590     Exclusion *e = excluTab;
6591     int i;
6592     for(i=0; i<exCnt; i++)
6593         if(e[i].ff == fromX && e[i].fr == fromY &&
6594            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6595     if(i == exCnt) { // was not in exclude list; add it
6596         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6597         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6598             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6599             return; // abort
6600         }
6601         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6602         excludePtr++; e[i].mark = excludePtr++;
6603         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6604         exCnt++;
6605     }
6606     exclusionHeader[e[i].mark] = state;
6607 }
6608
6609 static int
6610 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6611 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6612     char buf[MSG_SIZ];
6613     int j, k;
6614     ChessMove moveType;
6615     if((signed char)promoChar == -1) { // kludge to indicate best move
6616         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6617             return 1; // if unparsable, abort
6618     }
6619     // update exclusion map (resolving toggle by consulting existing state)
6620     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6621     j = k%8; k >>= 3;
6622     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6623     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6624          excludeMap[k] |=   1<<j;
6625     else excludeMap[k] &= ~(1<<j);
6626     // update header
6627     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6628     // inform engine
6629     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6630     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6631     SendToBoth(buf);
6632     return (state == '+');
6633 }
6634
6635 static void
6636 ExcludeClick (int index)
6637 {
6638     int i, j;
6639     Exclusion *e = excluTab;
6640     if(index < 25) { // none, best or tail clicked
6641         if(index < 13) { // none: include all
6642             WriteMap(0); // clear map
6643             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6644             SendToBoth("include all\n"); // and inform engine
6645         } else if(index > 18) { // tail
6646             if(exclusionHeader[19] == '-') { // tail was excluded
6647                 SendToBoth("include all\n");
6648                 WriteMap(0); // clear map completely
6649                 // now re-exclude selected moves
6650                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6651                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6652             } else { // tail was included or in mixed state
6653                 SendToBoth("exclude all\n");
6654                 WriteMap(0xFF); // fill map completely
6655                 // now re-include selected moves
6656                 j = 0; // count them
6657                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6658                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6659                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6660             }
6661         } else { // best
6662             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6663         }
6664     } else {
6665         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6666             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6667             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6668             break;
6669         }
6670     }
6671 }
6672
6673 ChessSquare
6674 DefaultPromoChoice (int white)
6675 {
6676     ChessSquare result;
6677     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6678        gameInfo.variant == VariantMakruk)
6679         result = WhiteFerz; // no choice
6680     else if(gameInfo.variant == VariantASEAN)
6681         result = WhiteRook; // no choice
6682     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6683         result= WhiteKing; // in Suicide Q is the last thing we want
6684     else if(gameInfo.variant == VariantSpartan)
6685         result = white ? WhiteQueen : WhiteAngel;
6686     else result = WhiteQueen;
6687     if(!white) result = WHITE_TO_BLACK result;
6688     return result;
6689 }
6690
6691 static int autoQueen; // [HGM] oneclick
6692
6693 int
6694 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6695 {
6696     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6697     /* [HGM] add Shogi promotions */
6698     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6699     ChessSquare piece, partner;
6700     ChessMove moveType;
6701     Boolean premove;
6702
6703     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6704     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6705
6706     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6707       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6708         return FALSE;
6709
6710     if(legal[toY][toX] == 4) return FALSE;
6711
6712     piece = boards[currentMove][fromY][fromX];
6713     if(gameInfo.variant == VariantChu) {
6714         promotionZoneSize = BOARD_HEIGHT/3;
6715         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6716         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6717     } else if(gameInfo.variant == VariantShogi) {
6718         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6719         highestPromotingPiece = (int)WhiteAlfil;
6720     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6721         promotionZoneSize = 3;
6722     }
6723
6724     // Treat Lance as Pawn when it is not representing Amazon or Lance
6725     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6726         if(piece == WhiteLance) piece = WhitePawn; else
6727         if(piece == BlackLance) piece = BlackPawn;
6728     }
6729
6730     // next weed out all moves that do not touch the promotion zone at all
6731     if((int)piece >= BlackPawn) {
6732         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6733              return FALSE;
6734         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6735         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6736     } else {
6737         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6738            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6739         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6740              return FALSE;
6741     }
6742
6743     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6744
6745     // weed out mandatory Shogi promotions
6746     if(gameInfo.variant == VariantShogi) {
6747         if(piece >= BlackPawn) {
6748             if(toY == 0 && piece == BlackPawn ||
6749                toY == 0 && piece == BlackQueen ||
6750                toY <= 1 && piece == BlackKnight) {
6751                 *promoChoice = '+';
6752                 return FALSE;
6753             }
6754         } else {
6755             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6756                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6757                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6758                 *promoChoice = '+';
6759                 return FALSE;
6760             }
6761         }
6762     }
6763
6764     // weed out obviously illegal Pawn moves
6765     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6766         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6767         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6768         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6769         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6770         // note we are not allowed to test for valid (non-)capture, due to premove
6771     }
6772
6773     // we either have a choice what to promote to, or (in Shogi) whether to promote
6774     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6775        gameInfo.variant == VariantMakruk) {
6776         ChessSquare p=BlackFerz;  // no choice
6777         while(p < EmptySquare) {  //but make sure we use piece that exists
6778             *promoChoice = PieceToChar(p++);
6779             if(*promoChoice != '.') break;
6780         }
6781         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6782     }
6783     // no sense asking what we must promote to if it is going to explode...
6784     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6785         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6786         return FALSE;
6787     }
6788     // give caller the default choice even if we will not make it
6789     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6790     partner = piece; // pieces can promote if the pieceToCharTable says so
6791     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6792     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6793     if(        sweepSelect && gameInfo.variant != VariantGreat
6794                            && gameInfo.variant != VariantGrand
6795                            && gameInfo.variant != VariantSuper) return FALSE;
6796     if(autoQueen) return FALSE; // predetermined
6797
6798     // suppress promotion popup on illegal moves that are not premoves
6799     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6800               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6801     if(appData.testLegality && !premove) {
6802         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6803                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6804         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6805         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6806             return FALSE;
6807     }
6808
6809     return TRUE;
6810 }
6811
6812 int
6813 InPalace (int row, int column)
6814 {   /* [HGM] for Xiangqi */
6815     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6816          column < (BOARD_WIDTH + 4)/2 &&
6817          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6818     return FALSE;
6819 }
6820
6821 int
6822 PieceForSquare (int x, int y)
6823 {
6824   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6825      return -1;
6826   else
6827      return boards[currentMove][y][x];
6828 }
6829
6830 int
6831 OKToStartUserMove (int x, int y)
6832 {
6833     ChessSquare from_piece;
6834     int white_piece;
6835
6836     if (matchMode) return FALSE;
6837     if (gameMode == EditPosition) return TRUE;
6838
6839     if (x >= 0 && y >= 0)
6840       from_piece = boards[currentMove][y][x];
6841     else
6842       from_piece = EmptySquare;
6843
6844     if (from_piece == EmptySquare) return FALSE;
6845
6846     white_piece = (int)from_piece >= (int)WhitePawn &&
6847       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6848
6849     switch (gameMode) {
6850       case AnalyzeFile:
6851       case TwoMachinesPlay:
6852       case EndOfGame:
6853         return FALSE;
6854
6855       case IcsObserving:
6856       case IcsIdle:
6857         return FALSE;
6858
6859       case MachinePlaysWhite:
6860       case IcsPlayingBlack:
6861         if (appData.zippyPlay) return FALSE;
6862         if (white_piece) {
6863             DisplayMoveError(_("You are playing Black"));
6864             return FALSE;
6865         }
6866         break;
6867
6868       case MachinePlaysBlack:
6869       case IcsPlayingWhite:
6870         if (appData.zippyPlay) return FALSE;
6871         if (!white_piece) {
6872             DisplayMoveError(_("You are playing White"));
6873             return FALSE;
6874         }
6875         break;
6876
6877       case PlayFromGameFile:
6878             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6879       case EditGame:
6880       case AnalyzeMode:
6881         if (!white_piece && WhiteOnMove(currentMove)) {
6882             DisplayMoveError(_("It is White's turn"));
6883             return FALSE;
6884         }
6885         if (white_piece && !WhiteOnMove(currentMove)) {
6886             DisplayMoveError(_("It is Black's turn"));
6887             return FALSE;
6888         }
6889         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6890             /* Editing correspondence game history */
6891             /* Could disallow this or prompt for confirmation */
6892             cmailOldMove = -1;
6893         }
6894         break;
6895
6896       case BeginningOfGame:
6897         if (appData.icsActive) return FALSE;
6898         if (!appData.noChessProgram) {
6899             if (!white_piece) {
6900                 DisplayMoveError(_("You are playing White"));
6901                 return FALSE;
6902             }
6903         }
6904         break;
6905
6906       case Training:
6907         if (!white_piece && WhiteOnMove(currentMove)) {
6908             DisplayMoveError(_("It is White's turn"));
6909             return FALSE;
6910         }
6911         if (white_piece && !WhiteOnMove(currentMove)) {
6912             DisplayMoveError(_("It is Black's turn"));
6913             return FALSE;
6914         }
6915         break;
6916
6917       default:
6918       case IcsExamining:
6919         break;
6920     }
6921     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6922         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6923         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6924         && gameMode != AnalyzeFile && gameMode != Training) {
6925         DisplayMoveError(_("Displayed position is not current"));
6926         return FALSE;
6927     }
6928     return TRUE;
6929 }
6930
6931 Boolean
6932 OnlyMove (int *x, int *y, Boolean captures)
6933 {
6934     DisambiguateClosure cl;
6935     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6936     switch(gameMode) {
6937       case MachinePlaysBlack:
6938       case IcsPlayingWhite:
6939       case BeginningOfGame:
6940         if(!WhiteOnMove(currentMove)) return FALSE;
6941         break;
6942       case MachinePlaysWhite:
6943       case IcsPlayingBlack:
6944         if(WhiteOnMove(currentMove)) return FALSE;
6945         break;
6946       case EditGame:
6947         break;
6948       default:
6949         return FALSE;
6950     }
6951     cl.pieceIn = EmptySquare;
6952     cl.rfIn = *y;
6953     cl.ffIn = *x;
6954     cl.rtIn = -1;
6955     cl.ftIn = -1;
6956     cl.promoCharIn = NULLCHAR;
6957     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6958     if( cl.kind == NormalMove ||
6959         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6960         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6961         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6962       fromX = cl.ff;
6963       fromY = cl.rf;
6964       *x = cl.ft;
6965       *y = cl.rt;
6966       return TRUE;
6967     }
6968     if(cl.kind != ImpossibleMove) return FALSE;
6969     cl.pieceIn = EmptySquare;
6970     cl.rfIn = -1;
6971     cl.ffIn = -1;
6972     cl.rtIn = *y;
6973     cl.ftIn = *x;
6974     cl.promoCharIn = NULLCHAR;
6975     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6976     if( cl.kind == NormalMove ||
6977         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6978         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6979         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6980       fromX = cl.ff;
6981       fromY = cl.rf;
6982       *x = cl.ft;
6983       *y = cl.rt;
6984       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6985       return TRUE;
6986     }
6987     return FALSE;
6988 }
6989
6990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6992 int lastLoadGameUseList = FALSE;
6993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6994 ChessMove lastLoadGameStart = EndOfFile;
6995 int doubleClick;
6996 Boolean addToBookFlag;
6997 static Board rightsBoard, nullBoard;
6998
6999 void
7000 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7001 {
7002     ChessMove moveType;
7003     ChessSquare pup;
7004     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7005
7006     /* Check if the user is playing in turn.  This is complicated because we
7007        let the user "pick up" a piece before it is his turn.  So the piece he
7008        tried to pick up may have been captured by the time he puts it down!
7009        Therefore we use the color the user is supposed to be playing in this
7010        test, not the color of the piece that is currently on the starting
7011        square---except in EditGame mode, where the user is playing both
7012        sides; fortunately there the capture race can't happen.  (It can
7013        now happen in IcsExamining mode, but that's just too bad.  The user
7014        will get a somewhat confusing message in that case.)
7015        */
7016
7017     switch (gameMode) {
7018       case AnalyzeFile:
7019       case TwoMachinesPlay:
7020       case EndOfGame:
7021       case IcsObserving:
7022       case IcsIdle:
7023         /* We switched into a game mode where moves are not accepted,
7024            perhaps while the mouse button was down. */
7025         return;
7026
7027       case MachinePlaysWhite:
7028         /* User is moving for Black */
7029         if (WhiteOnMove(currentMove)) {
7030             DisplayMoveError(_("It is White's turn"));
7031             return;
7032         }
7033         break;
7034
7035       case MachinePlaysBlack:
7036         /* User is moving for White */
7037         if (!WhiteOnMove(currentMove)) {
7038             DisplayMoveError(_("It is Black's turn"));
7039             return;
7040         }
7041         break;
7042
7043       case PlayFromGameFile:
7044             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7045       case EditGame:
7046       case IcsExamining:
7047       case BeginningOfGame:
7048       case AnalyzeMode:
7049       case Training:
7050         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7051         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7052             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7053             /* User is moving for Black */
7054             if (WhiteOnMove(currentMove)) {
7055                 DisplayMoveError(_("It is White's turn"));
7056                 return;
7057             }
7058         } else {
7059             /* User is moving for White */
7060             if (!WhiteOnMove(currentMove)) {
7061                 DisplayMoveError(_("It is Black's turn"));
7062                 return;
7063             }
7064         }
7065         break;
7066
7067       case IcsPlayingBlack:
7068         /* User is moving for Black */
7069         if (WhiteOnMove(currentMove)) {
7070             if (!appData.premove) {
7071                 DisplayMoveError(_("It is White's turn"));
7072             } else if (toX >= 0 && toY >= 0) {
7073                 premoveToX = toX;
7074                 premoveToY = toY;
7075                 premoveFromX = fromX;
7076                 premoveFromY = fromY;
7077                 premovePromoChar = promoChar;
7078                 gotPremove = 1;
7079                 if (appData.debugMode)
7080                     fprintf(debugFP, "Got premove: fromX %d,"
7081                             "fromY %d, toX %d, toY %d\n",
7082                             fromX, fromY, toX, toY);
7083             }
7084             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7085             return;
7086         }
7087         break;
7088
7089       case IcsPlayingWhite:
7090         /* User is moving for White */
7091         if (!WhiteOnMove(currentMove)) {
7092             if (!appData.premove) {
7093                 DisplayMoveError(_("It is Black's turn"));
7094             } else if (toX >= 0 && toY >= 0) {
7095                 premoveToX = toX;
7096                 premoveToY = toY;
7097                 premoveFromX = fromX;
7098                 premoveFromY = fromY;
7099                 premovePromoChar = promoChar;
7100                 gotPremove = 1;
7101                 if (appData.debugMode)
7102                     fprintf(debugFP, "Got premove: fromX %d,"
7103                             "fromY %d, toX %d, toY %d\n",
7104                             fromX, fromY, toX, toY);
7105             }
7106             DrawPosition(TRUE, boards[currentMove]);
7107             return;
7108         }
7109         break;
7110
7111       default:
7112         break;
7113
7114       case EditPosition:
7115         /* EditPosition, empty square, or different color piece;
7116            click-click move is possible */
7117         if (toX == -2 || toY == -2) {
7118             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7119             DrawPosition(FALSE, boards[currentMove]);
7120             return;
7121         } else if (toX >= 0 && toY >= 0) {
7122             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7123                 ChessSquare p = boards[0][rf][ff];
7124                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7125                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7126                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7127                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7128                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7129                     gatingPiece = p;
7130                 }
7131             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7132             boards[0][toY][toX] = boards[0][fromY][fromX];
7133             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7134                 if(boards[0][fromY][0] != EmptySquare) {
7135                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7136                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7137                 }
7138             } else
7139             if(fromX == BOARD_RGHT+1) {
7140                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7141                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7142                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7143                 }
7144             } else
7145             boards[0][fromY][fromX] = gatingPiece;
7146             ClearHighlights();
7147             DrawPosition(FALSE, boards[currentMove]);
7148             return;
7149         }
7150         return;
7151     }
7152
7153     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7154     pup = boards[currentMove][toY][toX];
7155
7156     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7157     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7158          if( pup != EmptySquare ) return;
7159          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7160            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7161                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7162            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7163            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7164            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7165            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7166          fromY = DROP_RANK;
7167     }
7168
7169     /* [HGM] always test for legality, to get promotion info */
7170     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7171                                          fromY, fromX, toY, toX, promoChar);
7172
7173     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7174
7175     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7176
7177     /* [HGM] but possibly ignore an IllegalMove result */
7178     if (appData.testLegality) {
7179         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7180             DisplayMoveError(_("Illegal move"));
7181             return;
7182         }
7183     }
7184
7185     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7186         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7187              ClearPremoveHighlights(); // was included
7188         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7189         DrawPosition(FALSE, NULL);
7190         return;
7191     }
7192
7193     if(addToBookFlag) { // adding moves to book
7194         char buf[MSG_SIZ], move[MSG_SIZ];
7195         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7196         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7197                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7198         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7199         AddBookMove(buf);
7200         addToBookFlag = FALSE;
7201         ClearHighlights();
7202         return;
7203     }
7204
7205     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7206 }
7207
7208 /* Common tail of UserMoveEvent and DropMenuEvent */
7209 int
7210 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7211 {
7212     char *bookHit = 0;
7213
7214     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7215         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7216         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7217         if(WhiteOnMove(currentMove)) {
7218             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7219         } else {
7220             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7221         }
7222     }
7223
7224     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7225        move type in caller when we know the move is a legal promotion */
7226     if(moveType == NormalMove && promoChar)
7227         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7228
7229     /* [HGM] <popupFix> The following if has been moved here from
7230        UserMoveEvent(). Because it seemed to belong here (why not allow
7231        piece drops in training games?), and because it can only be
7232        performed after it is known to what we promote. */
7233     if (gameMode == Training) {
7234       /* compare the move played on the board to the next move in the
7235        * game. If they match, display the move and the opponent's response.
7236        * If they don't match, display an error message.
7237        */
7238       int saveAnimate;
7239       Board testBoard;
7240       CopyBoard(testBoard, boards[currentMove]);
7241       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7242
7243       if (CompareBoards(testBoard, boards[currentMove+1])) {
7244         ForwardInner(currentMove+1);
7245
7246         /* Autoplay the opponent's response.
7247          * if appData.animate was TRUE when Training mode was entered,
7248          * the response will be animated.
7249          */
7250         saveAnimate = appData.animate;
7251         appData.animate = animateTraining;
7252         ForwardInner(currentMove+1);
7253         appData.animate = saveAnimate;
7254
7255         /* check for the end of the game */
7256         if (currentMove >= forwardMostMove) {
7257           gameMode = PlayFromGameFile;
7258           ModeHighlight();
7259           SetTrainingModeOff();
7260           DisplayInformation(_("End of game"));
7261         }
7262       } else {
7263         DisplayError(_("Incorrect move"), 0);
7264       }
7265       return 1;
7266     }
7267
7268   /* Ok, now we know that the move is good, so we can kill
7269      the previous line in Analysis Mode */
7270   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7271                                 && currentMove < forwardMostMove) {
7272     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7273     else forwardMostMove = currentMove;
7274   }
7275
7276   ClearMap();
7277
7278   /* If we need the chess program but it's dead, restart it */
7279   ResurrectChessProgram();
7280
7281   /* A user move restarts a paused game*/
7282   if (pausing)
7283     PauseEvent();
7284
7285   thinkOutput[0] = NULLCHAR;
7286
7287   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7288
7289   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7290     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7291     return 1;
7292   }
7293
7294   if (gameMode == BeginningOfGame) {
7295     if (appData.noChessProgram) {
7296       gameMode = EditGame;
7297       SetGameInfo();
7298     } else {
7299       char buf[MSG_SIZ];
7300       gameMode = MachinePlaysBlack;
7301       StartClocks();
7302       SetGameInfo();
7303       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7304       DisplayTitle(buf);
7305       if (first.sendName) {
7306         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7307         SendToProgram(buf, &first);
7308       }
7309       StartClocks();
7310     }
7311     ModeHighlight();
7312   }
7313
7314   /* Relay move to ICS or chess engine */
7315   if (appData.icsActive) {
7316     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7317         gameMode == IcsExamining) {
7318       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7319         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7320         SendToICS("draw ");
7321         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7322       }
7323       // also send plain move, in case ICS does not understand atomic claims
7324       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7325       ics_user_moved = 1;
7326     }
7327   } else {
7328     if (first.sendTime && (gameMode == BeginningOfGame ||
7329                            gameMode == MachinePlaysWhite ||
7330                            gameMode == MachinePlaysBlack)) {
7331       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7332     }
7333     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7334          // [HGM] book: if program might be playing, let it use book
7335         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7336         first.maybeThinking = TRUE;
7337     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7338         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7339         SendBoard(&first, currentMove+1);
7340         if(second.analyzing) {
7341             if(!second.useSetboard) SendToProgram("undo\n", &second);
7342             SendBoard(&second, currentMove+1);
7343         }
7344     } else {
7345         SendMoveToProgram(forwardMostMove-1, &first);
7346         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7347     }
7348     if (currentMove == cmailOldMove + 1) {
7349       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7350     }
7351   }
7352
7353   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7354
7355   switch (gameMode) {
7356   case EditGame:
7357     if(appData.testLegality)
7358     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7359     case MT_NONE:
7360     case MT_CHECK:
7361       break;
7362     case MT_CHECKMATE:
7363     case MT_STAINMATE:
7364       if (WhiteOnMove(currentMove)) {
7365         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7366       } else {
7367         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7368       }
7369       break;
7370     case MT_STALEMATE:
7371       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7372       break;
7373     }
7374     break;
7375
7376   case MachinePlaysBlack:
7377   case MachinePlaysWhite:
7378     /* disable certain menu options while machine is thinking */
7379     SetMachineThinkingEnables();
7380     break;
7381
7382   default:
7383     break;
7384   }
7385
7386   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7387   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7388
7389   if(bookHit) { // [HGM] book: simulate book reply
7390         static char bookMove[MSG_SIZ]; // a bit generous?
7391
7392         programStats.nodes = programStats.depth = programStats.time =
7393         programStats.score = programStats.got_only_move = 0;
7394         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7395
7396         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7397         strcat(bookMove, bookHit);
7398         HandleMachineMove(bookMove, &first);
7399   }
7400   return 1;
7401 }
7402
7403 void
7404 MarkByFEN(char *fen)
7405 {
7406         int r, f;
7407         if(!appData.markers || !appData.highlightDragging) return;
7408         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7409         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7410         while(*fen) {
7411             int s = 0;
7412             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7413             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7414             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7415             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7416             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7417             if(*fen == 'T') marker[r][f++] = 0; else
7418             if(*fen == 'Y') marker[r][f++] = 1; else
7419             if(*fen == 'G') marker[r][f++] = 3; else
7420             if(*fen == 'B') marker[r][f++] = 4; else
7421             if(*fen == 'C') marker[r][f++] = 5; else
7422             if(*fen == 'M') marker[r][f++] = 6; else
7423             if(*fen == 'W') marker[r][f++] = 7; else
7424             if(*fen == 'D') marker[r][f++] = 8; else
7425             if(*fen == 'R') marker[r][f++] = 2; else {
7426                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7427               f += s; fen -= s>0;
7428             }
7429             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7430             if(r < 0) break;
7431             fen++;
7432         }
7433         DrawPosition(TRUE, NULL);
7434 }
7435
7436 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7437
7438 void
7439 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7440 {
7441     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7442     Markers *m = (Markers *) closure;
7443     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7444                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7445         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7446                          || kind == WhiteCapturesEnPassant
7447                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7448     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7449 }
7450
7451 static int hoverSavedValid;
7452
7453 void
7454 MarkTargetSquares (int clear)
7455 {
7456   int x, y, sum=0;
7457   if(clear) { // no reason to ever suppress clearing
7458     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7459     hoverSavedValid = 0;
7460     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7461   } else {
7462     int capt = 0;
7463     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7464        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7465     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7466     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7467       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7468       if(capt)
7469       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7470     }
7471   }
7472   DrawPosition(FALSE, NULL);
7473 }
7474
7475 int
7476 Explode (Board board, int fromX, int fromY, int toX, int toY)
7477 {
7478     if(gameInfo.variant == VariantAtomic &&
7479        (board[toY][toX] != EmptySquare ||                     // capture?
7480         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7481                          board[fromY][fromX] == BlackPawn   )
7482       )) {
7483         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7484         return TRUE;
7485     }
7486     return FALSE;
7487 }
7488
7489 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7490
7491 int
7492 CanPromote (ChessSquare piece, int y)
7493 {
7494         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7495         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7496         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7497         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7498            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7499           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7500            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7501         return (piece == BlackPawn && y <= zone ||
7502                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7503                 piece == BlackLance && y <= zone ||
7504                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7505 }
7506
7507 void
7508 HoverEvent (int xPix, int yPix, int x, int y)
7509 {
7510         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7511         int r, f;
7512         if(!first.highlight) return;
7513         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7514         if(x == oldX && y == oldY) return; // only do something if we enter new square
7515         oldFromX = fromX; oldFromY = fromY;
7516         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7517           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7518             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7519           hoverSavedValid = 1;
7520         } else if(oldX != x || oldY != y) {
7521           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7522           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7523           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7524             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7525           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7526             char buf[MSG_SIZ];
7527             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7528             SendToProgram(buf, &first);
7529           }
7530           oldX = x; oldY = y;
7531 //        SetHighlights(fromX, fromY, x, y);
7532         }
7533 }
7534
7535 void ReportClick(char *action, int x, int y)
7536 {
7537         char buf[MSG_SIZ]; // Inform engine of what user does
7538         int r, f;
7539         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7540           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7541             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7542         if(!first.highlight || gameMode == EditPosition) return;
7543         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7544         SendToProgram(buf, &first);
7545 }
7546
7547 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7548 Boolean deferChoice;
7549
7550 void
7551 LeftClick (ClickType clickType, int xPix, int yPix)
7552 {
7553     int x, y;
7554     static Boolean saveAnimate;
7555     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7556     char promoChoice = NULLCHAR;
7557     ChessSquare piece;
7558     static TimeMark lastClickTime, prevClickTime;
7559
7560     if(flashing) return;
7561
7562   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7563     x = EventToSquare(xPix, BOARD_WIDTH);
7564     y = EventToSquare(yPix, BOARD_HEIGHT);
7565     if (!flipView && y >= 0) {
7566         y = BOARD_HEIGHT - 1 - y;
7567     }
7568     if (flipView && x >= 0) {
7569         x = BOARD_WIDTH - 1 - x;
7570     }
7571
7572     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7573         static int dummy;
7574         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7575         right = TRUE;
7576         return;
7577     }
7578
7579     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7580
7581     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7582
7583     if (clickType == Press) ErrorPopDown();
7584     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7585
7586     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7587         defaultPromoChoice = promoSweep;
7588         promoSweep = EmptySquare;   // terminate sweep
7589         promoDefaultAltered = TRUE;
7590         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7591     }
7592
7593     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7594         if(clickType == Release) return; // ignore upclick of click-click destination
7595         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7596         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7597         if(gameInfo.holdingsWidth &&
7598                 (WhiteOnMove(currentMove)
7599                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7600                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7601             // click in right holdings, for determining promotion piece
7602             ChessSquare p = boards[currentMove][y][x];
7603             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7604             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7605             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7606                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7607                 fromX = fromY = -1;
7608                 return;
7609             }
7610         }
7611         DrawPosition(FALSE, boards[currentMove]);
7612         return;
7613     }
7614
7615     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7616     if(clickType == Press
7617             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7618               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7619               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7620         return;
7621
7622     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7623         // could be static click on premove from-square: abort premove
7624         gotPremove = 0;
7625         ClearPremoveHighlights();
7626     }
7627
7628     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7629         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7630
7631     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7632         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7633                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7634         defaultPromoChoice = DefaultPromoChoice(side);
7635     }
7636
7637     autoQueen = appData.alwaysPromoteToQueen;
7638
7639     if (fromX == -1) {
7640       int originalY = y;
7641       gatingPiece = EmptySquare;
7642       if (clickType != Press) {
7643         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7644             DragPieceEnd(xPix, yPix); dragging = 0;
7645             DrawPosition(FALSE, NULL);
7646         }
7647         return;
7648       }
7649       doubleClick = FALSE;
7650       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7651         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7652       }
7653       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7654       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7655          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7656          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7657             /* First square */
7658             if (OKToStartUserMove(fromX, fromY)) {
7659                 second = 0;
7660                 ReportClick("lift", x, y);
7661                 MarkTargetSquares(0);
7662                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7663                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7664                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7665                     promoSweep = defaultPromoChoice;
7666                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7667                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7668                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7669                 }
7670                 if (appData.highlightDragging) {
7671                     SetHighlights(fromX, fromY, -1, -1);
7672                 } else {
7673                     ClearHighlights();
7674                 }
7675             } else fromX = fromY = -1;
7676             return;
7677         }
7678     }
7679
7680     /* fromX != -1 */
7681     if (clickType == Press && gameMode != EditPosition) {
7682         ChessSquare fromP;
7683         ChessSquare toP;
7684         int frc;
7685
7686         // ignore off-board to clicks
7687         if(y < 0 || x < 0) return;
7688
7689         /* Check if clicking again on the same color piece */
7690         fromP = boards[currentMove][fromY][fromX];
7691         toP = boards[currentMove][y][x];
7692         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7693         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7694             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7695            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7696              WhitePawn <= toP && toP <= WhiteKing &&
7697              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7698              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7699             (BlackPawn <= fromP && fromP <= BlackKing &&
7700              BlackPawn <= toP && toP <= BlackKing &&
7701              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7702              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7703             /* Clicked again on same color piece -- changed his mind */
7704             second = (x == fromX && y == fromY);
7705             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7706             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7707                 second = FALSE; // first double-click rather than scond click
7708                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7709             }
7710             promoDefaultAltered = FALSE;
7711            if(!second) MarkTargetSquares(1);
7712            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7713             if (appData.highlightDragging) {
7714                 SetHighlights(x, y, -1, -1);
7715             } else {
7716                 ClearHighlights();
7717             }
7718             if (OKToStartUserMove(x, y)) {
7719                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7720                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7721                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7722                  gatingPiece = boards[currentMove][fromY][fromX];
7723                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7724                 fromX = x;
7725                 fromY = y; dragging = 1;
7726                 if(!second) ReportClick("lift", x, y);
7727                 MarkTargetSquares(0);
7728                 DragPieceBegin(xPix, yPix, FALSE);
7729                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7730                     promoSweep = defaultPromoChoice;
7731                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7732                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7733                 }
7734             }
7735            }
7736            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7737            second = FALSE;
7738         }
7739         // ignore clicks on holdings
7740         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7741     }
7742
7743     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7744         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7745         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7746         return;
7747     }
7748
7749     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7750         DragPieceEnd(xPix, yPix); dragging = 0;
7751         if(clearFlag) {
7752             // a deferred attempt to click-click move an empty square on top of a piece
7753             boards[currentMove][y][x] = EmptySquare;
7754             ClearHighlights();
7755             DrawPosition(FALSE, boards[currentMove]);
7756             fromX = fromY = -1; clearFlag = 0;
7757             return;
7758         }
7759         if (appData.animateDragging) {
7760             /* Undo animation damage if any */
7761             DrawPosition(FALSE, NULL);
7762         }
7763         if (second) {
7764             /* Second up/down in same square; just abort move */
7765             second = 0;
7766             fromX = fromY = -1;
7767             gatingPiece = EmptySquare;
7768             ClearHighlights();
7769             gotPremove = 0;
7770             ClearPremoveHighlights();
7771             MarkTargetSquares(-1);
7772             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7773         } else {
7774             /* First upclick in same square; start click-click mode */
7775             SetHighlights(x, y, -1, -1);
7776         }
7777         return;
7778     }
7779
7780     clearFlag = 0;
7781
7782     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7783        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7784         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7785         DisplayMessage(_("only marked squares are legal"),"");
7786         DrawPosition(TRUE, NULL);
7787         return; // ignore to-click
7788     }
7789
7790     /* we now have a different from- and (possibly off-board) to-square */
7791     /* Completed move */
7792     if(!sweepSelecting) {
7793         toX = x;
7794         toY = y;
7795     }
7796
7797     piece = boards[currentMove][fromY][fromX];
7798
7799     saveAnimate = appData.animate;
7800     if (clickType == Press) {
7801         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7802         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7803             // must be Edit Position mode with empty-square selected
7804             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7805             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7806             return;
7807         }
7808         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7809             return;
7810         }
7811         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7812             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7813         } else
7814         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7815         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7816           if(appData.sweepSelect) {
7817             promoSweep = defaultPromoChoice;
7818             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7819             selectFlag = 0; lastX = xPix; lastY = yPix;
7820             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7821             saveFlash = appData.flashCount; appData.flashCount = 0;
7822             Sweep(0); // Pawn that is going to promote: preview promotion piece
7823             sweepSelecting = 1;
7824             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7825             MarkTargetSquares(1);
7826           }
7827           return; // promo popup appears on up-click
7828         }
7829         /* Finish clickclick move */
7830         if (appData.animate || appData.highlightLastMove) {
7831             SetHighlights(fromX, fromY, toX, toY);
7832         } else {
7833             ClearHighlights();
7834         }
7835         MarkTargetSquares(1);
7836     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7837         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7838         *promoRestrict = 0; appData.flashCount = saveFlash;
7839         if (appData.animate || appData.highlightLastMove) {
7840             SetHighlights(fromX, fromY, toX, toY);
7841         } else {
7842             ClearHighlights();
7843         }
7844         MarkTargetSquares(1);
7845     } else {
7846 #if 0
7847 // [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
7848         /* Finish drag move */
7849         if (appData.highlightLastMove) {
7850             SetHighlights(fromX, fromY, toX, toY);
7851         } else {
7852             ClearHighlights();
7853         }
7854 #endif
7855         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7856           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7857         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7858         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7859           dragging *= 2;            // flag button-less dragging if we are dragging
7860           MarkTargetSquares(1);
7861           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7862           else {
7863             kill2X = killX; kill2Y = killY;
7864             killX = x; killY = y;     // remember this square as intermediate
7865             ReportClick("put", x, y); // and inform engine
7866             ReportClick("lift", x, y);
7867             MarkTargetSquares(0);
7868             return;
7869           }
7870         }
7871         DragPieceEnd(xPix, yPix); dragging = 0;
7872         /* Don't animate move and drag both */
7873         appData.animate = FALSE;
7874         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7875     }
7876
7877     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7878     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7879         ChessSquare piece = boards[currentMove][fromY][fromX];
7880         if(gameMode == EditPosition && piece != EmptySquare &&
7881            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7882             int n;
7883
7884             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7885                 n = PieceToNumber(piece - (int)BlackPawn);
7886                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7887                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7888                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7889             } else
7890             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7891                 n = PieceToNumber(piece);
7892                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7893                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7894                 boards[currentMove][n][BOARD_WIDTH-2]++;
7895             }
7896             boards[currentMove][fromY][fromX] = EmptySquare;
7897         }
7898         ClearHighlights();
7899         fromX = fromY = -1;
7900         MarkTargetSquares(1);
7901         DrawPosition(TRUE, boards[currentMove]);
7902         return;
7903     }
7904
7905     // off-board moves should not be highlighted
7906     if(x < 0 || y < 0) {
7907         ClearHighlights();
7908         DrawPosition(FALSE, NULL);
7909     } else ReportClick("put", x, y);
7910
7911     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7912  }
7913
7914     if(legal[toY][toX] == 2) { // highlight-induced promotion
7915         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7916         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7917     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7918       if(!*promoRestrict) {           // but has not done that yet
7919         deferChoice = TRUE;           // set up retry for when it does
7920         return;                       // and wait for that
7921       }
7922       promoChoice = ToLower(*promoRestrict); // force engine's choice
7923       deferChoice = FALSE;
7924     }
7925
7926     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7927         SetHighlights(fromX, fromY, toX, toY);
7928         MarkTargetSquares(1);
7929         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7930             // [HGM] super: promotion to captured piece selected from holdings
7931             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7932             promotionChoice = TRUE;
7933             // kludge follows to temporarily execute move on display, without promoting yet
7934             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7935             boards[currentMove][toY][toX] = p;
7936             DrawPosition(FALSE, boards[currentMove]);
7937             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7938             boards[currentMove][toY][toX] = q;
7939             DisplayMessage("Click in holdings to choose piece", "");
7940             return;
7941         }
7942         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7943         PromotionPopUp(promoChoice);
7944     } else {
7945         int oldMove = currentMove;
7946         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7947         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7948         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7949         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7950         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7951            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7952             DrawPosition(TRUE, boards[currentMove]);
7953         else DrawPosition(FALSE, NULL);
7954         fromX = fromY = -1;
7955         flashing = 0;
7956     }
7957     appData.animate = saveAnimate;
7958     if (appData.animate || appData.animateDragging) {
7959         /* Undo animation damage if needed */
7960 //      DrawPosition(FALSE, NULL);
7961     }
7962 }
7963
7964 int
7965 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7966 {   // front-end-free part taken out of PieceMenuPopup
7967     int whichMenu; int xSqr, ySqr;
7968
7969     if(seekGraphUp) { // [HGM] seekgraph
7970         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7971         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7972         return -2;
7973     }
7974
7975     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7976          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7977         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7978         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7979         if(action == Press)   {
7980             originalFlip = flipView;
7981             flipView = !flipView; // temporarily flip board to see game from partners perspective
7982             DrawPosition(TRUE, partnerBoard);
7983             DisplayMessage(partnerStatus, "");
7984             partnerUp = TRUE;
7985         } else if(action == Release) {
7986             flipView = originalFlip;
7987             DrawPosition(TRUE, boards[currentMove]);
7988             partnerUp = FALSE;
7989         }
7990         return -2;
7991     }
7992
7993     xSqr = EventToSquare(x, BOARD_WIDTH);
7994     ySqr = EventToSquare(y, BOARD_HEIGHT);
7995     if (action == Release) {
7996         if(pieceSweep != EmptySquare) {
7997             EditPositionMenuEvent(pieceSweep, toX, toY);
7998             pieceSweep = EmptySquare;
7999         } else UnLoadPV(); // [HGM] pv
8000     }
8001     if (action != Press) return -2; // return code to be ignored
8002     switch (gameMode) {
8003       case IcsExamining:
8004         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8005       case EditPosition:
8006         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8007         if (xSqr < 0 || ySqr < 0) return -1;
8008         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8009         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8010         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8011         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8012         NextPiece(0);
8013         return 2; // grab
8014       case IcsObserving:
8015         if(!appData.icsEngineAnalyze) return -1;
8016       case IcsPlayingWhite:
8017       case IcsPlayingBlack:
8018         if(!appData.zippyPlay) goto noZip;
8019       case AnalyzeMode:
8020       case AnalyzeFile:
8021       case MachinePlaysWhite:
8022       case MachinePlaysBlack:
8023       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8024         if (!appData.dropMenu) {
8025           LoadPV(x, y);
8026           return 2; // flag front-end to grab mouse events
8027         }
8028         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8029            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8030       case EditGame:
8031       noZip:
8032         if (xSqr < 0 || ySqr < 0) return -1;
8033         if (!appData.dropMenu || appData.testLegality &&
8034             gameInfo.variant != VariantBughouse &&
8035             gameInfo.variant != VariantCrazyhouse) return -1;
8036         whichMenu = 1; // drop menu
8037         break;
8038       default:
8039         return -1;
8040     }
8041
8042     if (((*fromX = xSqr) < 0) ||
8043         ((*fromY = ySqr) < 0)) {
8044         *fromX = *fromY = -1;
8045         return -1;
8046     }
8047     if (flipView)
8048       *fromX = BOARD_WIDTH - 1 - *fromX;
8049     else
8050       *fromY = BOARD_HEIGHT - 1 - *fromY;
8051
8052     return whichMenu;
8053 }
8054
8055 void
8056 Wheel (int dir, int x, int y)
8057 {
8058     if(gameMode == EditPosition) {
8059         int xSqr = EventToSquare(x, BOARD_WIDTH);
8060         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8061         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8062         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8063         do {
8064             boards[currentMove][ySqr][xSqr] += dir;
8065             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8066             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8067         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8068         DrawPosition(FALSE, boards[currentMove]);
8069     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8070 }
8071
8072 void
8073 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8074 {
8075 //    char * hint = lastHint;
8076     FrontEndProgramStats stats;
8077
8078     stats.which = cps == &first ? 0 : 1;
8079     stats.depth = cpstats->depth;
8080     stats.nodes = cpstats->nodes;
8081     stats.score = cpstats->score;
8082     stats.time = cpstats->time;
8083     stats.pv = cpstats->movelist;
8084     stats.hint = lastHint;
8085     stats.an_move_index = 0;
8086     stats.an_move_count = 0;
8087
8088     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8089         stats.hint = cpstats->move_name;
8090         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8091         stats.an_move_count = cpstats->nr_moves;
8092     }
8093
8094     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
8095
8096     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8097         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8098
8099     SetProgramStats( &stats );
8100 }
8101
8102 void
8103 ClearEngineOutputPane (int which)
8104 {
8105     static FrontEndProgramStats dummyStats;
8106     dummyStats.which = which;
8107     dummyStats.pv = "#";
8108     SetProgramStats( &dummyStats );
8109 }
8110
8111 #define MAXPLAYERS 500
8112
8113 char *
8114 TourneyStandings (int display)
8115 {
8116     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8117     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8118     char result, *p, *names[MAXPLAYERS];
8119
8120     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8121         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8122     names[0] = p = strdup(appData.participants);
8123     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8124
8125     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8126
8127     while(result = appData.results[nr]) {
8128         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8129         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8130         wScore = bScore = 0;
8131         switch(result) {
8132           case '+': wScore = 2; break;
8133           case '-': bScore = 2; break;
8134           case '=': wScore = bScore = 1; break;
8135           case ' ':
8136           case '*': return strdup("busy"); // tourney not finished
8137         }
8138         score[w] += wScore;
8139         score[b] += bScore;
8140         games[w]++;
8141         games[b]++;
8142         nr++;
8143     }
8144     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8145     for(w=0; w<nPlayers; w++) {
8146         bScore = -1;
8147         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8148         ranking[w] = b; points[w] = bScore; score[b] = -2;
8149     }
8150     p = malloc(nPlayers*34+1);
8151     for(w=0; w<nPlayers && w<display; w++)
8152         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8153     free(names[0]);
8154     return p;
8155 }
8156
8157 void
8158 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8159 {       // count all piece types
8160         int p, f, r;
8161         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8162         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8163         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8164                 p = board[r][f];
8165                 pCnt[p]++;
8166                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8167                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8168                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8169                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8170                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8171                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8172         }
8173 }
8174
8175 int
8176 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8177 {
8178         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8179         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8180
8181         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8182         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8183         if(myPawns == 2 && nMine == 3) // KPP
8184             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8185         if(myPawns == 1 && nMine == 2) // KP
8186             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8187         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8188             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8189         if(myPawns) return FALSE;
8190         if(pCnt[WhiteRook+side])
8191             return pCnt[BlackRook-side] ||
8192                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8193                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8194                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8195         if(pCnt[WhiteCannon+side]) {
8196             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8197             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8198         }
8199         if(pCnt[WhiteKnight+side])
8200             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8201         return FALSE;
8202 }
8203
8204 int
8205 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8206 {
8207         VariantClass v = gameInfo.variant;
8208
8209         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8210         if(v == VariantShatranj) return TRUE; // always winnable through baring
8211         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8212         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8213
8214         if(v == VariantXiangqi) {
8215                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8216
8217                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8218                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8219                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8220                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8221                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8222                 if(stale) // we have at least one last-rank P plus perhaps C
8223                     return majors // KPKX
8224                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8225                 else // KCA*E*
8226                     return pCnt[WhiteFerz+side] // KCAK
8227                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8228                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8229                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8230
8231         } else if(v == VariantKnightmate) {
8232                 if(nMine == 1) return FALSE;
8233                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8234         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8235                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8236
8237                 if(nMine == 1) return FALSE; // bare King
8238                 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
8239                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8240                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8241                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8242                 if(pCnt[WhiteKnight+side])
8243                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8244                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8245                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8246                 if(nBishops)
8247                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8248                 if(pCnt[WhiteAlfil+side])
8249                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8250                 if(pCnt[WhiteWazir+side])
8251                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8252         }
8253
8254         return TRUE;
8255 }
8256
8257 int
8258 CompareWithRights (Board b1, Board b2)
8259 {
8260     int rights = 0;
8261     if(!CompareBoards(b1, b2)) return FALSE;
8262     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8263     /* compare castling rights */
8264     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8265            rights++; /* King lost rights, while rook still had them */
8266     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8267         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8268            rights++; /* but at least one rook lost them */
8269     }
8270     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8271            rights++;
8272     if( b1[CASTLING][5] != NoRights ) {
8273         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8274            rights++;
8275     }
8276     return rights == 0;
8277 }
8278
8279 int
8280 Adjudicate (ChessProgramState *cps)
8281 {       // [HGM] some adjudications useful with buggy engines
8282         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8283         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8284         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8285         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8286         int k, drop, count = 0; static int bare = 1;
8287         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8288         Boolean canAdjudicate = !appData.icsActive;
8289
8290         // most tests only when we understand the game, i.e. legality-checking on
8291             if( appData.testLegality )
8292             {   /* [HGM] Some more adjudications for obstinate engines */
8293                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8294                 static int moveCount = 6;
8295                 ChessMove result;
8296                 char *reason = NULL;
8297
8298                 /* Count what is on board. */
8299                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8300
8301                 /* Some material-based adjudications that have to be made before stalemate test */
8302                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8303                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8304                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8305                      if(canAdjudicate && appData.checkMates) {
8306                          if(engineOpponent)
8307                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8308                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8309                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8310                          return 1;
8311                      }
8312                 }
8313
8314                 /* Bare King in Shatranj (loses) or Losers (wins) */
8315                 if( nrW == 1 || nrB == 1) {
8316                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8317                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8318                      if(canAdjudicate && appData.checkMates) {
8319                          if(engineOpponent)
8320                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8321                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8322                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8323                          return 1;
8324                      }
8325                   } else
8326                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8327                   {    /* bare King */
8328                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8329                         if(canAdjudicate && appData.checkMates) {
8330                             /* but only adjudicate if adjudication enabled */
8331                             if(engineOpponent)
8332                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8333                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8334                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8335                             return 1;
8336                         }
8337                   }
8338                 } else bare = 1;
8339
8340
8341             // don't wait for engine to announce game end if we can judge ourselves
8342             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8343               case MT_CHECK:
8344                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8345                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8346                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8347                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8348                             checkCnt++;
8349                         if(checkCnt >= 2) {
8350                             reason = "Xboard adjudication: 3rd check";
8351                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8352                             break;
8353                         }
8354                     }
8355                 }
8356               case MT_NONE:
8357               default:
8358                 break;
8359               case MT_STEALMATE:
8360               case MT_STALEMATE:
8361               case MT_STAINMATE:
8362                 reason = "Xboard adjudication: Stalemate";
8363                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8364                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8365                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8366                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8367                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8368                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8369                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8370                                                                         EP_CHECKMATE : EP_WINS);
8371                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8372                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8373                 }
8374                 break;
8375               case MT_CHECKMATE:
8376                 reason = "Xboard adjudication: Checkmate";
8377                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8378                 if(gameInfo.variant == VariantShogi) {
8379                     if(forwardMostMove > backwardMostMove
8380                        && moveList[forwardMostMove-1][1] == '@'
8381                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8382                         reason = "XBoard adjudication: pawn-drop mate";
8383                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8384                     }
8385                 }
8386                 break;
8387             }
8388
8389                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8390                     case EP_STALEMATE:
8391                         result = GameIsDrawn; break;
8392                     case EP_CHECKMATE:
8393                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8394                     case EP_WINS:
8395                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8396                     default:
8397                         result = EndOfFile;
8398                 }
8399                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8400                     if(engineOpponent)
8401                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8402                     GameEnds( result, reason, GE_XBOARD );
8403                     return 1;
8404                 }
8405
8406                 /* Next absolutely insufficient mating material. */
8407                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8408                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8409                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8410
8411                      /* always flag draws, for judging claims */
8412                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8413
8414                      if(canAdjudicate && appData.materialDraws) {
8415                          /* but only adjudicate them if adjudication enabled */
8416                          if(engineOpponent) {
8417                            SendToProgram("force\n", engineOpponent); // suppress reply
8418                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8419                          }
8420                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8421                          return 1;
8422                      }
8423                 }
8424
8425                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8426                 if(gameInfo.variant == VariantXiangqi ?
8427                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8428                  : nrW + nrB == 4 &&
8429                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8430                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8431                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8432                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8433                    ) ) {
8434                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8435                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8436                           if(engineOpponent) {
8437                             SendToProgram("force\n", engineOpponent); // suppress reply
8438                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8439                           }
8440                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8441                           return 1;
8442                      }
8443                 } else moveCount = 6;
8444             }
8445
8446         // Repetition draws and 50-move rule can be applied independently of legality testing
8447
8448                 /* Check for rep-draws */
8449                 count = 0;
8450                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8451                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8452                 for(k = forwardMostMove-2;
8453                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8454                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8455                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8456                     k-=2)
8457                 {   int rights=0;
8458                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8459                         /* compare castling rights */
8460                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8461                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8462                                 rights++; /* King lost rights, while rook still had them */
8463                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8464                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8465                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8466                                    rights++; /* but at least one rook lost them */
8467                         }
8468                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8469                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8470                                 rights++;
8471                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8472                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8473                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8474                                    rights++;
8475                         }
8476                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8477                             && appData.drawRepeats > 1) {
8478                              /* adjudicate after user-specified nr of repeats */
8479                              int result = GameIsDrawn;
8480                              char *details = "XBoard adjudication: repetition draw";
8481                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8482                                 // [HGM] xiangqi: check for forbidden perpetuals
8483                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8484                                 for(m=forwardMostMove; m>k; m-=2) {
8485                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8486                                         ourPerpetual = 0; // the current mover did not always check
8487                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8488                                         hisPerpetual = 0; // the opponent did not always check
8489                                 }
8490                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8491                                                                         ourPerpetual, hisPerpetual);
8492                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8493                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8494                                     details = "Xboard adjudication: perpetual checking";
8495                                 } else
8496                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8497                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8498                                 } else
8499                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8500                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8501                                         result = BlackWins;
8502                                         details = "Xboard adjudication: repetition";
8503                                     }
8504                                 } else // it must be XQ
8505                                 // Now check for perpetual chases
8506                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8507                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8508                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8509                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8510                                         static char resdet[MSG_SIZ];
8511                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8512                                         details = resdet;
8513                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8514                                     } else
8515                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8516                                         break; // Abort repetition-checking loop.
8517                                 }
8518                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8519                              }
8520                              if(engineOpponent) {
8521                                SendToProgram("force\n", engineOpponent); // suppress reply
8522                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8523                              }
8524                              GameEnds( result, details, GE_XBOARD );
8525                              return 1;
8526                         }
8527                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8528                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8529                     }
8530                 }
8531
8532                 /* Now we test for 50-move draws. Determine ply count */
8533                 count = forwardMostMove;
8534                 /* look for last irreversble move */
8535                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8536                     count--;
8537                 /* if we hit starting position, add initial plies */
8538                 if( count == backwardMostMove )
8539                     count -= initialRulePlies;
8540                 count = forwardMostMove - count;
8541                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8542                         // adjust reversible move counter for checks in Xiangqi
8543                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8544                         if(i < backwardMostMove) i = backwardMostMove;
8545                         while(i <= forwardMostMove) {
8546                                 lastCheck = inCheck; // check evasion does not count
8547                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8548                                 if(inCheck || lastCheck) count--; // check does not count
8549                                 i++;
8550                         }
8551                 }
8552                 if( count >= 100)
8553                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8554                          /* this is used to judge if draw claims are legal */
8555                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8556                          if(engineOpponent) {
8557                            SendToProgram("force\n", engineOpponent); // suppress reply
8558                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8559                          }
8560                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8561                          return 1;
8562                 }
8563
8564                 /* if draw offer is pending, treat it as a draw claim
8565                  * when draw condition present, to allow engines a way to
8566                  * claim draws before making their move to avoid a race
8567                  * condition occurring after their move
8568                  */
8569                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8570                          char *p = NULL;
8571                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8572                              p = "Draw claim: 50-move rule";
8573                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8574                              p = "Draw claim: 3-fold repetition";
8575                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8576                              p = "Draw claim: insufficient mating material";
8577                          if( p != NULL && canAdjudicate) {
8578                              if(engineOpponent) {
8579                                SendToProgram("force\n", engineOpponent); // suppress reply
8580                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8581                              }
8582                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8583                              return 1;
8584                          }
8585                 }
8586
8587                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8588                     if(engineOpponent) {
8589                       SendToProgram("force\n", engineOpponent); // suppress reply
8590                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8591                     }
8592                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8593                     return 1;
8594                 }
8595         return 0;
8596 }
8597
8598 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8599 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8600 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8601
8602 static int
8603 BitbaseProbe ()
8604 {
8605     int pieces[10], squares[10], cnt=0, r, f, res;
8606     static int loaded;
8607     static PPROBE_EGBB probeBB;
8608     if(!appData.testLegality) return 10;
8609     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8610     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8611     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8612     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8613         ChessSquare piece = boards[forwardMostMove][r][f];
8614         int black = (piece >= BlackPawn);
8615         int type = piece - black*BlackPawn;
8616         if(piece == EmptySquare) continue;
8617         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8618         if(type == WhiteKing) type = WhiteQueen + 1;
8619         type = egbbCode[type];
8620         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8621         pieces[cnt] = type + black*6;
8622         if(++cnt > 5) return 11;
8623     }
8624     pieces[cnt] = squares[cnt] = 0;
8625     // probe EGBB
8626     if(loaded == 2) return 13; // loading failed before
8627     if(loaded == 0) {
8628         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8629         HMODULE lib;
8630         PLOAD_EGBB loadBB;
8631         loaded = 2; // prepare for failure
8632         if(!path) return 13; // no egbb installed
8633         strncpy(buf, path + 8, MSG_SIZ);
8634         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8635         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8636         lib = LoadLibrary(buf);
8637         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8638         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8639         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8640         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8641         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8642         loaded = 1; // success!
8643     }
8644     res = probeBB(forwardMostMove & 1, pieces, squares);
8645     return res > 0 ? 1 : res < 0 ? -1 : 0;
8646 }
8647
8648 char *
8649 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8650 {   // [HGM] book: this routine intercepts moves to simulate book replies
8651     char *bookHit = NULL;
8652
8653     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8654         char buf[MSG_SIZ];
8655         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8656         SendToProgram(buf, cps);
8657     }
8658     //first determine if the incoming move brings opponent into his book
8659     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8660         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8661     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8662     if(bookHit != NULL && !cps->bookSuspend) {
8663         // make sure opponent is not going to reply after receiving move to book position
8664         SendToProgram("force\n", cps);
8665         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8666     }
8667     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8668     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8669     // now arrange restart after book miss
8670     if(bookHit) {
8671         // after a book hit we never send 'go', and the code after the call to this routine
8672         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8673         char buf[MSG_SIZ], *move = bookHit;
8674         if(cps->useSAN) {
8675             int fromX, fromY, toX, toY;
8676             char promoChar;
8677             ChessMove moveType;
8678             move = buf + 30;
8679             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8680                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8681                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8682                                     PosFlags(forwardMostMove),
8683                                     fromY, fromX, toY, toX, promoChar, move);
8684             } else {
8685                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8686                 bookHit = NULL;
8687             }
8688         }
8689         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8690         SendToProgram(buf, cps);
8691         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8692     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8693         SendToProgram("go\n", cps);
8694         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8695     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8696         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8697             SendToProgram("go\n", cps);
8698         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8699     }
8700     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8701 }
8702
8703 int
8704 LoadError (char *errmess, ChessProgramState *cps)
8705 {   // unloads engine and switches back to -ncp mode if it was first
8706     if(cps->initDone) return FALSE;
8707     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8708     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8709     cps->pr = NoProc;
8710     if(cps == &first) {
8711         appData.noChessProgram = TRUE;
8712         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8713         gameMode = BeginningOfGame; ModeHighlight();
8714         SetNCPMode();
8715     }
8716     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8717     DisplayMessage("", ""); // erase waiting message
8718     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8719     return TRUE;
8720 }
8721
8722 char *savedMessage;
8723 ChessProgramState *savedState;
8724 void
8725 DeferredBookMove (void)
8726 {
8727         if(savedState->lastPing != savedState->lastPong)
8728                     ScheduleDelayedEvent(DeferredBookMove, 10);
8729         else
8730         HandleMachineMove(savedMessage, savedState);
8731 }
8732
8733 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8734 static ChessProgramState *stalledEngine;
8735 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8736
8737 void
8738 HandleMachineMove (char *message, ChessProgramState *cps)
8739 {
8740     static char firstLeg[20], legs;
8741     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8742     char realname[MSG_SIZ];
8743     int fromX, fromY, toX, toY;
8744     ChessMove moveType;
8745     char promoChar, roar;
8746     char *p, *pv=buf1;
8747     int oldError;
8748     char *bookHit;
8749
8750     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8751         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8752         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8753             DisplayError(_("Invalid pairing from pairing engine"), 0);
8754             return;
8755         }
8756         pairingReceived = 1;
8757         NextMatchGame();
8758         return; // Skim the pairing messages here.
8759     }
8760
8761     oldError = cps->userError; cps->userError = 0;
8762
8763 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8764     /*
8765      * Kludge to ignore BEL characters
8766      */
8767     while (*message == '\007') message++;
8768
8769     /*
8770      * [HGM] engine debug message: ignore lines starting with '#' character
8771      */
8772     if(cps->debug && *message == '#') return;
8773
8774     /*
8775      * Look for book output
8776      */
8777     if (cps == &first && bookRequested) {
8778         if (message[0] == '\t' || message[0] == ' ') {
8779             /* Part of the book output is here; append it */
8780             strcat(bookOutput, message);
8781             strcat(bookOutput, "  \n");
8782             return;
8783         } else if (bookOutput[0] != NULLCHAR) {
8784             /* All of book output has arrived; display it */
8785             char *p = bookOutput;
8786             while (*p != NULLCHAR) {
8787                 if (*p == '\t') *p = ' ';
8788                 p++;
8789             }
8790             DisplayInformation(bookOutput);
8791             bookRequested = FALSE;
8792             /* Fall through to parse the current output */
8793         }
8794     }
8795
8796     /*
8797      * Look for machine move.
8798      */
8799     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8800         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8801     {
8802         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8803             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8804             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8805             stalledEngine = cps;
8806             if(appData.ponderNextMove) { // bring opponent out of ponder
8807                 if(gameMode == TwoMachinesPlay) {
8808                     if(cps->other->pause)
8809                         PauseEngine(cps->other);
8810                     else
8811                         SendToProgram("easy\n", cps->other);
8812                 }
8813             }
8814             StopClocks();
8815             return;
8816         }
8817
8818       if(cps->usePing) {
8819
8820         /* This method is only useful on engines that support ping */
8821         if(abortEngineThink) {
8822             if (appData.debugMode) {
8823                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8824             }
8825             SendToProgram("undo\n", cps);
8826             return;
8827         }
8828
8829         if (cps->lastPing != cps->lastPong) {
8830             /* Extra move from before last new; ignore */
8831             if (appData.debugMode) {
8832                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8833             }
8834           return;
8835         }
8836
8837       } else {
8838
8839         int machineWhite = FALSE;
8840
8841         switch (gameMode) {
8842           case BeginningOfGame:
8843             /* Extra move from before last reset; ignore */
8844             if (appData.debugMode) {
8845                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8846             }
8847             return;
8848
8849           case EndOfGame:
8850           case IcsIdle:
8851           default:
8852             /* Extra move after we tried to stop.  The mode test is
8853                not a reliable way of detecting this problem, but it's
8854                the best we can do on engines that don't support ping.
8855             */
8856             if (appData.debugMode) {
8857                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8858                         cps->which, gameMode);
8859             }
8860             SendToProgram("undo\n", cps);
8861             return;
8862
8863           case MachinePlaysWhite:
8864           case IcsPlayingWhite:
8865             machineWhite = TRUE;
8866             break;
8867
8868           case MachinePlaysBlack:
8869           case IcsPlayingBlack:
8870             machineWhite = FALSE;
8871             break;
8872
8873           case TwoMachinesPlay:
8874             machineWhite = (cps->twoMachinesColor[0] == 'w');
8875             break;
8876         }
8877         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8878             if (appData.debugMode) {
8879                 fprintf(debugFP,
8880                         "Ignoring move out of turn by %s, gameMode %d"
8881                         ", forwardMost %d\n",
8882                         cps->which, gameMode, forwardMostMove);
8883             }
8884             return;
8885         }
8886       }
8887
8888         if(cps->alphaRank) AlphaRank(machineMove, 4);
8889
8890         // [HGM] lion: (some very limited) support for Alien protocol
8891         killX = killY = kill2X = kill2Y = -1;
8892         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8893             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8894             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8895             return;
8896         }
8897         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8898             char *q = strchr(p+1, ',');            // second comma?
8899             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8900             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8901             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8902         }
8903         if(firstLeg[0]) { // there was a previous leg;
8904             // only support case where same piece makes two step
8905             char buf[20], *p = machineMove+1, *q = buf+1, f;
8906             safeStrCpy(buf, machineMove, 20);
8907             while(isdigit(*q)) q++; // find start of to-square
8908             safeStrCpy(machineMove, firstLeg, 20);
8909             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8910             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
8911             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)
8912             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8913             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8914             firstLeg[0] = NULLCHAR; legs = 0;
8915         }
8916
8917         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8918                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8919             /* Machine move could not be parsed; ignore it. */
8920           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8921                     machineMove, _(cps->which));
8922             DisplayMoveError(buf1);
8923             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8924                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8925             if (gameMode == TwoMachinesPlay) {
8926               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8927                        buf1, GE_XBOARD);
8928             }
8929             return;
8930         }
8931
8932         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8933         /* So we have to redo legality test with true e.p. status here,  */
8934         /* to make sure an illegal e.p. capture does not slip through,   */
8935         /* to cause a forfeit on a justified illegal-move complaint      */
8936         /* of the opponent.                                              */
8937         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8938            ChessMove moveType;
8939            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8940                              fromY, fromX, toY, toX, promoChar);
8941             if(moveType == IllegalMove) {
8942               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8944                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8945                            buf1, GE_XBOARD);
8946                 return;
8947            } else if(!appData.fischerCastling)
8948            /* [HGM] Kludge to handle engines that send FRC-style castling
8949               when they shouldn't (like TSCP-Gothic) */
8950            switch(moveType) {
8951              case WhiteASideCastleFR:
8952              case BlackASideCastleFR:
8953                toX+=2;
8954                currentMoveString[2]++;
8955                break;
8956              case WhiteHSideCastleFR:
8957              case BlackHSideCastleFR:
8958                toX--;
8959                currentMoveString[2]--;
8960                break;
8961              default: ; // nothing to do, but suppresses warning of pedantic compilers
8962            }
8963         }
8964         hintRequested = FALSE;
8965         lastHint[0] = NULLCHAR;
8966         bookRequested = FALSE;
8967         /* Program may be pondering now */
8968         cps->maybeThinking = TRUE;
8969         if (cps->sendTime == 2) cps->sendTime = 1;
8970         if (cps->offeredDraw) cps->offeredDraw--;
8971
8972         /* [AS] Save move info*/
8973         pvInfoList[ forwardMostMove ].score = programStats.score;
8974         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8975         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8976
8977         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8978
8979         /* Test suites abort the 'game' after one move */
8980         if(*appData.finger) {
8981            static FILE *f;
8982            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8983            if(!f) f = fopen(appData.finger, "w");
8984            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8985            else { DisplayFatalError("Bad output file", errno, 0); return; }
8986            free(fen);
8987            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8988         }
8989         if(appData.epd) {
8990            if(solvingTime >= 0) {
8991               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8992               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8993            } else {
8994               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8995               if(solvingTime == -2) second.matchWins++;
8996            }
8997            OutputKibitz(2, buf1);
8998            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8999         }
9000
9001         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9002         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9003             int count = 0;
9004
9005             while( count < adjudicateLossPlies ) {
9006                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9007
9008                 if( count & 1 ) {
9009                     score = -score; /* Flip score for winning side */
9010                 }
9011
9012                 if( score > appData.adjudicateLossThreshold ) {
9013                     break;
9014                 }
9015
9016                 count++;
9017             }
9018
9019             if( count >= adjudicateLossPlies ) {
9020                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9021
9022                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9023                     "Xboard adjudication",
9024                     GE_XBOARD );
9025
9026                 return;
9027             }
9028         }
9029
9030         if(Adjudicate(cps)) {
9031             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9032             return; // [HGM] adjudicate: for all automatic game ends
9033         }
9034
9035 #if ZIPPY
9036         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9037             first.initDone) {
9038           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9039                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9040                 SendToICS("draw ");
9041                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9042           }
9043           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9044           ics_user_moved = 1;
9045           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9046                 char buf[3*MSG_SIZ];
9047
9048                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9049                         programStats.score / 100.,
9050                         programStats.depth,
9051                         programStats.time / 100.,
9052                         (unsigned int)programStats.nodes,
9053                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9054                         programStats.movelist);
9055                 SendToICS(buf);
9056           }
9057         }
9058 #endif
9059
9060         /* [AS] Clear stats for next move */
9061         ClearProgramStats();
9062         thinkOutput[0] = NULLCHAR;
9063         hiddenThinkOutputState = 0;
9064
9065         bookHit = NULL;
9066         if (gameMode == TwoMachinesPlay) {
9067             /* [HGM] relaying draw offers moved to after reception of move */
9068             /* and interpreting offer as claim if it brings draw condition */
9069             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9070                 SendToProgram("draw\n", cps->other);
9071             }
9072             if (cps->other->sendTime) {
9073                 SendTimeRemaining(cps->other,
9074                                   cps->other->twoMachinesColor[0] == 'w');
9075             }
9076             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9077             if (firstMove && !bookHit) {
9078                 firstMove = FALSE;
9079                 if (cps->other->useColors) {
9080                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9081                 }
9082                 SendToProgram("go\n", cps->other);
9083             }
9084             cps->other->maybeThinking = TRUE;
9085         }
9086
9087         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9088
9089         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9090
9091         if (!pausing && appData.ringBellAfterMoves) {
9092             if(!roar) RingBell();
9093         }
9094
9095         /*
9096          * Reenable menu items that were disabled while
9097          * machine was thinking
9098          */
9099         if (gameMode != TwoMachinesPlay)
9100             SetUserThinkingEnables();
9101
9102         // [HGM] book: after book hit opponent has received move and is now in force mode
9103         // force the book reply into it, and then fake that it outputted this move by jumping
9104         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9105         if(bookHit) {
9106                 static char bookMove[MSG_SIZ]; // a bit generous?
9107
9108                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9109                 strcat(bookMove, bookHit);
9110                 message = bookMove;
9111                 cps = cps->other;
9112                 programStats.nodes = programStats.depth = programStats.time =
9113                 programStats.score = programStats.got_only_move = 0;
9114                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9115
9116                 if(cps->lastPing != cps->lastPong) {
9117                     savedMessage = message; // args for deferred call
9118                     savedState = cps;
9119                     ScheduleDelayedEvent(DeferredBookMove, 10);
9120                     return;
9121                 }
9122                 goto FakeBookMove;
9123         }
9124
9125         return;
9126     }
9127
9128     /* Set special modes for chess engines.  Later something general
9129      *  could be added here; for now there is just one kludge feature,
9130      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9131      *  when "xboard" is given as an interactive command.
9132      */
9133     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9134         cps->useSigint = FALSE;
9135         cps->useSigterm = FALSE;
9136     }
9137     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9138       ParseFeatures(message+8, cps);
9139       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9140     }
9141
9142     if (!strncmp(message, "setup ", 6) && 
9143         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9144           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9145                                         ) { // [HGM] allow first engine to define opening position
9146       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9147       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9148       *buf = NULLCHAR;
9149       if(sscanf(message, "setup (%s", buf) == 1) {
9150         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9151         ASSIGN(appData.pieceToCharTable, buf);
9152       }
9153       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9154       if(dummy >= 3) {
9155         while(message[s] && message[s++] != ' ');
9156         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9157            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9158             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9159             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9160             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9161           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9162           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9163           startedFromSetupPosition = FALSE;
9164         }
9165       }
9166       if(startedFromSetupPosition) return;
9167       ParseFEN(boards[0], &dummy, message+s, FALSE);
9168       DrawPosition(TRUE, boards[0]);
9169       CopyBoard(initialPosition, boards[0]);
9170       startedFromSetupPosition = TRUE;
9171       return;
9172     }
9173     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9174       ChessSquare piece = WhitePawn;
9175       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9176       if(*p == '+') promoted++, ID = *++p;
9177       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9178       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9179       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9180       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9181       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9182       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9183       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9184       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9185                                                && gameInfo.variant != VariantGreat
9186                                                && gameInfo.variant != VariantFairy    ) return;
9187       if(piece < EmptySquare) {
9188         pieceDefs = TRUE;
9189         ASSIGN(pieceDesc[piece], buf1);
9190         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9191       }
9192       return;
9193     }
9194     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9195       if(deferChoice) {
9196         LeftClick(Press, 0, 0); // finish the click that was interrupted
9197       } else if(promoSweep != EmptySquare) {
9198         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9199         if(strlen(promoRestrict) > 1) Sweep(0);
9200       }
9201       return;
9202     }
9203     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9204      * want this, I was asked to put it in, and obliged.
9205      */
9206     if (!strncmp(message, "setboard ", 9)) {
9207         Board initial_position;
9208
9209         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9210
9211         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9212             DisplayError(_("Bad FEN received from engine"), 0);
9213             return ;
9214         } else {
9215            Reset(TRUE, FALSE);
9216            CopyBoard(boards[0], initial_position);
9217            initialRulePlies = FENrulePlies;
9218            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9219            else gameMode = MachinePlaysBlack;
9220            DrawPosition(FALSE, boards[currentMove]);
9221         }
9222         return;
9223     }
9224
9225     /*
9226      * Look for communication commands
9227      */
9228     if (!strncmp(message, "telluser ", 9)) {
9229         if(message[9] == '\\' && message[10] == '\\')
9230             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9231         PlayTellSound();
9232         DisplayNote(message + 9);
9233         return;
9234     }
9235     if (!strncmp(message, "tellusererror ", 14)) {
9236         cps->userError = 1;
9237         if(message[14] == '\\' && message[15] == '\\')
9238             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9239         PlayTellSound();
9240         DisplayError(message + 14, 0);
9241         return;
9242     }
9243     if (!strncmp(message, "tellopponent ", 13)) {
9244       if (appData.icsActive) {
9245         if (loggedOn) {
9246           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9247           SendToICS(buf1);
9248         }
9249       } else {
9250         DisplayNote(message + 13);
9251       }
9252       return;
9253     }
9254     if (!strncmp(message, "tellothers ", 11)) {
9255       if (appData.icsActive) {
9256         if (loggedOn) {
9257           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9258           SendToICS(buf1);
9259         }
9260       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9261       return;
9262     }
9263     if (!strncmp(message, "tellall ", 8)) {
9264       if (appData.icsActive) {
9265         if (loggedOn) {
9266           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9267           SendToICS(buf1);
9268         }
9269       } else {
9270         DisplayNote(message + 8);
9271       }
9272       return;
9273     }
9274     if (strncmp(message, "warning", 7) == 0) {
9275         /* Undocumented feature, use tellusererror in new code */
9276         DisplayError(message, 0);
9277         return;
9278     }
9279     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9280         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9281         strcat(realname, " query");
9282         AskQuestion(realname, buf2, buf1, cps->pr);
9283         return;
9284     }
9285     /* Commands from the engine directly to ICS.  We don't allow these to be
9286      *  sent until we are logged on. Crafty kibitzes have been known to
9287      *  interfere with the login process.
9288      */
9289     if (loggedOn) {
9290         if (!strncmp(message, "tellics ", 8)) {
9291             SendToICS(message + 8);
9292             SendToICS("\n");
9293             return;
9294         }
9295         if (!strncmp(message, "tellicsnoalias ", 15)) {
9296             SendToICS(ics_prefix);
9297             SendToICS(message + 15);
9298             SendToICS("\n");
9299             return;
9300         }
9301         /* The following are for backward compatibility only */
9302         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9303             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9304             SendToICS(ics_prefix);
9305             SendToICS(message);
9306             SendToICS("\n");
9307             return;
9308         }
9309     }
9310     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9311         if(initPing == cps->lastPong) {
9312             if(gameInfo.variant == VariantUnknown) {
9313                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9314                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9315                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9316             }
9317             initPing = -1;
9318         }
9319         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9320             abortEngineThink = FALSE;
9321             DisplayMessage("", "");
9322             ThawUI();
9323         }
9324         return;
9325     }
9326     if(!strncmp(message, "highlight ", 10)) {
9327         if(appData.testLegality && !*engineVariant && appData.markers) return;
9328         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9329         return;
9330     }
9331     if(!strncmp(message, "click ", 6)) {
9332         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9333         if(appData.testLegality || !appData.oneClick) return;
9334         sscanf(message+6, "%c%d%c", &f, &y, &c);
9335         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9336         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9337         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9338         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9339         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9340         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9341             LeftClick(Release, lastLeftX, lastLeftY);
9342         controlKey  = (c == ',');
9343         LeftClick(Press, x, y);
9344         LeftClick(Release, x, y);
9345         first.highlight = f;
9346         return;
9347     }
9348     /*
9349      * If the move is illegal, cancel it and redraw the board.
9350      * Also deal with other error cases.  Matching is rather loose
9351      * here to accommodate engines written before the spec.
9352      */
9353     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9354         strncmp(message, "Error", 5) == 0) {
9355         if (StrStr(message, "name") ||
9356             StrStr(message, "rating") || StrStr(message, "?") ||
9357             StrStr(message, "result") || StrStr(message, "board") ||
9358             StrStr(message, "bk") || StrStr(message, "computer") ||
9359             StrStr(message, "variant") || StrStr(message, "hint") ||
9360             StrStr(message, "random") || StrStr(message, "depth") ||
9361             StrStr(message, "accepted")) {
9362             return;
9363         }
9364         if (StrStr(message, "protover")) {
9365           /* Program is responding to input, so it's apparently done
9366              initializing, and this error message indicates it is
9367              protocol version 1.  So we don't need to wait any longer
9368              for it to initialize and send feature commands. */
9369           FeatureDone(cps, 1);
9370           cps->protocolVersion = 1;
9371           return;
9372         }
9373         cps->maybeThinking = FALSE;
9374
9375         if (StrStr(message, "draw")) {
9376             /* Program doesn't have "draw" command */
9377             cps->sendDrawOffers = 0;
9378             return;
9379         }
9380         if (cps->sendTime != 1 &&
9381             (StrStr(message, "time") || StrStr(message, "otim"))) {
9382           /* Program apparently doesn't have "time" or "otim" command */
9383           cps->sendTime = 0;
9384           return;
9385         }
9386         if (StrStr(message, "analyze")) {
9387             cps->analysisSupport = FALSE;
9388             cps->analyzing = FALSE;
9389 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9390             EditGameEvent(); // [HGM] try to preserve loaded game
9391             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9392             DisplayError(buf2, 0);
9393             return;
9394         }
9395         if (StrStr(message, "(no matching move)st")) {
9396           /* Special kludge for GNU Chess 4 only */
9397           cps->stKludge = TRUE;
9398           SendTimeControl(cps, movesPerSession, timeControl,
9399                           timeIncrement, appData.searchDepth,
9400                           searchTime);
9401           return;
9402         }
9403         if (StrStr(message, "(no matching move)sd")) {
9404           /* Special kludge for GNU Chess 4 only */
9405           cps->sdKludge = TRUE;
9406           SendTimeControl(cps, movesPerSession, timeControl,
9407                           timeIncrement, appData.searchDepth,
9408                           searchTime);
9409           return;
9410         }
9411         if (!StrStr(message, "llegal")) {
9412             return;
9413         }
9414         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9415             gameMode == IcsIdle) return;
9416         if (forwardMostMove <= backwardMostMove) return;
9417         if (pausing) PauseEvent();
9418       if(appData.forceIllegal) {
9419             // [HGM] illegal: machine refused move; force position after move into it
9420           SendToProgram("force\n", cps);
9421           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9422                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9423                 // when black is to move, while there might be nothing on a2 or black
9424                 // might already have the move. So send the board as if white has the move.
9425                 // But first we must change the stm of the engine, as it refused the last move
9426                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9427                 if(WhiteOnMove(forwardMostMove)) {
9428                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9429                     SendBoard(cps, forwardMostMove); // kludgeless board
9430                 } else {
9431                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9432                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9433                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9434                 }
9435           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9436             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9437                  gameMode == TwoMachinesPlay)
9438               SendToProgram("go\n", cps);
9439             return;
9440       } else
9441         if (gameMode == PlayFromGameFile) {
9442             /* Stop reading this game file */
9443             gameMode = EditGame;
9444             ModeHighlight();
9445         }
9446         /* [HGM] illegal-move claim should forfeit game when Xboard */
9447         /* only passes fully legal moves                            */
9448         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9449             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9450                                 "False illegal-move claim", GE_XBOARD );
9451             return; // do not take back move we tested as valid
9452         }
9453         currentMove = forwardMostMove-1;
9454         DisplayMove(currentMove-1); /* before DisplayMoveError */
9455         SwitchClocks(forwardMostMove-1); // [HGM] race
9456         DisplayBothClocks();
9457         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9458                 parseList[currentMove], _(cps->which));
9459         DisplayMoveError(buf1);
9460         DrawPosition(FALSE, boards[currentMove]);
9461
9462         SetUserThinkingEnables();
9463         return;
9464     }
9465     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9466         /* Program has a broken "time" command that
9467            outputs a string not ending in newline.
9468            Don't use it. */
9469         cps->sendTime = 0;
9470     }
9471     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9472         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9473             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9474     }
9475
9476     /*
9477      * If chess program startup fails, exit with an error message.
9478      * Attempts to recover here are futile. [HGM] Well, we try anyway
9479      */
9480     if ((StrStr(message, "unknown host") != NULL)
9481         || (StrStr(message, "No remote directory") != NULL)
9482         || (StrStr(message, "not found") != NULL)
9483         || (StrStr(message, "No such file") != NULL)
9484         || (StrStr(message, "can't alloc") != NULL)
9485         || (StrStr(message, "Permission denied") != NULL)) {
9486
9487         cps->maybeThinking = FALSE;
9488         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9489                 _(cps->which), cps->program, cps->host, message);
9490         RemoveInputSource(cps->isr);
9491         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9492             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9493             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9494         }
9495         return;
9496     }
9497
9498     /*
9499      * Look for hint output
9500      */
9501     if (sscanf(message, "Hint: %s", buf1) == 1) {
9502         if (cps == &first && hintRequested) {
9503             hintRequested = FALSE;
9504             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9505                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9506                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9507                                     PosFlags(forwardMostMove),
9508                                     fromY, fromX, toY, toX, promoChar, buf1);
9509                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9510                 DisplayInformation(buf2);
9511             } else {
9512                 /* Hint move could not be parsed!? */
9513               snprintf(buf2, sizeof(buf2),
9514                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9515                         buf1, _(cps->which));
9516                 DisplayError(buf2, 0);
9517             }
9518         } else {
9519           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9520         }
9521         return;
9522     }
9523
9524     /*
9525      * Ignore other messages if game is not in progress
9526      */
9527     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9528         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9529
9530     /*
9531      * look for win, lose, draw, or draw offer
9532      */
9533     if (strncmp(message, "1-0", 3) == 0) {
9534         char *p, *q, *r = "";
9535         p = strchr(message, '{');
9536         if (p) {
9537             q = strchr(p, '}');
9538             if (q) {
9539                 *q = NULLCHAR;
9540                 r = p + 1;
9541             }
9542         }
9543         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9544         return;
9545     } else if (strncmp(message, "0-1", 3) == 0) {
9546         char *p, *q, *r = "";
9547         p = strchr(message, '{');
9548         if (p) {
9549             q = strchr(p, '}');
9550             if (q) {
9551                 *q = NULLCHAR;
9552                 r = p + 1;
9553             }
9554         }
9555         /* Kludge for Arasan 4.1 bug */
9556         if (strcmp(r, "Black resigns") == 0) {
9557             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9558             return;
9559         }
9560         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9561         return;
9562     } else if (strncmp(message, "1/2", 3) == 0) {
9563         char *p, *q, *r = "";
9564         p = strchr(message, '{');
9565         if (p) {
9566             q = strchr(p, '}');
9567             if (q) {
9568                 *q = NULLCHAR;
9569                 r = p + 1;
9570             }
9571         }
9572
9573         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9574         return;
9575
9576     } else if (strncmp(message, "White resign", 12) == 0) {
9577         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9578         return;
9579     } else if (strncmp(message, "Black resign", 12) == 0) {
9580         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9581         return;
9582     } else if (strncmp(message, "White matches", 13) == 0 ||
9583                strncmp(message, "Black matches", 13) == 0   ) {
9584         /* [HGM] ignore GNUShogi noises */
9585         return;
9586     } else if (strncmp(message, "White", 5) == 0 &&
9587                message[5] != '(' &&
9588                StrStr(message, "Black") == NULL) {
9589         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9590         return;
9591     } else if (strncmp(message, "Black", 5) == 0 &&
9592                message[5] != '(') {
9593         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9594         return;
9595     } else if (strcmp(message, "resign") == 0 ||
9596                strcmp(message, "computer resigns") == 0) {
9597         switch (gameMode) {
9598           case MachinePlaysBlack:
9599           case IcsPlayingBlack:
9600             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9601             break;
9602           case MachinePlaysWhite:
9603           case IcsPlayingWhite:
9604             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9605             break;
9606           case TwoMachinesPlay:
9607             if (cps->twoMachinesColor[0] == 'w')
9608               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9609             else
9610               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9611             break;
9612           default:
9613             /* can't happen */
9614             break;
9615         }
9616         return;
9617     } else if (strncmp(message, "opponent mates", 14) == 0) {
9618         switch (gameMode) {
9619           case MachinePlaysBlack:
9620           case IcsPlayingBlack:
9621             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9622             break;
9623           case MachinePlaysWhite:
9624           case IcsPlayingWhite:
9625             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9626             break;
9627           case TwoMachinesPlay:
9628             if (cps->twoMachinesColor[0] == 'w')
9629               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9630             else
9631               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9632             break;
9633           default:
9634             /* can't happen */
9635             break;
9636         }
9637         return;
9638     } else if (strncmp(message, "computer mates", 14) == 0) {
9639         switch (gameMode) {
9640           case MachinePlaysBlack:
9641           case IcsPlayingBlack:
9642             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9643             break;
9644           case MachinePlaysWhite:
9645           case IcsPlayingWhite:
9646             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9647             break;
9648           case TwoMachinesPlay:
9649             if (cps->twoMachinesColor[0] == 'w')
9650               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9651             else
9652               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9653             break;
9654           default:
9655             /* can't happen */
9656             break;
9657         }
9658         return;
9659     } else if (strncmp(message, "checkmate", 9) == 0) {
9660         if (WhiteOnMove(forwardMostMove)) {
9661             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9662         } else {
9663             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9664         }
9665         return;
9666     } else if (strstr(message, "Draw") != NULL ||
9667                strstr(message, "game is a draw") != NULL) {
9668         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9669         return;
9670     } else if (strstr(message, "offer") != NULL &&
9671                strstr(message, "draw") != NULL) {
9672 #if ZIPPY
9673         if (appData.zippyPlay && first.initDone) {
9674             /* Relay offer to ICS */
9675             SendToICS(ics_prefix);
9676             SendToICS("draw\n");
9677         }
9678 #endif
9679         cps->offeredDraw = 2; /* valid until this engine moves twice */
9680         if (gameMode == TwoMachinesPlay) {
9681             if (cps->other->offeredDraw) {
9682                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9683             /* [HGM] in two-machine mode we delay relaying draw offer      */
9684             /* until after we also have move, to see if it is really claim */
9685             }
9686         } else if (gameMode == MachinePlaysWhite ||
9687                    gameMode == MachinePlaysBlack) {
9688           if (userOfferedDraw) {
9689             DisplayInformation(_("Machine accepts your draw offer"));
9690             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9691           } else {
9692             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9693           }
9694         }
9695     }
9696
9697
9698     /*
9699      * Look for thinking output
9700      */
9701     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9702           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9703                                 ) {
9704         int plylev, mvleft, mvtot, curscore, time;
9705         char mvname[MOVE_LEN];
9706         u64 nodes; // [DM]
9707         char plyext;
9708         int ignore = FALSE;
9709         int prefixHint = FALSE;
9710         mvname[0] = NULLCHAR;
9711
9712         switch (gameMode) {
9713           case MachinePlaysBlack:
9714           case IcsPlayingBlack:
9715             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9716             break;
9717           case MachinePlaysWhite:
9718           case IcsPlayingWhite:
9719             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9720             break;
9721           case AnalyzeMode:
9722           case AnalyzeFile:
9723             break;
9724           case IcsObserving: /* [DM] icsEngineAnalyze */
9725             if (!appData.icsEngineAnalyze) ignore = TRUE;
9726             break;
9727           case TwoMachinesPlay:
9728             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9729                 ignore = TRUE;
9730             }
9731             break;
9732           default:
9733             ignore = TRUE;
9734             break;
9735         }
9736
9737         if (!ignore) {
9738             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9739             int solved = 0;
9740             buf1[0] = NULLCHAR;
9741             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9742                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9743                 char score_buf[MSG_SIZ];
9744
9745                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9746                     nodes += u64Const(0x100000000);
9747
9748                 if (plyext != ' ' && plyext != '\t') {
9749                     time *= 100;
9750                 }
9751
9752                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9753                 if( cps->scoreIsAbsolute &&
9754                     ( gameMode == MachinePlaysBlack ||
9755                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9756                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9757                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9758                      !WhiteOnMove(currentMove)
9759                     ) )
9760                 {
9761                     curscore = -curscore;
9762                 }
9763
9764                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9765
9766                 if(*bestMove) { // rememer time best EPD move was first found
9767                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9768                     ChessMove mt; char *p = bestMove;
9769                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9770                     solved = 0;
9771                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9772                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9773                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9774                             solved = 1;
9775                             break;
9776                         }
9777                         while(*p && *p != ' ') p++;
9778                         while(*p == ' ') p++;
9779                     }
9780                     if(!solved) solvingTime = -1;
9781                 }
9782                 if(*avoidMove && !solved) {
9783                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9784                     ChessMove mt; char *p = avoidMove, solved = 1;
9785                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9786                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9787                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9788                             solved = 0; solvingTime = -2;
9789                             break;
9790                         }
9791                         while(*p && *p != ' ') p++;
9792                         while(*p == ' ') p++;
9793                     }
9794                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9795                 }
9796
9797                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9798                         char buf[MSG_SIZ];
9799                         FILE *f;
9800                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9801                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9802                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9803                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9804                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9805                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9806                                 fclose(f);
9807                         }
9808                         else
9809                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9810                           DisplayError(_("failed writing PV"), 0);
9811                 }
9812
9813                 tempStats.depth = plylev;
9814                 tempStats.nodes = nodes;
9815                 tempStats.time = time;
9816                 tempStats.score = curscore;
9817                 tempStats.got_only_move = 0;
9818
9819                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9820                         int ticklen;
9821
9822                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9823                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9824                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9825                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9826                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9827                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9828                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9829                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9830                 }
9831
9832                 /* Buffer overflow protection */
9833                 if (pv[0] != NULLCHAR) {
9834                     if (strlen(pv) >= sizeof(tempStats.movelist)
9835                         && appData.debugMode) {
9836                         fprintf(debugFP,
9837                                 "PV is too long; using the first %u bytes.\n",
9838                                 (unsigned) sizeof(tempStats.movelist) - 1);
9839                     }
9840
9841                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9842                 } else {
9843                     sprintf(tempStats.movelist, " no PV\n");
9844                 }
9845
9846                 if (tempStats.seen_stat) {
9847                     tempStats.ok_to_send = 1;
9848                 }
9849
9850                 if (strchr(tempStats.movelist, '(') != NULL) {
9851                     tempStats.line_is_book = 1;
9852                     tempStats.nr_moves = 0;
9853                     tempStats.moves_left = 0;
9854                 } else {
9855                     tempStats.line_is_book = 0;
9856                 }
9857
9858                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9859                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9860
9861                 SendProgramStatsToFrontend( cps, &tempStats );
9862
9863                 /*
9864                     [AS] Protect the thinkOutput buffer from overflow... this
9865                     is only useful if buf1 hasn't overflowed first!
9866                 */
9867                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9868                 if(curscore >= MATE_SCORE) 
9869                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9870                 else if(curscore <= -MATE_SCORE) 
9871                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9872                 else
9873                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9874                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9875                          plylev,
9876                          (gameMode == TwoMachinesPlay ?
9877                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9878                          score_buf,
9879                          prefixHint ? lastHint : "",
9880                          prefixHint ? " " : "" );
9881
9882                 if( buf1[0] != NULLCHAR ) {
9883                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9884
9885                     if( strlen(pv) > max_len ) {
9886                         if( appData.debugMode) {
9887                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9888                         }
9889                         pv[max_len+1] = '\0';
9890                     }
9891
9892                     strcat( thinkOutput, pv);
9893                 }
9894
9895                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9896                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9897                     DisplayMove(currentMove - 1);
9898                 }
9899                 return;
9900
9901             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9902                 /* crafty (9.25+) says "(only move) <move>"
9903                  * if there is only 1 legal move
9904                  */
9905                 sscanf(p, "(only move) %s", buf1);
9906                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9907                 sprintf(programStats.movelist, "%s (only move)", buf1);
9908                 programStats.depth = 1;
9909                 programStats.nr_moves = 1;
9910                 programStats.moves_left = 1;
9911                 programStats.nodes = 1;
9912                 programStats.time = 1;
9913                 programStats.got_only_move = 1;
9914
9915                 /* Not really, but we also use this member to
9916                    mean "line isn't going to change" (Crafty
9917                    isn't searching, so stats won't change) */
9918                 programStats.line_is_book = 1;
9919
9920                 SendProgramStatsToFrontend( cps, &programStats );
9921
9922                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9923                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9924                     DisplayMove(currentMove - 1);
9925                 }
9926                 return;
9927             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9928                               &time, &nodes, &plylev, &mvleft,
9929                               &mvtot, mvname) >= 5) {
9930                 /* The stat01: line is from Crafty (9.29+) in response
9931                    to the "." command */
9932                 programStats.seen_stat = 1;
9933                 cps->maybeThinking = TRUE;
9934
9935                 if (programStats.got_only_move || !appData.periodicUpdates)
9936                   return;
9937
9938                 programStats.depth = plylev;
9939                 programStats.time = time;
9940                 programStats.nodes = nodes;
9941                 programStats.moves_left = mvleft;
9942                 programStats.nr_moves = mvtot;
9943                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9944                 programStats.ok_to_send = 1;
9945                 programStats.movelist[0] = '\0';
9946
9947                 SendProgramStatsToFrontend( cps, &programStats );
9948
9949                 return;
9950
9951             } else if (strncmp(message,"++",2) == 0) {
9952                 /* Crafty 9.29+ outputs this */
9953                 programStats.got_fail = 2;
9954                 return;
9955
9956             } else if (strncmp(message,"--",2) == 0) {
9957                 /* Crafty 9.29+ outputs this */
9958                 programStats.got_fail = 1;
9959                 return;
9960
9961             } else if (thinkOutput[0] != NULLCHAR &&
9962                        strncmp(message, "    ", 4) == 0) {
9963                 unsigned message_len;
9964
9965                 p = message;
9966                 while (*p && *p == ' ') p++;
9967
9968                 message_len = strlen( p );
9969
9970                 /* [AS] Avoid buffer overflow */
9971                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9972                     strcat(thinkOutput, " ");
9973                     strcat(thinkOutput, p);
9974                 }
9975
9976                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9977                     strcat(programStats.movelist, " ");
9978                     strcat(programStats.movelist, p);
9979                 }
9980
9981                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9982                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9983                     DisplayMove(currentMove - 1);
9984                 }
9985                 return;
9986             }
9987         }
9988         else {
9989             buf1[0] = NULLCHAR;
9990
9991             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9992                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9993             {
9994                 ChessProgramStats cpstats;
9995
9996                 if (plyext != ' ' && plyext != '\t') {
9997                     time *= 100;
9998                 }
9999
10000                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10001                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10002                     curscore = -curscore;
10003                 }
10004
10005                 cpstats.depth = plylev;
10006                 cpstats.nodes = nodes;
10007                 cpstats.time = time;
10008                 cpstats.score = curscore;
10009                 cpstats.got_only_move = 0;
10010                 cpstats.movelist[0] = '\0';
10011
10012                 if (buf1[0] != NULLCHAR) {
10013                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10014                 }
10015
10016                 cpstats.ok_to_send = 0;
10017                 cpstats.line_is_book = 0;
10018                 cpstats.nr_moves = 0;
10019                 cpstats.moves_left = 0;
10020
10021                 SendProgramStatsToFrontend( cps, &cpstats );
10022             }
10023         }
10024     }
10025 }
10026
10027
10028 /* Parse a game score from the character string "game", and
10029    record it as the history of the current game.  The game
10030    score is NOT assumed to start from the standard position.
10031    The display is not updated in any way.
10032    */
10033 void
10034 ParseGameHistory (char *game)
10035 {
10036     ChessMove moveType;
10037     int fromX, fromY, toX, toY, boardIndex, mask;
10038     char promoChar;
10039     char *p, *q;
10040     char buf[MSG_SIZ];
10041
10042     if (appData.debugMode)
10043       fprintf(debugFP, "Parsing game history: %s\n", game);
10044
10045     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10046     gameInfo.site = StrSave(appData.icsHost);
10047     gameInfo.date = PGNDate();
10048     gameInfo.round = StrSave("-");
10049
10050     /* Parse out names of players */
10051     while (*game == ' ') game++;
10052     p = buf;
10053     while (*game != ' ') *p++ = *game++;
10054     *p = NULLCHAR;
10055     gameInfo.white = StrSave(buf);
10056     while (*game == ' ') game++;
10057     p = buf;
10058     while (*game != ' ' && *game != '\n') *p++ = *game++;
10059     *p = NULLCHAR;
10060     gameInfo.black = StrSave(buf);
10061
10062     /* Parse moves */
10063     boardIndex = blackPlaysFirst ? 1 : 0;
10064     yynewstr(game);
10065     for (;;) {
10066         yyboardindex = boardIndex;
10067         moveType = (ChessMove) Myylex();
10068         switch (moveType) {
10069           case IllegalMove:             /* maybe suicide chess, etc. */
10070   if (appData.debugMode) {
10071     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10072     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10073     setbuf(debugFP, NULL);
10074   }
10075           case WhitePromotion:
10076           case BlackPromotion:
10077           case WhiteNonPromotion:
10078           case BlackNonPromotion:
10079           case NormalMove:
10080           case FirstLeg:
10081           case WhiteCapturesEnPassant:
10082           case BlackCapturesEnPassant:
10083           case WhiteKingSideCastle:
10084           case WhiteQueenSideCastle:
10085           case BlackKingSideCastle:
10086           case BlackQueenSideCastle:
10087           case WhiteKingSideCastleWild:
10088           case WhiteQueenSideCastleWild:
10089           case BlackKingSideCastleWild:
10090           case BlackQueenSideCastleWild:
10091           /* PUSH Fabien */
10092           case WhiteHSideCastleFR:
10093           case WhiteASideCastleFR:
10094           case BlackHSideCastleFR:
10095           case BlackASideCastleFR:
10096           /* POP Fabien */
10097             fromX = currentMoveString[0] - AAA;
10098             fromY = currentMoveString[1] - ONE;
10099             toX = currentMoveString[2] - AAA;
10100             toY = currentMoveString[3] - ONE;
10101             promoChar = currentMoveString[4];
10102             break;
10103           case WhiteDrop:
10104           case BlackDrop:
10105             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10106             fromX = moveType == WhiteDrop ?
10107               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10108             (int) CharToPiece(ToLower(currentMoveString[0]));
10109             fromY = DROP_RANK;
10110             toX = currentMoveString[2] - AAA;
10111             toY = currentMoveString[3] - ONE;
10112             promoChar = NULLCHAR;
10113             break;
10114           case AmbiguousMove:
10115             /* bug? */
10116             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10117   if (appData.debugMode) {
10118     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10119     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10120     setbuf(debugFP, NULL);
10121   }
10122             DisplayError(buf, 0);
10123             return;
10124           case ImpossibleMove:
10125             /* bug? */
10126             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10127   if (appData.debugMode) {
10128     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10129     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10130     setbuf(debugFP, NULL);
10131   }
10132             DisplayError(buf, 0);
10133             return;
10134           case EndOfFile:
10135             if (boardIndex < backwardMostMove) {
10136                 /* Oops, gap.  How did that happen? */
10137                 DisplayError(_("Gap in move list"), 0);
10138                 return;
10139             }
10140             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10141             if (boardIndex > forwardMostMove) {
10142                 forwardMostMove = boardIndex;
10143             }
10144             return;
10145           case ElapsedTime:
10146             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10147                 strcat(parseList[boardIndex-1], " ");
10148                 strcat(parseList[boardIndex-1], yy_text);
10149             }
10150             continue;
10151           case Comment:
10152           case PGNTag:
10153           case NAG:
10154           default:
10155             /* ignore */
10156             continue;
10157           case WhiteWins:
10158           case BlackWins:
10159           case GameIsDrawn:
10160           case GameUnfinished:
10161             if (gameMode == IcsExamining) {
10162                 if (boardIndex < backwardMostMove) {
10163                     /* Oops, gap.  How did that happen? */
10164                     return;
10165                 }
10166                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10167                 return;
10168             }
10169             gameInfo.result = moveType;
10170             p = strchr(yy_text, '{');
10171             if (p == NULL) p = strchr(yy_text, '(');
10172             if (p == NULL) {
10173                 p = yy_text;
10174                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10175             } else {
10176                 q = strchr(p, *p == '{' ? '}' : ')');
10177                 if (q != NULL) *q = NULLCHAR;
10178                 p++;
10179             }
10180             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10181             gameInfo.resultDetails = StrSave(p);
10182             continue;
10183         }
10184         if (boardIndex >= forwardMostMove &&
10185             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10186             backwardMostMove = blackPlaysFirst ? 1 : 0;
10187             return;
10188         }
10189         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10190                                  fromY, fromX, toY, toX, promoChar,
10191                                  parseList[boardIndex]);
10192         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10193         /* currentMoveString is set as a side-effect of yylex */
10194         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10195         strcat(moveList[boardIndex], "\n");
10196         boardIndex++;
10197         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10198         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10199         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10200           case MT_NONE:
10201           case MT_STALEMATE:
10202           default:
10203             break;
10204           case MT_CHECK:
10205             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10206             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10207                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10208                 break;
10209             }
10210           case MT_CHECKMATE:
10211           case MT_STAINMATE:
10212             strcat(parseList[boardIndex - 1], "#");
10213             break;
10214         }
10215     }
10216 }
10217
10218
10219 /* Apply a move to the given board  */
10220 void
10221 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10222 {
10223   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10224   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10225
10226     /* [HGM] compute & store e.p. status and castling rights for new position */
10227     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10228
10229       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10230       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10231       board[EP_STATUS] = EP_NONE;
10232       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10233
10234   if (fromY == DROP_RANK) {
10235         /* must be first */
10236         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10237             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10238             return;
10239         }
10240         piece = board[toY][toX] = (ChessSquare) fromX;
10241   } else {
10242 //      ChessSquare victim;
10243       int i;
10244
10245       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10246 //           victim = board[killY][killX],
10247            killed = board[killY][killX],
10248            board[killY][killX] = EmptySquare,
10249            board[EP_STATUS] = EP_CAPTURE;
10250            if( kill2X >= 0 && kill2Y >= 0)
10251              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10252       }
10253
10254       if( board[toY][toX] != EmptySquare ) {
10255            board[EP_STATUS] = EP_CAPTURE;
10256            if( (fromX != toX || fromY != toY) && // not igui!
10257                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10258                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10259                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10260            }
10261       }
10262
10263       pawn = board[fromY][fromX];
10264       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10265         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10266             captured = board[lastRank][lastFile]; // remove victim
10267             board[lastRank][lastFile] = EmptySquare;
10268             pawn = EmptySquare; // kludge to suppress old e.p. code
10269         }
10270       }
10271       if( pawn == WhiteLance || pawn == BlackLance ) {
10272            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10273                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10274                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10275            }
10276       }
10277       if( pawn == WhitePawn ) {
10278            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10279                board[EP_STATUS] = EP_PAWN_MOVE;
10280            if( toY-fromY>=2) {
10281                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10282                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10283                         gameInfo.variant != VariantBerolina || toX < fromX)
10284                       board[EP_STATUS] = toX | berolina;
10285                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10286                         gameInfo.variant != VariantBerolina || toX > fromX)
10287                       board[EP_STATUS] = toX;
10288                board[LAST_TO] = toX + 256*toY;
10289            }
10290       } else
10291       if( pawn == BlackPawn ) {
10292            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10293                board[EP_STATUS] = EP_PAWN_MOVE;
10294            if( toY-fromY<= -2) {
10295                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10296                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10297                         gameInfo.variant != VariantBerolina || toX < fromX)
10298                       board[EP_STATUS] = toX | berolina;
10299                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10300                         gameInfo.variant != VariantBerolina || toX > fromX)
10301                       board[EP_STATUS] = toX;
10302                board[LAST_TO] = toX + 256*toY;
10303            }
10304        }
10305
10306        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10307        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10308        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10309        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10310
10311        for(i=0; i<nrCastlingRights; i++) {
10312            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10313               board[CASTLING][i] == toX   && castlingRank[i] == toY
10314              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10315        }
10316
10317        if(gameInfo.variant == VariantSChess) { // update virginity
10318            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10319            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10320            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10321            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10322        }
10323
10324      if (fromX == toX && fromY == toY && killX < 0) return;
10325
10326      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10327      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10328      if(gameInfo.variant == VariantKnightmate)
10329          king += (int) WhiteUnicorn - (int) WhiteKing;
10330
10331     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10332        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10333         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10334         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10335         board[EP_STATUS] = EP_NONE; // capture was fake!
10336     } else
10337     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10338         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10339         board[toY][toX] = piece;
10340         board[EP_STATUS] = EP_NONE; // capture was fake!
10341     } else
10342     /* Code added by Tord: */
10343     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10344     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10345         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10346       board[EP_STATUS] = EP_NONE; // capture was fake!
10347       board[fromY][fromX] = EmptySquare;
10348       board[toY][toX] = EmptySquare;
10349       if((toX > fromX) != (piece == WhiteRook)) {
10350         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10351       } else {
10352         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10353       }
10354     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10355                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10356       board[EP_STATUS] = EP_NONE;
10357       board[fromY][fromX] = EmptySquare;
10358       board[toY][toX] = EmptySquare;
10359       if((toX > fromX) != (piece == BlackRook)) {
10360         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10361       } else {
10362         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10363       }
10364     /* End of code added by Tord */
10365
10366     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10367         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10368         board[toY][toX] = piece;
10369     } else if (board[fromY][fromX] == king
10370         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10371         && toY == fromY && toX > fromX+1) {
10372         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10373                                                                                              ; // castle with nearest piece
10374         board[fromY][toX-1] = board[fromY][rookX];
10375         board[fromY][rookX] = EmptySquare;
10376         board[fromY][fromX] = EmptySquare;
10377         board[toY][toX] = king;
10378     } else if (board[fromY][fromX] == king
10379         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10380                && toY == fromY && toX < fromX-1) {
10381         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10382                                                                                   ; // castle with nearest piece
10383         board[fromY][toX+1] = board[fromY][rookX];
10384         board[fromY][rookX] = EmptySquare;
10385         board[fromY][fromX] = EmptySquare;
10386         board[toY][toX] = king;
10387     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10388                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10389                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10390                ) {
10391         /* white pawn promotion */
10392         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10393         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10394             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10395         board[fromY][fromX] = EmptySquare;
10396     } else if ((fromY >= BOARD_HEIGHT>>1)
10397                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10398                && (toX != fromX)
10399                && gameInfo.variant != VariantXiangqi
10400                && gameInfo.variant != VariantBerolina
10401                && (pawn == WhitePawn)
10402                && (board[toY][toX] == EmptySquare)) {
10403         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10404         board[fromY][fromX] = EmptySquare;
10405         board[toY][toX] = piece;
10406         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10407     } else if ((fromY == BOARD_HEIGHT-4)
10408                && (toX == fromX)
10409                && gameInfo.variant == VariantBerolina
10410                && (board[fromY][fromX] == WhitePawn)
10411                && (board[toY][toX] == EmptySquare)) {
10412         board[fromY][fromX] = EmptySquare;
10413         board[toY][toX] = WhitePawn;
10414         if(oldEP & EP_BEROLIN_A) {
10415                 captured = board[fromY][fromX-1];
10416                 board[fromY][fromX-1] = EmptySquare;
10417         }else{  captured = board[fromY][fromX+1];
10418                 board[fromY][fromX+1] = EmptySquare;
10419         }
10420     } else if (board[fromY][fromX] == king
10421         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10422                && toY == fromY && toX > fromX+1) {
10423         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10424                                                                                              ;
10425         board[fromY][toX-1] = board[fromY][rookX];
10426         board[fromY][rookX] = EmptySquare;
10427         board[fromY][fromX] = EmptySquare;
10428         board[toY][toX] = king;
10429     } else if (board[fromY][fromX] == king
10430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10431                && toY == fromY && toX < fromX-1) {
10432         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10433                                                                                 ;
10434         board[fromY][toX+1] = board[fromY][rookX];
10435         board[fromY][rookX] = EmptySquare;
10436         board[fromY][fromX] = EmptySquare;
10437         board[toY][toX] = king;
10438     } else if (fromY == 7 && fromX == 3
10439                && board[fromY][fromX] == BlackKing
10440                && toY == 7 && toX == 5) {
10441         board[fromY][fromX] = EmptySquare;
10442         board[toY][toX] = BlackKing;
10443         board[fromY][7] = EmptySquare;
10444         board[toY][4] = BlackRook;
10445     } else if (fromY == 7 && fromX == 3
10446                && board[fromY][fromX] == BlackKing
10447                && toY == 7 && toX == 1) {
10448         board[fromY][fromX] = EmptySquare;
10449         board[toY][toX] = BlackKing;
10450         board[fromY][0] = EmptySquare;
10451         board[toY][2] = BlackRook;
10452     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10453                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10454                && toY < promoRank && promoChar
10455                ) {
10456         /* black pawn promotion */
10457         board[toY][toX] = CharToPiece(ToLower(promoChar));
10458         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10459             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10460         board[fromY][fromX] = EmptySquare;
10461     } else if ((fromY < BOARD_HEIGHT>>1)
10462                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10463                && (toX != fromX)
10464                && gameInfo.variant != VariantXiangqi
10465                && gameInfo.variant != VariantBerolina
10466                && (pawn == BlackPawn)
10467                && (board[toY][toX] == EmptySquare)) {
10468         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10469         board[fromY][fromX] = EmptySquare;
10470         board[toY][toX] = piece;
10471         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10472     } else if ((fromY == 3)
10473                && (toX == fromX)
10474                && gameInfo.variant == VariantBerolina
10475                && (board[fromY][fromX] == BlackPawn)
10476                && (board[toY][toX] == EmptySquare)) {
10477         board[fromY][fromX] = EmptySquare;
10478         board[toY][toX] = BlackPawn;
10479         if(oldEP & EP_BEROLIN_A) {
10480                 captured = board[fromY][fromX-1];
10481                 board[fromY][fromX-1] = EmptySquare;
10482         }else{  captured = board[fromY][fromX+1];
10483                 board[fromY][fromX+1] = EmptySquare;
10484         }
10485     } else {
10486         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10487         board[fromY][fromX] = EmptySquare;
10488         board[toY][toX] = piece;
10489     }
10490   }
10491
10492     if (gameInfo.holdingsWidth != 0) {
10493
10494       /* !!A lot more code needs to be written to support holdings  */
10495       /* [HGM] OK, so I have written it. Holdings are stored in the */
10496       /* penultimate board files, so they are automaticlly stored   */
10497       /* in the game history.                                       */
10498       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10499                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10500         /* Delete from holdings, by decreasing count */
10501         /* and erasing image if necessary            */
10502         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10503         if(p < (int) BlackPawn) { /* white drop */
10504              p -= (int)WhitePawn;
10505                  p = PieceToNumber((ChessSquare)p);
10506              if(p >= gameInfo.holdingsSize) p = 0;
10507              if(--board[p][BOARD_WIDTH-2] <= 0)
10508                   board[p][BOARD_WIDTH-1] = EmptySquare;
10509              if((int)board[p][BOARD_WIDTH-2] < 0)
10510                         board[p][BOARD_WIDTH-2] = 0;
10511         } else {                  /* black drop */
10512              p -= (int)BlackPawn;
10513                  p = PieceToNumber((ChessSquare)p);
10514              if(p >= gameInfo.holdingsSize) p = 0;
10515              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10516                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10517              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10518                         board[BOARD_HEIGHT-1-p][1] = 0;
10519         }
10520       }
10521       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10522           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10523         /* [HGM] holdings: Add to holdings, if holdings exist */
10524         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10525                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10526                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10527         }
10528         p = (int) captured;
10529         if (p >= (int) BlackPawn) {
10530           p -= (int)BlackPawn;
10531           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10532                   /* Restore shogi-promoted piece to its original  first */
10533                   captured = (ChessSquare) (DEMOTED(captured));
10534                   p = DEMOTED(p);
10535           }
10536           p = PieceToNumber((ChessSquare)p);
10537           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10538           board[p][BOARD_WIDTH-2]++;
10539           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10540         } else {
10541           p -= (int)WhitePawn;
10542           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10543                   captured = (ChessSquare) (DEMOTED(captured));
10544                   p = DEMOTED(p);
10545           }
10546           p = PieceToNumber((ChessSquare)p);
10547           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10548           board[BOARD_HEIGHT-1-p][1]++;
10549           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10550         }
10551       }
10552     } else if (gameInfo.variant == VariantAtomic) {
10553       if (captured != EmptySquare) {
10554         int y, x;
10555         for (y = toY-1; y <= toY+1; y++) {
10556           for (x = toX-1; x <= toX+1; x++) {
10557             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10558                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10559               board[y][x] = EmptySquare;
10560             }
10561           }
10562         }
10563         board[toY][toX] = EmptySquare;
10564       }
10565     }
10566
10567     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10568         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10569     } else
10570     if(promoChar == '+') {
10571         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10572         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10573         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10574           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10575     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10576         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10577         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10578            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10579         board[toY][toX] = newPiece;
10580     }
10581     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10582                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10583         // [HGM] superchess: take promotion piece out of holdings
10584         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10585         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10586             if(!--board[k][BOARD_WIDTH-2])
10587                 board[k][BOARD_WIDTH-1] = EmptySquare;
10588         } else {
10589             if(!--board[BOARD_HEIGHT-1-k][1])
10590                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10591         }
10592     }
10593 }
10594
10595 /* Updates forwardMostMove */
10596 void
10597 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10598 {
10599     int x = toX, y = toY, mask;
10600     char *s = parseList[forwardMostMove];
10601     ChessSquare p = boards[forwardMostMove][toY][toX];
10602 //    forwardMostMove++; // [HGM] bare: moved downstream
10603
10604     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10605     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10606     (void) CoordsToAlgebraic(boards[forwardMostMove],
10607                              PosFlags(forwardMostMove),
10608                              fromY, fromX, y, x, (killX < 0)*promoChar,
10609                              s);
10610     if(kill2X >= 0 && kill2Y >= 0)
10611         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10612     if(killX >= 0 && killY >= 0)
10613         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10614                                            toX + AAA, toY + ONE - '0', promoChar);
10615
10616     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10617         int timeLeft; static int lastLoadFlag=0; int king, piece;
10618         piece = boards[forwardMostMove][fromY][fromX];
10619         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10620         if(gameInfo.variant == VariantKnightmate)
10621             king += (int) WhiteUnicorn - (int) WhiteKing;
10622         if(forwardMostMove == 0) {
10623             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10624                 fprintf(serverMoves, "%s;", UserName());
10625             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10626                 fprintf(serverMoves, "%s;", second.tidy);
10627             fprintf(serverMoves, "%s;", first.tidy);
10628             if(gameMode == MachinePlaysWhite)
10629                 fprintf(serverMoves, "%s;", UserName());
10630             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10631                 fprintf(serverMoves, "%s;", second.tidy);
10632         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10633         lastLoadFlag = loadFlag;
10634         // print base move
10635         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10636         // print castling suffix
10637         if( toY == fromY && piece == king ) {
10638             if(toX-fromX > 1)
10639                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10640             if(fromX-toX >1)
10641                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10642         }
10643         // e.p. suffix
10644         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10645              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10646              boards[forwardMostMove][toY][toX] == EmptySquare
10647              && fromX != toX && fromY != toY)
10648                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10649         // promotion suffix
10650         if(promoChar != NULLCHAR) {
10651             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10652                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10653                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10654             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10655         }
10656         if(!loadFlag) {
10657                 char buf[MOVE_LEN*2], *p; int len;
10658             fprintf(serverMoves, "/%d/%d",
10659                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10660             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10661             else                      timeLeft = blackTimeRemaining/1000;
10662             fprintf(serverMoves, "/%d", timeLeft);
10663                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10664                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10665                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10666                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10667             fprintf(serverMoves, "/%s", buf);
10668         }
10669         fflush(serverMoves);
10670     }
10671
10672     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10673         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10674       return;
10675     }
10676     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10677     if (commentList[forwardMostMove+1] != NULL) {
10678         free(commentList[forwardMostMove+1]);
10679         commentList[forwardMostMove+1] = NULL;
10680     }
10681     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10682     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10683     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10684     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10685     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10686     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10687     adjustedClock = FALSE;
10688     gameInfo.result = GameUnfinished;
10689     if (gameInfo.resultDetails != NULL) {
10690         free(gameInfo.resultDetails);
10691         gameInfo.resultDetails = NULL;
10692     }
10693     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10694                               moveList[forwardMostMove - 1]);
10695     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10696     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10697       case MT_NONE:
10698       case MT_STALEMATE:
10699       default:
10700         break;
10701       case MT_CHECK:
10702         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10703         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10704             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10705             break;
10706         }
10707       case MT_CHECKMATE:
10708       case MT_STAINMATE:
10709         strcat(parseList[forwardMostMove - 1], "#");
10710         break;
10711     }
10712 }
10713
10714 /* Updates currentMove if not pausing */
10715 void
10716 ShowMove (int fromX, int fromY, int toX, int toY)
10717 {
10718     int instant = (gameMode == PlayFromGameFile) ?
10719         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10720     if(appData.noGUI) return;
10721     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10722         if (!instant) {
10723             if (forwardMostMove == currentMove + 1) {
10724                 AnimateMove(boards[forwardMostMove - 1],
10725                             fromX, fromY, toX, toY);
10726             }
10727         }
10728         currentMove = forwardMostMove;
10729     }
10730
10731     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10732
10733     if (instant) return;
10734
10735     DisplayMove(currentMove - 1);
10736     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10737             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10738                 SetHighlights(fromX, fromY, toX, toY);
10739             }
10740     }
10741     DrawPosition(FALSE, boards[currentMove]);
10742     DisplayBothClocks();
10743     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10744 }
10745
10746 void
10747 SendEgtPath (ChessProgramState *cps)
10748 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10749         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10750
10751         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10752
10753         while(*p) {
10754             char c, *q = name+1, *r, *s;
10755
10756             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10757             while(*p && *p != ',') *q++ = *p++;
10758             *q++ = ':'; *q = 0;
10759             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10760                 strcmp(name, ",nalimov:") == 0 ) {
10761                 // take nalimov path from the menu-changeable option first, if it is defined
10762               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10763                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10764             } else
10765             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10766                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10767                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10768                 s = r = StrStr(s, ":") + 1; // beginning of path info
10769                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10770                 c = *r; *r = 0;             // temporarily null-terminate path info
10771                     *--q = 0;               // strip of trailig ':' from name
10772                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10773                 *r = c;
10774                 SendToProgram(buf,cps);     // send egtbpath command for this format
10775             }
10776             if(*p == ',') p++; // read away comma to position for next format name
10777         }
10778 }
10779
10780 static int
10781 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10782 {
10783       int width = 8, height = 8, holdings = 0;             // most common sizes
10784       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10785       // correct the deviations default for each variant
10786       if( v == VariantXiangqi ) width = 9,  height = 10;
10787       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10788       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10789       if( v == VariantCapablanca || v == VariantCapaRandom ||
10790           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10791                                 width = 10;
10792       if( v == VariantCourier ) width = 12;
10793       if( v == VariantSuper )                            holdings = 8;
10794       if( v == VariantGreat )   width = 10,              holdings = 8;
10795       if( v == VariantSChess )                           holdings = 7;
10796       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10797       if( v == VariantChuChess) width = 10, height = 10;
10798       if( v == VariantChu )     width = 12, height = 12;
10799       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10800              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10801              holdingsSize >= 0 && holdingsSize != holdings;
10802 }
10803
10804 char variantError[MSG_SIZ];
10805
10806 char *
10807 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10808 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10809       char *p, *variant = VariantName(v);
10810       static char b[MSG_SIZ];
10811       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10812            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10813                                                holdingsSize, variant); // cook up sized variant name
10814            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10815            if(StrStr(list, b) == NULL) {
10816                // specific sized variant not known, check if general sizing allowed
10817                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10818                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10819                             boardWidth, boardHeight, holdingsSize, engine);
10820                    return NULL;
10821                }
10822                /* [HGM] here we really should compare with the maximum supported board size */
10823            }
10824       } else snprintf(b, MSG_SIZ,"%s", variant);
10825       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10826       p = StrStr(list, b);
10827       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10828       if(p == NULL) {
10829           // occurs not at all in list, or only as sub-string
10830           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10831           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10832               int l = strlen(variantError);
10833               char *q;
10834               while(p != list && p[-1] != ',') p--;
10835               q = strchr(p, ',');
10836               if(q) *q = NULLCHAR;
10837               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10838               if(q) *q= ',';
10839           }
10840           return NULL;
10841       }
10842       return b;
10843 }
10844
10845 void
10846 InitChessProgram (ChessProgramState *cps, int setup)
10847 /* setup needed to setup FRC opening position */
10848 {
10849     char buf[MSG_SIZ], *b;
10850     if (appData.noChessProgram) return;
10851     hintRequested = FALSE;
10852     bookRequested = FALSE;
10853
10854     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10855     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10856     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10857     if(cps->memSize) { /* [HGM] memory */
10858       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10859         SendToProgram(buf, cps);
10860     }
10861     SendEgtPath(cps); /* [HGM] EGT */
10862     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10863       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10864         SendToProgram(buf, cps);
10865     }
10866
10867     setboardSpoiledMachineBlack = FALSE;
10868     SendToProgram(cps->initString, cps);
10869     if (gameInfo.variant != VariantNormal &&
10870         gameInfo.variant != VariantLoadable
10871         /* [HGM] also send variant if board size non-standard */
10872         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10873
10874       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10875                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10876
10877       if (b == NULL) {
10878         VariantClass v;
10879         char c, *q = cps->variants, *p = strchr(q, ',');
10880         if(p) *p = NULLCHAR;
10881         v = StringToVariant(q);
10882         DisplayError(variantError, 0);
10883         if(v != VariantUnknown && cps == &first) {
10884             int w, h, s;
10885             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10886                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10887             ASSIGN(appData.variant, q);
10888             Reset(TRUE, FALSE);
10889         }
10890         if(p) *p = ',';
10891         return;
10892       }
10893
10894       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10895       SendToProgram(buf, cps);
10896     }
10897     currentlyInitializedVariant = gameInfo.variant;
10898
10899     /* [HGM] send opening position in FRC to first engine */
10900     if(setup) {
10901           SendToProgram("force\n", cps);
10902           SendBoard(cps, 0);
10903           /* engine is now in force mode! Set flag to wake it up after first move. */
10904           setboardSpoiledMachineBlack = 1;
10905     }
10906
10907     if (cps->sendICS) {
10908       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10909       SendToProgram(buf, cps);
10910     }
10911     cps->maybeThinking = FALSE;
10912     cps->offeredDraw = 0;
10913     if (!appData.icsActive) {
10914         SendTimeControl(cps, movesPerSession, timeControl,
10915                         timeIncrement, appData.searchDepth,
10916                         searchTime);
10917     }
10918     if (appData.showThinking
10919         // [HGM] thinking: four options require thinking output to be sent
10920         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10921                                 ) {
10922         SendToProgram("post\n", cps);
10923     }
10924     SendToProgram("hard\n", cps);
10925     if (!appData.ponderNextMove) {
10926         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10927            it without being sure what state we are in first.  "hard"
10928            is not a toggle, so that one is OK.
10929          */
10930         SendToProgram("easy\n", cps);
10931     }
10932     if (cps->usePing) {
10933       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10934       SendToProgram(buf, cps);
10935     }
10936     cps->initDone = TRUE;
10937     ClearEngineOutputPane(cps == &second);
10938 }
10939
10940
10941 char *
10942 ResendOptions (ChessProgramState *cps, int toEngine)
10943 { // send the stored value of the options
10944   int i;
10945   static char buf2[MSG_SIZ*10];
10946   char buf[MSG_SIZ], *p = buf2;
10947   Option *opt = cps->option;
10948   *p = NULLCHAR;
10949   for(i=0; i<cps->nrOptions; i++, opt++) {
10950       *buf = NULLCHAR;
10951       switch(opt->type) {
10952         case Spin:
10953         case Slider:
10954         case CheckBox:
10955             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10956             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10957           break;
10958         case ComboBox:
10959             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10960             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10961           break;
10962         default:
10963             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10964             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10965           break;
10966         case Button:
10967         case SaveButton:
10968           continue;
10969       }
10970       if(*buf) {
10971         if(toEngine) {
10972           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10973           SendToProgram(buf2, cps);
10974         } else {
10975           if(p != buf2) *p++ = ',';
10976           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10977           while(*p) p++;
10978         }
10979       }
10980   }
10981   return buf2;
10982 }
10983
10984 void
10985 StartChessProgram (ChessProgramState *cps)
10986 {
10987     char buf[MSG_SIZ];
10988     int err;
10989
10990     if (appData.noChessProgram) return;
10991     cps->initDone = FALSE;
10992
10993     if (strcmp(cps->host, "localhost") == 0) {
10994         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10995     } else if (*appData.remoteShell == NULLCHAR) {
10996         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10997     } else {
10998         if (*appData.remoteUser == NULLCHAR) {
10999           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11000                     cps->program);
11001         } else {
11002           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11003                     cps->host, appData.remoteUser, cps->program);
11004         }
11005         err = StartChildProcess(buf, "", &cps->pr);
11006     }
11007
11008     if (err != 0) {
11009       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11010         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11011         if(cps != &first) return;
11012         appData.noChessProgram = TRUE;
11013         ThawUI();
11014         SetNCPMode();
11015 //      DisplayFatalError(buf, err, 1);
11016 //      cps->pr = NoProc;
11017 //      cps->isr = NULL;
11018         return;
11019     }
11020
11021     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11022     if (cps->protocolVersion > 1) {
11023       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11024       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11025         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11026         cps->comboCnt = 0;  //                and values of combo boxes
11027       }
11028       SendToProgram(buf, cps);
11029       if(cps->reload) ResendOptions(cps, TRUE);
11030     } else {
11031       SendToProgram("xboard\n", cps);
11032     }
11033 }
11034
11035 void
11036 TwoMachinesEventIfReady P((void))
11037 {
11038   static int curMess = 0;
11039   if (first.lastPing != first.lastPong) {
11040     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11041     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11042     return;
11043   }
11044   if (second.lastPing != second.lastPong) {
11045     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11046     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11047     return;
11048   }
11049   DisplayMessage("", ""); curMess = 0;
11050   TwoMachinesEvent();
11051 }
11052
11053 char *
11054 MakeName (char *template)
11055 {
11056     time_t clock;
11057     struct tm *tm;
11058     static char buf[MSG_SIZ];
11059     char *p = buf;
11060     int i;
11061
11062     clock = time((time_t *)NULL);
11063     tm = localtime(&clock);
11064
11065     while(*p++ = *template++) if(p[-1] == '%') {
11066         switch(*template++) {
11067           case 0:   *p = 0; return buf;
11068           case 'Y': i = tm->tm_year+1900; break;
11069           case 'y': i = tm->tm_year-100; break;
11070           case 'M': i = tm->tm_mon+1; break;
11071           case 'd': i = tm->tm_mday; break;
11072           case 'h': i = tm->tm_hour; break;
11073           case 'm': i = tm->tm_min; break;
11074           case 's': i = tm->tm_sec; break;
11075           default:  i = 0;
11076         }
11077         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11078     }
11079     return buf;
11080 }
11081
11082 int
11083 CountPlayers (char *p)
11084 {
11085     int n = 0;
11086     while(p = strchr(p, '\n')) p++, n++; // count participants
11087     return n;
11088 }
11089
11090 FILE *
11091 WriteTourneyFile (char *results, FILE *f)
11092 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11093     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11094     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11095         // create a file with tournament description
11096         fprintf(f, "-participants {%s}\n", appData.participants);
11097         fprintf(f, "-seedBase %d\n", appData.seedBase);
11098         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11099         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11100         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11101         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11102         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11103         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11104         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11105         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11106         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11107         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11108         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11109         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11110         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11111         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11112         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11113         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11114         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11115         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11116         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11117         fprintf(f, "-smpCores %d\n", appData.smpCores);
11118         if(searchTime > 0)
11119                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11120         else {
11121                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11122                 fprintf(f, "-tc %s\n", appData.timeControl);
11123                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11124         }
11125         fprintf(f, "-results \"%s\"\n", results);
11126     }
11127     return f;
11128 }
11129
11130 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11131
11132 void
11133 Substitute (char *participants, int expunge)
11134 {
11135     int i, changed, changes=0, nPlayers=0;
11136     char *p, *q, *r, buf[MSG_SIZ];
11137     if(participants == NULL) return;
11138     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11139     r = p = participants; q = appData.participants;
11140     while(*p && *p == *q) {
11141         if(*p == '\n') r = p+1, nPlayers++;
11142         p++; q++;
11143     }
11144     if(*p) { // difference
11145         while(*p && *p++ != '\n')
11146                                  ;
11147         while(*q && *q++ != '\n')
11148                                  ;
11149       changed = nPlayers;
11150         changes = 1 + (strcmp(p, q) != 0);
11151     }
11152     if(changes == 1) { // a single engine mnemonic was changed
11153         q = r; while(*q) nPlayers += (*q++ == '\n');
11154         p = buf; while(*r && (*p = *r++) != '\n') p++;
11155         *p = NULLCHAR;
11156         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11157         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11158         if(mnemonic[i]) { // The substitute is valid
11159             FILE *f;
11160             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11161                 flock(fileno(f), LOCK_EX);
11162                 ParseArgsFromFile(f);
11163                 fseek(f, 0, SEEK_SET);
11164                 FREE(appData.participants); appData.participants = participants;
11165                 if(expunge) { // erase results of replaced engine
11166                     int len = strlen(appData.results), w, b, dummy;
11167                     for(i=0; i<len; i++) {
11168                         Pairing(i, nPlayers, &w, &b, &dummy);
11169                         if((w == changed || b == changed) && appData.results[i] == '*') {
11170                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11171                             fclose(f);
11172                             return;
11173                         }
11174                     }
11175                     for(i=0; i<len; i++) {
11176                         Pairing(i, nPlayers, &w, &b, &dummy);
11177                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11178                     }
11179                 }
11180                 WriteTourneyFile(appData.results, f);
11181                 fclose(f); // release lock
11182                 return;
11183             }
11184         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11185     }
11186     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11187     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11188     free(participants);
11189     return;
11190 }
11191
11192 int
11193 CheckPlayers (char *participants)
11194 {
11195         int i;
11196         char buf[MSG_SIZ], *p;
11197         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11198         while(p = strchr(participants, '\n')) {
11199             *p = NULLCHAR;
11200             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11201             if(!mnemonic[i]) {
11202                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11203                 *p = '\n';
11204                 DisplayError(buf, 0);
11205                 return 1;
11206             }
11207             *p = '\n';
11208             participants = p + 1;
11209         }
11210         return 0;
11211 }
11212
11213 int
11214 CreateTourney (char *name)
11215 {
11216         FILE *f;
11217         if(matchMode && strcmp(name, appData.tourneyFile)) {
11218              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11219         }
11220         if(name[0] == NULLCHAR) {
11221             if(appData.participants[0])
11222                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11223             return 0;
11224         }
11225         f = fopen(name, "r");
11226         if(f) { // file exists
11227             ASSIGN(appData.tourneyFile, name);
11228             ParseArgsFromFile(f); // parse it
11229         } else {
11230             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11231             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11232                 DisplayError(_("Not enough participants"), 0);
11233                 return 0;
11234             }
11235             if(CheckPlayers(appData.participants)) return 0;
11236             ASSIGN(appData.tourneyFile, name);
11237             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11238             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11239         }
11240         fclose(f);
11241         appData.noChessProgram = FALSE;
11242         appData.clockMode = TRUE;
11243         SetGNUMode();
11244         return 1;
11245 }
11246
11247 int
11248 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11249 {
11250     char buf[2*MSG_SIZ], *p, *q;
11251     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11252     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11253     skip = !all && group[0]; // if group requested, we start in skip mode
11254     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11255         p = names; q = buf; header = 0;
11256         while(*p && *p != '\n') *q++ = *p++;
11257         *q = 0;
11258         if(*p == '\n') p++;
11259         if(buf[0] == '#') {
11260             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11261             depth++; // we must be entering a new group
11262             if(all) continue; // suppress printing group headers when complete list requested
11263             header = 1;
11264             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11265         }
11266         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11267         if(engineList[i]) free(engineList[i]);
11268         engineList[i] = strdup(buf);
11269         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11270         if(engineMnemonic[i]) free(engineMnemonic[i]);
11271         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11272             strcat(buf, " (");
11273             sscanf(q + 8, "%s", buf + strlen(buf));
11274             strcat(buf, ")");
11275         }
11276         engineMnemonic[i] = strdup(buf);
11277         i++;
11278     }
11279     engineList[i] = engineMnemonic[i] = NULL;
11280     return i;
11281 }
11282
11283 void
11284 SaveEngineSettings (int n)
11285 {
11286     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11287     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11288     p = strstr(firstChessProgramNames, currentEngine[n]);
11289     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11290     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11291     len = strlen(currentEngine[n]);
11292     q = p + len; *p = 0; // cut list into head and tail piece
11293     s = strstr(currentEngine[n], "firstOptions");
11294     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11295         char *r = s + 14;
11296         while(*r && *r != s[13]) r++;
11297         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11298         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11299     } else if(*optionSettings) {
11300         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11301     }
11302     ASSIGN(currentEngine[n], buf); // updated engine line
11303     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11304     s = malloc(len);
11305     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11306     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11307 }
11308
11309 // following implemented as macro to avoid type limitations
11310 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11311
11312 void
11313 SwapEngines (int n)
11314 {   // swap settings for first engine and other engine (so far only some selected options)
11315     int h;
11316     char *p;
11317     if(n == 0) return;
11318     SWAP(directory, p)
11319     SWAP(chessProgram, p)
11320     SWAP(isUCI, h)
11321     SWAP(hasOwnBookUCI, h)
11322     SWAP(protocolVersion, h)
11323     SWAP(reuse, h)
11324     SWAP(scoreIsAbsolute, h)
11325     SWAP(timeOdds, h)
11326     SWAP(logo, p)
11327     SWAP(pgnName, p)
11328     SWAP(pvSAN, h)
11329     SWAP(engOptions, p)
11330     SWAP(engInitString, p)
11331     SWAP(computerString, p)
11332     SWAP(features, p)
11333     SWAP(fenOverride, p)
11334     SWAP(NPS, h)
11335     SWAP(accumulateTC, h)
11336     SWAP(drawDepth, h)
11337     SWAP(host, p)
11338     SWAP(pseudo, h)
11339 }
11340
11341 int
11342 GetEngineLine (char *s, int n)
11343 {
11344     int i;
11345     char buf[MSG_SIZ];
11346     extern char *icsNames;
11347     if(!s || !*s) return 0;
11348     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11349     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11350     if(!mnemonic[i]) return 0;
11351     if(n == 11) return 1; // just testing if there was a match
11352     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11353     if(n == 1) SwapEngines(n);
11354     ParseArgsFromString(buf);
11355     if(n == 1) SwapEngines(n);
11356     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11357     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11358         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11359         ParseArgsFromString(buf);
11360     }
11361     return 1;
11362 }
11363
11364 int
11365 SetPlayer (int player, char *p)
11366 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11367     int i;
11368     char buf[MSG_SIZ], *engineName;
11369     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11370     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11371     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11372     if(mnemonic[i]) {
11373         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11374         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11375         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11376         ParseArgsFromString(buf);
11377     } else { // no engine with this nickname is installed!
11378         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11379         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11380         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11381         ModeHighlight();
11382         DisplayError(buf, 0);
11383         return 0;
11384     }
11385     free(engineName);
11386     return i;
11387 }
11388
11389 char *recentEngines;
11390
11391 void
11392 RecentEngineEvent (int nr)
11393 {
11394     int n;
11395 //    SwapEngines(1); // bump first to second
11396 //    ReplaceEngine(&second, 1); // and load it there
11397     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11398     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11399     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11400         ReplaceEngine(&first, 0);
11401         FloatToFront(&appData.recentEngineList, command[n]);
11402     }
11403 }
11404
11405 int
11406 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11407 {   // determine players from game number
11408     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11409
11410     if(appData.tourneyType == 0) {
11411         roundsPerCycle = (nPlayers - 1) | 1;
11412         pairingsPerRound = nPlayers / 2;
11413     } else if(appData.tourneyType > 0) {
11414         roundsPerCycle = nPlayers - appData.tourneyType;
11415         pairingsPerRound = appData.tourneyType;
11416     }
11417     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11418     gamesPerCycle = gamesPerRound * roundsPerCycle;
11419     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11420     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11421     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11422     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11423     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11424     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11425
11426     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11427     if(appData.roundSync) *syncInterval = gamesPerRound;
11428
11429     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11430
11431     if(appData.tourneyType == 0) {
11432         if(curPairing == (nPlayers-1)/2 ) {
11433             *whitePlayer = curRound;
11434             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11435         } else {
11436             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11437             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11438             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11439             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11440         }
11441     } else if(appData.tourneyType > 1) {
11442         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11443         *whitePlayer = curRound + appData.tourneyType;
11444     } else if(appData.tourneyType > 0) {
11445         *whitePlayer = curPairing;
11446         *blackPlayer = curRound + appData.tourneyType;
11447     }
11448
11449     // take care of white/black alternation per round.
11450     // For cycles and games this is already taken care of by default, derived from matchGame!
11451     return curRound & 1;
11452 }
11453
11454 int
11455 NextTourneyGame (int nr, int *swapColors)
11456 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11457     char *p, *q;
11458     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11459     FILE *tf;
11460     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11461     tf = fopen(appData.tourneyFile, "r");
11462     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11463     ParseArgsFromFile(tf); fclose(tf);
11464     InitTimeControls(); // TC might be altered from tourney file
11465
11466     nPlayers = CountPlayers(appData.participants); // count participants
11467     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11468     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11469
11470     if(syncInterval) {
11471         p = q = appData.results;
11472         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11473         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11474             DisplayMessage(_("Waiting for other game(s)"),"");
11475             waitingForGame = TRUE;
11476             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11477             return 0;
11478         }
11479         waitingForGame = FALSE;
11480     }
11481
11482     if(appData.tourneyType < 0) {
11483         if(nr>=0 && !pairingReceived) {
11484             char buf[1<<16];
11485             if(pairing.pr == NoProc) {
11486                 if(!appData.pairingEngine[0]) {
11487                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11488                     return 0;
11489                 }
11490                 StartChessProgram(&pairing); // starts the pairing engine
11491             }
11492             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11493             SendToProgram(buf, &pairing);
11494             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11495             SendToProgram(buf, &pairing);
11496             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11497         }
11498         pairingReceived = 0;                              // ... so we continue here
11499         *swapColors = 0;
11500         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11501         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11502         matchGame = 1; roundNr = nr / syncInterval + 1;
11503     }
11504
11505     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11506
11507     // redefine engines, engine dir, etc.
11508     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11509     if(first.pr == NoProc) {
11510       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11511       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11512     }
11513     if(second.pr == NoProc) {
11514       SwapEngines(1);
11515       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11516       SwapEngines(1);         // and make that valid for second engine by swapping
11517       InitEngine(&second, 1);
11518     }
11519     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11520     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11521     return OK;
11522 }
11523
11524 void
11525 NextMatchGame ()
11526 {   // performs game initialization that does not invoke engines, and then tries to start the game
11527     int res, firstWhite, swapColors = 0;
11528     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11529     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
11530         char buf[MSG_SIZ];
11531         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11532         if(strcmp(buf, currentDebugFile)) { // name has changed
11533             FILE *f = fopen(buf, "w");
11534             if(f) { // if opening the new file failed, just keep using the old one
11535                 ASSIGN(currentDebugFile, buf);
11536                 fclose(debugFP);
11537                 debugFP = f;
11538             }
11539             if(appData.serverFileName) {
11540                 if(serverFP) fclose(serverFP);
11541                 serverFP = fopen(appData.serverFileName, "w");
11542                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11543                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11544             }
11545         }
11546     }
11547     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11548     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11549     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11550     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11551     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11552     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11553     Reset(FALSE, first.pr != NoProc);
11554     res = LoadGameOrPosition(matchGame); // setup game
11555     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11556     if(!res) return; // abort when bad game/pos file
11557     if(appData.epd) {// in EPD mode we make sure first engine is to move
11558         firstWhite = !(forwardMostMove & 1);
11559         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11560         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11561     }
11562     TwoMachinesEvent();
11563 }
11564
11565 void
11566 UserAdjudicationEvent (int result)
11567 {
11568     ChessMove gameResult = GameIsDrawn;
11569
11570     if( result > 0 ) {
11571         gameResult = WhiteWins;
11572     }
11573     else if( result < 0 ) {
11574         gameResult = BlackWins;
11575     }
11576
11577     if( gameMode == TwoMachinesPlay ) {
11578         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11579     }
11580 }
11581
11582
11583 // [HGM] save: calculate checksum of game to make games easily identifiable
11584 int
11585 StringCheckSum (char *s)
11586 {
11587         int i = 0;
11588         if(s==NULL) return 0;
11589         while(*s) i = i*259 + *s++;
11590         return i;
11591 }
11592
11593 int
11594 GameCheckSum ()
11595 {
11596         int i, sum=0;
11597         for(i=backwardMostMove; i<forwardMostMove; i++) {
11598                 sum += pvInfoList[i].depth;
11599                 sum += StringCheckSum(parseList[i]);
11600                 sum += StringCheckSum(commentList[i]);
11601                 sum *= 261;
11602         }
11603         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11604         return sum + StringCheckSum(commentList[i]);
11605 } // end of save patch
11606
11607 void
11608 GameEnds (ChessMove result, char *resultDetails, int whosays)
11609 {
11610     GameMode nextGameMode;
11611     int isIcsGame;
11612     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11613
11614     if(endingGame) return; /* [HGM] crash: forbid recursion */
11615     endingGame = 1;
11616     if(twoBoards) { // [HGM] dual: switch back to one board
11617         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11618         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11619     }
11620     if (appData.debugMode) {
11621       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11622               result, resultDetails ? resultDetails : "(null)", whosays);
11623     }
11624
11625     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11626
11627     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11628
11629     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11630         /* If we are playing on ICS, the server decides when the
11631            game is over, but the engine can offer to draw, claim
11632            a draw, or resign.
11633          */
11634 #if ZIPPY
11635         if (appData.zippyPlay && first.initDone) {
11636             if (result == GameIsDrawn) {
11637                 /* In case draw still needs to be claimed */
11638                 SendToICS(ics_prefix);
11639                 SendToICS("draw\n");
11640             } else if (StrCaseStr(resultDetails, "resign")) {
11641                 SendToICS(ics_prefix);
11642                 SendToICS("resign\n");
11643             }
11644         }
11645 #endif
11646         endingGame = 0; /* [HGM] crash */
11647         return;
11648     }
11649
11650     /* If we're loading the game from a file, stop */
11651     if (whosays == GE_FILE) {
11652       (void) StopLoadGameTimer();
11653       gameFileFP = NULL;
11654     }
11655
11656     /* Cancel draw offers */
11657     first.offeredDraw = second.offeredDraw = 0;
11658
11659     /* If this is an ICS game, only ICS can really say it's done;
11660        if not, anyone can. */
11661     isIcsGame = (gameMode == IcsPlayingWhite ||
11662                  gameMode == IcsPlayingBlack ||
11663                  gameMode == IcsObserving    ||
11664                  gameMode == IcsExamining);
11665
11666     if (!isIcsGame || whosays == GE_ICS) {
11667         /* OK -- not an ICS game, or ICS said it was done */
11668         StopClocks();
11669         if (!isIcsGame && !appData.noChessProgram)
11670           SetUserThinkingEnables();
11671
11672         /* [HGM] if a machine claims the game end we verify this claim */
11673         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11674             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11675                 char claimer;
11676                 ChessMove trueResult = (ChessMove) -1;
11677
11678                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11679                                             first.twoMachinesColor[0] :
11680                                             second.twoMachinesColor[0] ;
11681
11682                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11683                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11684                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11685                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11686                 } else
11687                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11688                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11689                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11690                 } else
11691                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11692                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11693                 }
11694
11695                 // now verify win claims, but not in drop games, as we don't understand those yet
11696                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11697                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11698                     (result == WhiteWins && claimer == 'w' ||
11699                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11700                       if (appData.debugMode) {
11701                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11702                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11703                       }
11704                       if(result != trueResult) {
11705                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11706                               result = claimer == 'w' ? BlackWins : WhiteWins;
11707                               resultDetails = buf;
11708                       }
11709                 } else
11710                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11711                     && (forwardMostMove <= backwardMostMove ||
11712                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11713                         (claimer=='b')==(forwardMostMove&1))
11714                                                                                   ) {
11715                       /* [HGM] verify: draws that were not flagged are false claims */
11716                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11717                       result = claimer == 'w' ? BlackWins : WhiteWins;
11718                       resultDetails = buf;
11719                 }
11720                 /* (Claiming a loss is accepted no questions asked!) */
11721             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11722                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11723                 result = GameUnfinished;
11724                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11725             }
11726             /* [HGM] bare: don't allow bare King to win */
11727             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11728                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11729                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11730                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11731                && result != GameIsDrawn)
11732             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11733                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11734                         int p = (int)boards[forwardMostMove][i][j] - color;
11735                         if(p >= 0 && p <= (int)WhiteKing) k++;
11736                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11737                 }
11738                 if (appData.debugMode) {
11739                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11740                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11741                 }
11742                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11743                         result = GameIsDrawn;
11744                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11745                         resultDetails = buf;
11746                 }
11747             }
11748         }
11749
11750
11751         if(serverMoves != NULL && !loadFlag) { char c = '=';
11752             if(result==WhiteWins) c = '+';
11753             if(result==BlackWins) c = '-';
11754             if(resultDetails != NULL)
11755                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11756         }
11757         if (resultDetails != NULL) {
11758             gameInfo.result = result;
11759             gameInfo.resultDetails = StrSave(resultDetails);
11760
11761             /* display last move only if game was not loaded from file */
11762             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11763                 DisplayMove(currentMove - 1);
11764
11765             if (forwardMostMove != 0) {
11766                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11767                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11768                                                                 ) {
11769                     if (*appData.saveGameFile != NULLCHAR) {
11770                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11771                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11772                         else
11773                         SaveGameToFile(appData.saveGameFile, TRUE);
11774                     } else if (appData.autoSaveGames) {
11775                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11776                     }
11777                     if (*appData.savePositionFile != NULLCHAR) {
11778                         SavePositionToFile(appData.savePositionFile);
11779                     }
11780                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11781                 }
11782             }
11783
11784             /* Tell program how game ended in case it is learning */
11785             /* [HGM] Moved this to after saving the PGN, just in case */
11786             /* engine died and we got here through time loss. In that */
11787             /* case we will get a fatal error writing the pipe, which */
11788             /* would otherwise lose us the PGN.                       */
11789             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11790             /* output during GameEnds should never be fatal anymore   */
11791             if (gameMode == MachinePlaysWhite ||
11792                 gameMode == MachinePlaysBlack ||
11793                 gameMode == TwoMachinesPlay ||
11794                 gameMode == IcsPlayingWhite ||
11795                 gameMode == IcsPlayingBlack ||
11796                 gameMode == BeginningOfGame) {
11797                 char buf[MSG_SIZ];
11798                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11799                         resultDetails);
11800                 if (first.pr != NoProc) {
11801                     SendToProgram(buf, &first);
11802                 }
11803                 if (second.pr != NoProc &&
11804                     gameMode == TwoMachinesPlay) {
11805                     SendToProgram(buf, &second);
11806                 }
11807             }
11808         }
11809
11810         if (appData.icsActive) {
11811             if (appData.quietPlay &&
11812                 (gameMode == IcsPlayingWhite ||
11813                  gameMode == IcsPlayingBlack)) {
11814                 SendToICS(ics_prefix);
11815                 SendToICS("set shout 1\n");
11816             }
11817             nextGameMode = IcsIdle;
11818             ics_user_moved = FALSE;
11819             /* clean up premove.  It's ugly when the game has ended and the
11820              * premove highlights are still on the board.
11821              */
11822             if (gotPremove) {
11823               gotPremove = FALSE;
11824               ClearPremoveHighlights();
11825               DrawPosition(FALSE, boards[currentMove]);
11826             }
11827             if (whosays == GE_ICS) {
11828                 switch (result) {
11829                 case WhiteWins:
11830                     if (gameMode == IcsPlayingWhite)
11831                         PlayIcsWinSound();
11832                     else if(gameMode == IcsPlayingBlack)
11833                         PlayIcsLossSound();
11834                     break;
11835                 case BlackWins:
11836                     if (gameMode == IcsPlayingBlack)
11837                         PlayIcsWinSound();
11838                     else if(gameMode == IcsPlayingWhite)
11839                         PlayIcsLossSound();
11840                     break;
11841                 case GameIsDrawn:
11842                     PlayIcsDrawSound();
11843                     break;
11844                 default:
11845                     PlayIcsUnfinishedSound();
11846                 }
11847             }
11848             if(appData.quitNext) { ExitEvent(0); return; }
11849         } else if (gameMode == EditGame ||
11850                    gameMode == PlayFromGameFile ||
11851                    gameMode == AnalyzeMode ||
11852                    gameMode == AnalyzeFile) {
11853             nextGameMode = gameMode;
11854         } else {
11855             nextGameMode = EndOfGame;
11856         }
11857         pausing = FALSE;
11858         ModeHighlight();
11859     } else {
11860         nextGameMode = gameMode;
11861     }
11862
11863     if (appData.noChessProgram) {
11864         gameMode = nextGameMode;
11865         ModeHighlight();
11866         endingGame = 0; /* [HGM] crash */
11867         return;
11868     }
11869
11870     if (first.reuse) {
11871         /* Put first chess program into idle state */
11872         if (first.pr != NoProc &&
11873             (gameMode == MachinePlaysWhite ||
11874              gameMode == MachinePlaysBlack ||
11875              gameMode == TwoMachinesPlay ||
11876              gameMode == IcsPlayingWhite ||
11877              gameMode == IcsPlayingBlack ||
11878              gameMode == BeginningOfGame)) {
11879             SendToProgram("force\n", &first);
11880             if (first.usePing) {
11881               char buf[MSG_SIZ];
11882               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11883               SendToProgram(buf, &first);
11884             }
11885         }
11886     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11887         /* Kill off first chess program */
11888         if (first.isr != NULL)
11889           RemoveInputSource(first.isr);
11890         first.isr = NULL;
11891
11892         if (first.pr != NoProc) {
11893             ExitAnalyzeMode();
11894             DoSleep( appData.delayBeforeQuit );
11895             SendToProgram("quit\n", &first);
11896             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11897             first.reload = TRUE;
11898         }
11899         first.pr = NoProc;
11900     }
11901     if (second.reuse) {
11902         /* Put second chess program into idle state */
11903         if (second.pr != NoProc &&
11904             gameMode == TwoMachinesPlay) {
11905             SendToProgram("force\n", &second);
11906             if (second.usePing) {
11907               char buf[MSG_SIZ];
11908               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11909               SendToProgram(buf, &second);
11910             }
11911         }
11912     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11913         /* Kill off second chess program */
11914         if (second.isr != NULL)
11915           RemoveInputSource(second.isr);
11916         second.isr = NULL;
11917
11918         if (second.pr != NoProc) {
11919             DoSleep( appData.delayBeforeQuit );
11920             SendToProgram("quit\n", &second);
11921             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11922             second.reload = TRUE;
11923         }
11924         second.pr = NoProc;
11925     }
11926
11927     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11928         char resChar = '=';
11929         switch (result) {
11930         case WhiteWins:
11931           resChar = '+';
11932           if (first.twoMachinesColor[0] == 'w') {
11933             first.matchWins++;
11934           } else {
11935             second.matchWins++;
11936           }
11937           break;
11938         case BlackWins:
11939           resChar = '-';
11940           if (first.twoMachinesColor[0] == 'b') {
11941             first.matchWins++;
11942           } else {
11943             second.matchWins++;
11944           }
11945           break;
11946         case GameUnfinished:
11947           resChar = ' ';
11948         default:
11949           break;
11950         }
11951
11952         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11953         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11954             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11955             ReserveGame(nextGame, resChar); // sets nextGame
11956             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11957             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11958         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11959
11960         if (nextGame <= appData.matchGames && !abortMatch) {
11961             gameMode = nextGameMode;
11962             matchGame = nextGame; // this will be overruled in tourney mode!
11963             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11964             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11965             endingGame = 0; /* [HGM] crash */
11966             return;
11967         } else {
11968             gameMode = nextGameMode;
11969             if(appData.epd) {
11970                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11971                 OutputKibitz(2, buf);
11972                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11973                 OutputKibitz(2, buf);
11974                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11975                 if(second.matchWins) OutputKibitz(2, buf);
11976                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11977                 OutputKibitz(2, buf);
11978             }
11979             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11980                      first.tidy, second.tidy,
11981                      first.matchWins, second.matchWins,
11982                      appData.matchGames - (first.matchWins + second.matchWins));
11983             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11984             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11985             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11986             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11987                 first.twoMachinesColor = "black\n";
11988                 second.twoMachinesColor = "white\n";
11989             } else {
11990                 first.twoMachinesColor = "white\n";
11991                 second.twoMachinesColor = "black\n";
11992             }
11993         }
11994     }
11995     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11996         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11997       ExitAnalyzeMode();
11998     gameMode = nextGameMode;
11999     ModeHighlight();
12000     endingGame = 0;  /* [HGM] crash */
12001     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12002         if(matchMode == TRUE) { // match through command line: exit with or without popup
12003             if(ranking) {
12004                 ToNrEvent(forwardMostMove);
12005                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12006                 else ExitEvent(0);
12007             } else DisplayFatalError(buf, 0, 0);
12008         } else { // match through menu; just stop, with or without popup
12009             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12010             ModeHighlight();
12011             if(ranking){
12012                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12013             } else DisplayNote(buf);
12014       }
12015       if(ranking) free(ranking);
12016     }
12017 }
12018
12019 /* Assumes program was just initialized (initString sent).
12020    Leaves program in force mode. */
12021 void
12022 FeedMovesToProgram (ChessProgramState *cps, int upto)
12023 {
12024     int i;
12025
12026     if (appData.debugMode)
12027       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12028               startedFromSetupPosition ? "position and " : "",
12029               backwardMostMove, upto, cps->which);
12030     if(currentlyInitializedVariant != gameInfo.variant) {
12031       char buf[MSG_SIZ];
12032         // [HGM] variantswitch: make engine aware of new variant
12033         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12034                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12035                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12036         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12037         SendToProgram(buf, cps);
12038         currentlyInitializedVariant = gameInfo.variant;
12039     }
12040     SendToProgram("force\n", cps);
12041     if (startedFromSetupPosition) {
12042         SendBoard(cps, backwardMostMove);
12043     if (appData.debugMode) {
12044         fprintf(debugFP, "feedMoves\n");
12045     }
12046     }
12047     for (i = backwardMostMove; i < upto; i++) {
12048         SendMoveToProgram(i, cps);
12049     }
12050 }
12051
12052
12053 int
12054 ResurrectChessProgram ()
12055 {
12056      /* The chess program may have exited.
12057         If so, restart it and feed it all the moves made so far. */
12058     static int doInit = 0;
12059
12060     if (appData.noChessProgram) return 1;
12061
12062     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12063         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12064         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12065         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12066     } else {
12067         if (first.pr != NoProc) return 1;
12068         StartChessProgram(&first);
12069     }
12070     InitChessProgram(&first, FALSE);
12071     FeedMovesToProgram(&first, currentMove);
12072
12073     if (!first.sendTime) {
12074         /* can't tell gnuchess what its clock should read,
12075            so we bow to its notion. */
12076         ResetClocks();
12077         timeRemaining[0][currentMove] = whiteTimeRemaining;
12078         timeRemaining[1][currentMove] = blackTimeRemaining;
12079     }
12080
12081     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12082                 appData.icsEngineAnalyze) && first.analysisSupport) {
12083       SendToProgram("analyze\n", &first);
12084       first.analyzing = TRUE;
12085     }
12086     return 1;
12087 }
12088
12089 /*
12090  * Button procedures
12091  */
12092 void
12093 Reset (int redraw, int init)
12094 {
12095     int i;
12096
12097     if (appData.debugMode) {
12098         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12099                 redraw, init, gameMode);
12100     }
12101     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12102     deadRanks = 0; // assume entire board is used
12103     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12104     CleanupTail(); // [HGM] vari: delete any stored variations
12105     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12106     pausing = pauseExamInvalid = FALSE;
12107     startedFromSetupPosition = blackPlaysFirst = FALSE;
12108     firstMove = TRUE;
12109     whiteFlag = blackFlag = FALSE;
12110     userOfferedDraw = FALSE;
12111     hintRequested = bookRequested = FALSE;
12112     first.maybeThinking = FALSE;
12113     second.maybeThinking = FALSE;
12114     first.bookSuspend = FALSE; // [HGM] book
12115     second.bookSuspend = FALSE;
12116     thinkOutput[0] = NULLCHAR;
12117     lastHint[0] = NULLCHAR;
12118     ClearGameInfo(&gameInfo);
12119     gameInfo.variant = StringToVariant(appData.variant);
12120     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12121         gameInfo.variant = VariantUnknown;
12122         strncpy(engineVariant, appData.variant, MSG_SIZ);
12123     }
12124     ics_user_moved = ics_clock_paused = FALSE;
12125     ics_getting_history = H_FALSE;
12126     ics_gamenum = -1;
12127     white_holding[0] = black_holding[0] = NULLCHAR;
12128     ClearProgramStats();
12129     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12130
12131     ResetFrontEnd();
12132     ClearHighlights();
12133     flipView = appData.flipView;
12134     ClearPremoveHighlights();
12135     gotPremove = FALSE;
12136     alarmSounded = FALSE;
12137     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12138
12139     GameEnds(EndOfFile, NULL, GE_PLAYER);
12140     if(appData.serverMovesName != NULL) {
12141         /* [HGM] prepare to make moves file for broadcasting */
12142         clock_t t = clock();
12143         if(serverMoves != NULL) fclose(serverMoves);
12144         serverMoves = fopen(appData.serverMovesName, "r");
12145         if(serverMoves != NULL) {
12146             fclose(serverMoves);
12147             /* delay 15 sec before overwriting, so all clients can see end */
12148             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12149         }
12150         serverMoves = fopen(appData.serverMovesName, "w");
12151     }
12152
12153     ExitAnalyzeMode();
12154     gameMode = BeginningOfGame;
12155     ModeHighlight();
12156     if(appData.icsActive) gameInfo.variant = VariantNormal;
12157     currentMove = forwardMostMove = backwardMostMove = 0;
12158     MarkTargetSquares(1);
12159     InitPosition(redraw);
12160     for (i = 0; i < MAX_MOVES; i++) {
12161         if (commentList[i] != NULL) {
12162             free(commentList[i]);
12163             commentList[i] = NULL;
12164         }
12165     }
12166     ResetClocks();
12167     timeRemaining[0][0] = whiteTimeRemaining;
12168     timeRemaining[1][0] = blackTimeRemaining;
12169
12170     if (first.pr == NoProc) {
12171         StartChessProgram(&first);
12172     }
12173     if (init) {
12174             InitChessProgram(&first, startedFromSetupPosition);
12175     }
12176     DisplayTitle("");
12177     DisplayMessage("", "");
12178     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12179     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12180     ClearMap();        // [HGM] exclude: invalidate map
12181 }
12182
12183 void
12184 AutoPlayGameLoop ()
12185 {
12186     for (;;) {
12187         if (!AutoPlayOneMove())
12188           return;
12189         if (matchMode || appData.timeDelay == 0)
12190           continue;
12191         if (appData.timeDelay < 0)
12192           return;
12193         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12194         break;
12195     }
12196 }
12197
12198 void
12199 AnalyzeNextGame()
12200 {
12201     ReloadGame(1); // next game
12202 }
12203
12204 int
12205 AutoPlayOneMove ()
12206 {
12207     int fromX, fromY, toX, toY;
12208
12209     if (appData.debugMode) {
12210       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12211     }
12212
12213     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12214       return FALSE;
12215
12216     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12217       pvInfoList[currentMove].depth = programStats.depth;
12218       pvInfoList[currentMove].score = programStats.score;
12219       pvInfoList[currentMove].time  = 0;
12220       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12221       else { // append analysis of final position as comment
12222         char buf[MSG_SIZ];
12223         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12224         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12225       }
12226       programStats.depth = 0;
12227     }
12228
12229     if (currentMove >= forwardMostMove) {
12230       if(gameMode == AnalyzeFile) {
12231           if(appData.loadGameIndex == -1) {
12232             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12233           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12234           } else {
12235           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12236         }
12237       }
12238 //      gameMode = EndOfGame;
12239 //      ModeHighlight();
12240
12241       /* [AS] Clear current move marker at the end of a game */
12242       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12243
12244       return FALSE;
12245     }
12246
12247     toX = moveList[currentMove][2] - AAA;
12248     toY = moveList[currentMove][3] - ONE;
12249
12250     if (moveList[currentMove][1] == '@') {
12251         if (appData.highlightLastMove) {
12252             SetHighlights(-1, -1, toX, toY);
12253         }
12254     } else {
12255         fromX = moveList[currentMove][0] - AAA;
12256         fromY = moveList[currentMove][1] - ONE;
12257
12258         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12259
12260         if(moveList[currentMove][4] == ';') { // multi-leg
12261             killX = moveList[currentMove][5] - AAA;
12262             killY = moveList[currentMove][6] - ONE;
12263         }
12264         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12265         killX = killY = -1;
12266
12267         if (appData.highlightLastMove) {
12268             SetHighlights(fromX, fromY, toX, toY);
12269         }
12270     }
12271     DisplayMove(currentMove);
12272     SendMoveToProgram(currentMove++, &first);
12273     DisplayBothClocks();
12274     DrawPosition(FALSE, boards[currentMove]);
12275     // [HGM] PV info: always display, routine tests if empty
12276     DisplayComment(currentMove - 1, commentList[currentMove]);
12277     return TRUE;
12278 }
12279
12280
12281 int
12282 LoadGameOneMove (ChessMove readAhead)
12283 {
12284     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12285     char promoChar = NULLCHAR;
12286     ChessMove moveType;
12287     char move[MSG_SIZ];
12288     char *p, *q;
12289
12290     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12291         gameMode != AnalyzeMode && gameMode != Training) {
12292         gameFileFP = NULL;
12293         return FALSE;
12294     }
12295
12296     yyboardindex = forwardMostMove;
12297     if (readAhead != EndOfFile) {
12298       moveType = readAhead;
12299     } else {
12300       if (gameFileFP == NULL)
12301           return FALSE;
12302       moveType = (ChessMove) Myylex();
12303     }
12304
12305     done = FALSE;
12306     switch (moveType) {
12307       case Comment:
12308         if (appData.debugMode)
12309           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12310         p = yy_text;
12311
12312         /* append the comment but don't display it */
12313         AppendComment(currentMove, p, FALSE);
12314         return TRUE;
12315
12316       case WhiteCapturesEnPassant:
12317       case BlackCapturesEnPassant:
12318       case WhitePromotion:
12319       case BlackPromotion:
12320       case WhiteNonPromotion:
12321       case BlackNonPromotion:
12322       case NormalMove:
12323       case FirstLeg:
12324       case WhiteKingSideCastle:
12325       case WhiteQueenSideCastle:
12326       case BlackKingSideCastle:
12327       case BlackQueenSideCastle:
12328       case WhiteKingSideCastleWild:
12329       case WhiteQueenSideCastleWild:
12330       case BlackKingSideCastleWild:
12331       case BlackQueenSideCastleWild:
12332       /* PUSH Fabien */
12333       case WhiteHSideCastleFR:
12334       case WhiteASideCastleFR:
12335       case BlackHSideCastleFR:
12336       case BlackASideCastleFR:
12337       /* POP Fabien */
12338         if (appData.debugMode)
12339           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12340         fromX = currentMoveString[0] - AAA;
12341         fromY = currentMoveString[1] - ONE;
12342         toX = currentMoveString[2] - AAA;
12343         toY = currentMoveString[3] - ONE;
12344         promoChar = currentMoveString[4];
12345         if(promoChar == ';') promoChar = currentMoveString[7];
12346         break;
12347
12348       case WhiteDrop:
12349       case BlackDrop:
12350         if (appData.debugMode)
12351           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12352         fromX = moveType == WhiteDrop ?
12353           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12354         (int) CharToPiece(ToLower(currentMoveString[0]));
12355         fromY = DROP_RANK;
12356         toX = currentMoveString[2] - AAA;
12357         toY = currentMoveString[3] - ONE;
12358         break;
12359
12360       case WhiteWins:
12361       case BlackWins:
12362       case GameIsDrawn:
12363       case GameUnfinished:
12364         if (appData.debugMode)
12365           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12366         p = strchr(yy_text, '{');
12367         if (p == NULL) p = strchr(yy_text, '(');
12368         if (p == NULL) {
12369             p = yy_text;
12370             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12371         } else {
12372             q = strchr(p, *p == '{' ? '}' : ')');
12373             if (q != NULL) *q = NULLCHAR;
12374             p++;
12375         }
12376         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12377         GameEnds(moveType, p, GE_FILE);
12378         done = TRUE;
12379         if (cmailMsgLoaded) {
12380             ClearHighlights();
12381             flipView = WhiteOnMove(currentMove);
12382             if (moveType == GameUnfinished) flipView = !flipView;
12383             if (appData.debugMode)
12384               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12385         }
12386         break;
12387
12388       case EndOfFile:
12389         if (appData.debugMode)
12390           fprintf(debugFP, "Parser hit end of file\n");
12391         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12392           case MT_NONE:
12393           case MT_CHECK:
12394             break;
12395           case MT_CHECKMATE:
12396           case MT_STAINMATE:
12397             if (WhiteOnMove(currentMove)) {
12398                 GameEnds(BlackWins, "Black mates", GE_FILE);
12399             } else {
12400                 GameEnds(WhiteWins, "White mates", GE_FILE);
12401             }
12402             break;
12403           case MT_STALEMATE:
12404             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12405             break;
12406         }
12407         done = TRUE;
12408         break;
12409
12410       case MoveNumberOne:
12411         if (lastLoadGameStart == GNUChessGame) {
12412             /* GNUChessGames have numbers, but they aren't move numbers */
12413             if (appData.debugMode)
12414               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12415                       yy_text, (int) moveType);
12416             return LoadGameOneMove(EndOfFile); /* tail recursion */
12417         }
12418         /* else fall thru */
12419
12420       case XBoardGame:
12421       case GNUChessGame:
12422       case PGNTag:
12423         /* Reached start of next game in file */
12424         if (appData.debugMode)
12425           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12426         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12427           case MT_NONE:
12428           case MT_CHECK:
12429             break;
12430           case MT_CHECKMATE:
12431           case MT_STAINMATE:
12432             if (WhiteOnMove(currentMove)) {
12433                 GameEnds(BlackWins, "Black mates", GE_FILE);
12434             } else {
12435                 GameEnds(WhiteWins, "White mates", GE_FILE);
12436             }
12437             break;
12438           case MT_STALEMATE:
12439             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12440             break;
12441         }
12442         done = TRUE;
12443         break;
12444
12445       case PositionDiagram:     /* should not happen; ignore */
12446       case ElapsedTime:         /* ignore */
12447       case NAG:                 /* ignore */
12448         if (appData.debugMode)
12449           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12450                   yy_text, (int) moveType);
12451         return LoadGameOneMove(EndOfFile); /* tail recursion */
12452
12453       case IllegalMove:
12454         if (appData.testLegality) {
12455             if (appData.debugMode)
12456               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12457             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12458                     (forwardMostMove / 2) + 1,
12459                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12460             DisplayError(move, 0);
12461             done = TRUE;
12462         } else {
12463             if (appData.debugMode)
12464               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12465                       yy_text, currentMoveString);
12466             if(currentMoveString[1] == '@') {
12467                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12468                 fromY = DROP_RANK;
12469             } else {
12470                 fromX = currentMoveString[0] - AAA;
12471                 fromY = currentMoveString[1] - ONE;
12472             }
12473             toX = currentMoveString[2] - AAA;
12474             toY = currentMoveString[3] - ONE;
12475             promoChar = currentMoveString[4];
12476         }
12477         break;
12478
12479       case AmbiguousMove:
12480         if (appData.debugMode)
12481           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12482         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12483                 (forwardMostMove / 2) + 1,
12484                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12485         DisplayError(move, 0);
12486         done = TRUE;
12487         break;
12488
12489       default:
12490       case ImpossibleMove:
12491         if (appData.debugMode)
12492           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12493         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12494                 (forwardMostMove / 2) + 1,
12495                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12496         DisplayError(move, 0);
12497         done = TRUE;
12498         break;
12499     }
12500
12501     if (done) {
12502         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12503             DrawPosition(FALSE, boards[currentMove]);
12504             DisplayBothClocks();
12505             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12506               DisplayComment(currentMove - 1, commentList[currentMove]);
12507         }
12508         (void) StopLoadGameTimer();
12509         gameFileFP = NULL;
12510         cmailOldMove = forwardMostMove;
12511         return FALSE;
12512     } else {
12513         /* currentMoveString is set as a side-effect of yylex */
12514
12515         thinkOutput[0] = NULLCHAR;
12516         MakeMove(fromX, fromY, toX, toY, promoChar);
12517         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12518         currentMove = forwardMostMove;
12519         return TRUE;
12520     }
12521 }
12522
12523 /* Load the nth game from the given file */
12524 int
12525 LoadGameFromFile (char *filename, int n, char *title, int useList)
12526 {
12527     FILE *f;
12528     char buf[MSG_SIZ];
12529
12530     if (strcmp(filename, "-") == 0) {
12531         f = stdin;
12532         title = "stdin";
12533     } else {
12534         f = fopen(filename, "rb");
12535         if (f == NULL) {
12536           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12537             DisplayError(buf, errno);
12538             return FALSE;
12539         }
12540     }
12541     if (fseek(f, 0, 0) == -1) {
12542         /* f is not seekable; probably a pipe */
12543         useList = FALSE;
12544     }
12545     if (useList && n == 0) {
12546         int error = GameListBuild(f);
12547         if (error) {
12548             DisplayError(_("Cannot build game list"), error);
12549         } else if (!ListEmpty(&gameList) &&
12550                    ((ListGame *) gameList.tailPred)->number > 1) {
12551             GameListPopUp(f, title);
12552             return TRUE;
12553         }
12554         GameListDestroy();
12555         n = 1;
12556     }
12557     if (n == 0) n = 1;
12558     return LoadGame(f, n, title, FALSE);
12559 }
12560
12561
12562 void
12563 MakeRegisteredMove ()
12564 {
12565     int fromX, fromY, toX, toY;
12566     char promoChar;
12567     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12568         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12569           case CMAIL_MOVE:
12570           case CMAIL_DRAW:
12571             if (appData.debugMode)
12572               fprintf(debugFP, "Restoring %s for game %d\n",
12573                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12574
12575             thinkOutput[0] = NULLCHAR;
12576             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12577             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12578             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12579             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12580             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12581             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12582             MakeMove(fromX, fromY, toX, toY, promoChar);
12583             ShowMove(fromX, fromY, toX, toY);
12584
12585             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12586               case MT_NONE:
12587               case MT_CHECK:
12588                 break;
12589
12590               case MT_CHECKMATE:
12591               case MT_STAINMATE:
12592                 if (WhiteOnMove(currentMove)) {
12593                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12594                 } else {
12595                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12596                 }
12597                 break;
12598
12599               case MT_STALEMATE:
12600                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12601                 break;
12602             }
12603
12604             break;
12605
12606           case CMAIL_RESIGN:
12607             if (WhiteOnMove(currentMove)) {
12608                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12609             } else {
12610                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12611             }
12612             break;
12613
12614           case CMAIL_ACCEPT:
12615             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12616             break;
12617
12618           default:
12619             break;
12620         }
12621     }
12622
12623     return;
12624 }
12625
12626 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12627 int
12628 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12629 {
12630     int retVal;
12631
12632     if (gameNumber > nCmailGames) {
12633         DisplayError(_("No more games in this message"), 0);
12634         return FALSE;
12635     }
12636     if (f == lastLoadGameFP) {
12637         int offset = gameNumber - lastLoadGameNumber;
12638         if (offset == 0) {
12639             cmailMsg[0] = NULLCHAR;
12640             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12641                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12642                 nCmailMovesRegistered--;
12643             }
12644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12645             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12646                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12647             }
12648         } else {
12649             if (! RegisterMove()) return FALSE;
12650         }
12651     }
12652
12653     retVal = LoadGame(f, gameNumber, title, useList);
12654
12655     /* Make move registered during previous look at this game, if any */
12656     MakeRegisteredMove();
12657
12658     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12659         commentList[currentMove]
12660           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12661         DisplayComment(currentMove - 1, commentList[currentMove]);
12662     }
12663
12664     return retVal;
12665 }
12666
12667 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12668 int
12669 ReloadGame (int offset)
12670 {
12671     int gameNumber = lastLoadGameNumber + offset;
12672     if (lastLoadGameFP == NULL) {
12673         DisplayError(_("No game has been loaded yet"), 0);
12674         return FALSE;
12675     }
12676     if (gameNumber <= 0) {
12677         DisplayError(_("Can't back up any further"), 0);
12678         return FALSE;
12679     }
12680     if (cmailMsgLoaded) {
12681         return CmailLoadGame(lastLoadGameFP, gameNumber,
12682                              lastLoadGameTitle, lastLoadGameUseList);
12683     } else {
12684         return LoadGame(lastLoadGameFP, gameNumber,
12685                         lastLoadGameTitle, lastLoadGameUseList);
12686     }
12687 }
12688
12689 int keys[EmptySquare+1];
12690
12691 int
12692 PositionMatches (Board b1, Board b2)
12693 {
12694     int r, f, sum=0;
12695     switch(appData.searchMode) {
12696         case 1: return CompareWithRights(b1, b2);
12697         case 2:
12698             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12699                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12700             }
12701             return TRUE;
12702         case 3:
12703             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12705                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12706             }
12707             return sum==0;
12708         case 4:
12709             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12710                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12711             }
12712             return sum==0;
12713     }
12714     return TRUE;
12715 }
12716
12717 #define Q_PROMO  4
12718 #define Q_EP     3
12719 #define Q_BCASTL 2
12720 #define Q_WCASTL 1
12721
12722 int pieceList[256], quickBoard[256];
12723 ChessSquare pieceType[256] = { EmptySquare };
12724 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12725 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12726 int soughtTotal, turn;
12727 Boolean epOK, flipSearch;
12728
12729 typedef struct {
12730     unsigned char piece, to;
12731 } Move;
12732
12733 #define DSIZE (250000)
12734
12735 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12736 Move *moveDatabase = initialSpace;
12737 unsigned int movePtr, dataSize = DSIZE;
12738
12739 int
12740 MakePieceList (Board board, int *counts)
12741 {
12742     int r, f, n=Q_PROMO, total=0;
12743     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12744     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12745         int sq = f + (r<<4);
12746         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12747             quickBoard[sq] = ++n;
12748             pieceList[n] = sq;
12749             pieceType[n] = board[r][f];
12750             counts[board[r][f]]++;
12751             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12752             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12753             total++;
12754         }
12755     }
12756     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12757     return total;
12758 }
12759
12760 void
12761 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12762 {
12763     int sq = fromX + (fromY<<4);
12764     int piece = quickBoard[sq], rook;
12765     quickBoard[sq] = 0;
12766     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12767     if(piece == pieceList[1] && fromY == toY) {
12768       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12769         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12770         moveDatabase[movePtr++].piece = Q_WCASTL;
12771         quickBoard[sq] = piece;
12772         piece = quickBoard[from]; quickBoard[from] = 0;
12773         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12774       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12775         quickBoard[sq] = 0; // remove Rook
12776         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12777         moveDatabase[movePtr++].piece = Q_WCASTL;
12778         quickBoard[sq] = pieceList[1]; // put King
12779         piece = rook;
12780         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12781       }
12782     } else
12783     if(piece == pieceList[2] && fromY == toY) {
12784       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12785         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12786         moveDatabase[movePtr++].piece = Q_BCASTL;
12787         quickBoard[sq] = piece;
12788         piece = quickBoard[from]; quickBoard[from] = 0;
12789         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12790       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12791         quickBoard[sq] = 0; // remove Rook
12792         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12793         moveDatabase[movePtr++].piece = Q_BCASTL;
12794         quickBoard[sq] = pieceList[2]; // put King
12795         piece = rook;
12796         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12797       }
12798     } else
12799     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12800         quickBoard[(fromY<<4)+toX] = 0;
12801         moveDatabase[movePtr].piece = Q_EP;
12802         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12803         moveDatabase[movePtr].to = sq;
12804     } else
12805     if(promoPiece != pieceType[piece]) {
12806         moveDatabase[movePtr++].piece = Q_PROMO;
12807         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12808     }
12809     moveDatabase[movePtr].piece = piece;
12810     quickBoard[sq] = piece;
12811     movePtr++;
12812 }
12813
12814 int
12815 PackGame (Board board)
12816 {
12817     Move *newSpace = NULL;
12818     moveDatabase[movePtr].piece = 0; // terminate previous game
12819     if(movePtr > dataSize) {
12820         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12821         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12822         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12823         if(newSpace) {
12824             int i;
12825             Move *p = moveDatabase, *q = newSpace;
12826             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12827             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12828             moveDatabase = newSpace;
12829         } else { // calloc failed, we must be out of memory. Too bad...
12830             dataSize = 0; // prevent calloc events for all subsequent games
12831             return 0;     // and signal this one isn't cached
12832         }
12833     }
12834     movePtr++;
12835     MakePieceList(board, counts);
12836     return movePtr;
12837 }
12838
12839 int
12840 QuickCompare (Board board, int *minCounts, int *maxCounts)
12841 {   // compare according to search mode
12842     int r, f;
12843     switch(appData.searchMode)
12844     {
12845       case 1: // exact position match
12846         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12847         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12848             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12849         }
12850         break;
12851       case 2: // can have extra material on empty squares
12852         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12853             if(board[r][f] == EmptySquare) continue;
12854             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12855         }
12856         break;
12857       case 3: // material with exact Pawn structure
12858         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12859             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12860             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12861         } // fall through to material comparison
12862       case 4: // exact material
12863         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12864         break;
12865       case 6: // material range with given imbalance
12866         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12867         // fall through to range comparison
12868       case 5: // material range
12869         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12870     }
12871     return TRUE;
12872 }
12873
12874 int
12875 QuickScan (Board board, Move *move)
12876 {   // reconstruct game,and compare all positions in it
12877     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12878     do {
12879         int piece = move->piece;
12880         int to = move->to, from = pieceList[piece];
12881         if(found < 0) { // if already found just scan to game end for final piece count
12882           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12883            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12884            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12885                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12886             ) {
12887             static int lastCounts[EmptySquare+1];
12888             int i;
12889             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12890             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12891           } else stretch = 0;
12892           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12893           if(found >= 0 && !appData.minPieces) return found;
12894         }
12895         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12896           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12897           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12898             piece = (++move)->piece;
12899             from = pieceList[piece];
12900             counts[pieceType[piece]]--;
12901             pieceType[piece] = (ChessSquare) move->to;
12902             counts[move->to]++;
12903           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12904             counts[pieceType[quickBoard[to]]]--;
12905             quickBoard[to] = 0; total--;
12906             move++;
12907             continue;
12908           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12909             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12910             from  = pieceList[piece]; // so this must be King
12911             quickBoard[from] = 0;
12912             pieceList[piece] = to;
12913             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12914             quickBoard[from] = 0; // rook
12915             quickBoard[to] = piece;
12916             to = move->to; piece = move->piece;
12917             goto aftercastle;
12918           }
12919         }
12920         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12921         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12922         quickBoard[from] = 0;
12923       aftercastle:
12924         quickBoard[to] = piece;
12925         pieceList[piece] = to;
12926         cnt++; turn ^= 3;
12927         move++;
12928     } while(1);
12929 }
12930
12931 void
12932 InitSearch ()
12933 {
12934     int r, f;
12935     flipSearch = FALSE;
12936     CopyBoard(soughtBoard, boards[currentMove]);
12937     soughtTotal = MakePieceList(soughtBoard, maxSought);
12938     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12939     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12940     CopyBoard(reverseBoard, boards[currentMove]);
12941     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12942         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12943         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12944         reverseBoard[r][f] = piece;
12945     }
12946     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12947     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12948     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12949                  || (boards[currentMove][CASTLING][2] == NoRights ||
12950                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12951                  && (boards[currentMove][CASTLING][5] == NoRights ||
12952                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12953       ) {
12954         flipSearch = TRUE;
12955         CopyBoard(flipBoard, soughtBoard);
12956         CopyBoard(rotateBoard, reverseBoard);
12957         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12958             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12959             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12960         }
12961     }
12962     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12963     if(appData.searchMode >= 5) {
12964         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12965         MakePieceList(soughtBoard, minSought);
12966         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12967     }
12968     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12969         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12970 }
12971
12972 GameInfo dummyInfo;
12973 static int creatingBook;
12974
12975 int
12976 GameContainsPosition (FILE *f, ListGame *lg)
12977 {
12978     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12979     int fromX, fromY, toX, toY;
12980     char promoChar;
12981     static int initDone=FALSE;
12982
12983     // weed out games based on numerical tag comparison
12984     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12985     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12986     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12987     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12988     if(!initDone) {
12989         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12990         initDone = TRUE;
12991     }
12992     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12993     else CopyBoard(boards[scratch], initialPosition); // default start position
12994     if(lg->moves) {
12995         turn = btm + 1;
12996         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12997         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12998     }
12999     if(btm) plyNr++;
13000     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13001     fseek(f, lg->offset, 0);
13002     yynewfile(f);
13003     while(1) {
13004         yyboardindex = scratch;
13005         quickFlag = plyNr+1;
13006         next = Myylex();
13007         quickFlag = 0;
13008         switch(next) {
13009             case PGNTag:
13010                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13011             default:
13012                 continue;
13013
13014             case XBoardGame:
13015             case GNUChessGame:
13016                 if(plyNr) return -1; // after we have seen moves, this is for new game
13017               continue;
13018
13019             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13020             case ImpossibleMove:
13021             case WhiteWins: // game ends here with these four
13022             case BlackWins:
13023             case GameIsDrawn:
13024             case GameUnfinished:
13025                 return -1;
13026
13027             case IllegalMove:
13028                 if(appData.testLegality) return -1;
13029             case WhiteCapturesEnPassant:
13030             case BlackCapturesEnPassant:
13031             case WhitePromotion:
13032             case BlackPromotion:
13033             case WhiteNonPromotion:
13034             case BlackNonPromotion:
13035             case NormalMove:
13036             case FirstLeg:
13037             case WhiteKingSideCastle:
13038             case WhiteQueenSideCastle:
13039             case BlackKingSideCastle:
13040             case BlackQueenSideCastle:
13041             case WhiteKingSideCastleWild:
13042             case WhiteQueenSideCastleWild:
13043             case BlackKingSideCastleWild:
13044             case BlackQueenSideCastleWild:
13045             case WhiteHSideCastleFR:
13046             case WhiteASideCastleFR:
13047             case BlackHSideCastleFR:
13048             case BlackASideCastleFR:
13049                 fromX = currentMoveString[0] - AAA;
13050                 fromY = currentMoveString[1] - ONE;
13051                 toX = currentMoveString[2] - AAA;
13052                 toY = currentMoveString[3] - ONE;
13053                 promoChar = currentMoveString[4];
13054                 break;
13055             case WhiteDrop:
13056             case BlackDrop:
13057                 fromX = next == WhiteDrop ?
13058                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13059                   (int) CharToPiece(ToLower(currentMoveString[0]));
13060                 fromY = DROP_RANK;
13061                 toX = currentMoveString[2] - AAA;
13062                 toY = currentMoveString[3] - ONE;
13063                 promoChar = 0;
13064                 break;
13065         }
13066         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13067         plyNr++;
13068         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13069         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13070         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13071         if(appData.findMirror) {
13072             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13073             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13074         }
13075     }
13076 }
13077
13078 /* Load the nth game from open file f */
13079 int
13080 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13081 {
13082     ChessMove cm;
13083     char buf[MSG_SIZ];
13084     int gn = gameNumber;
13085     ListGame *lg = NULL;
13086     int numPGNTags = 0, i;
13087     int err, pos = -1;
13088     GameMode oldGameMode;
13089     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13090     char oldName[MSG_SIZ];
13091
13092     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13093
13094     if (appData.debugMode)
13095         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13096
13097     if (gameMode == Training )
13098         SetTrainingModeOff();
13099
13100     oldGameMode = gameMode;
13101     if (gameMode != BeginningOfGame) {
13102       Reset(FALSE, TRUE);
13103     }
13104     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13105
13106     gameFileFP = f;
13107     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13108         fclose(lastLoadGameFP);
13109     }
13110
13111     if (useList) {
13112         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13113
13114         if (lg) {
13115             fseek(f, lg->offset, 0);
13116             GameListHighlight(gameNumber);
13117             pos = lg->position;
13118             gn = 1;
13119         }
13120         else {
13121             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13122               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13123             else
13124             DisplayError(_("Game number out of range"), 0);
13125             return FALSE;
13126         }
13127     } else {
13128         GameListDestroy();
13129         if (fseek(f, 0, 0) == -1) {
13130             if (f == lastLoadGameFP ?
13131                 gameNumber == lastLoadGameNumber + 1 :
13132                 gameNumber == 1) {
13133                 gn = 1;
13134             } else {
13135                 DisplayError(_("Can't seek on game file"), 0);
13136                 return FALSE;
13137             }
13138         }
13139     }
13140     lastLoadGameFP = f;
13141     lastLoadGameNumber = gameNumber;
13142     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13143     lastLoadGameUseList = useList;
13144
13145     yynewfile(f);
13146
13147     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13148       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13149                 lg->gameInfo.black);
13150             DisplayTitle(buf);
13151     } else if (*title != NULLCHAR) {
13152         if (gameNumber > 1) {
13153           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13154             DisplayTitle(buf);
13155         } else {
13156             DisplayTitle(title);
13157         }
13158     }
13159
13160     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13161         gameMode = PlayFromGameFile;
13162         ModeHighlight();
13163     }
13164
13165     currentMove = forwardMostMove = backwardMostMove = 0;
13166     CopyBoard(boards[0], initialPosition);
13167     StopClocks();
13168
13169     /*
13170      * Skip the first gn-1 games in the file.
13171      * Also skip over anything that precedes an identifiable
13172      * start of game marker, to avoid being confused by
13173      * garbage at the start of the file.  Currently
13174      * recognized start of game markers are the move number "1",
13175      * the pattern "gnuchess .* game", the pattern
13176      * "^[#;%] [^ ]* game file", and a PGN tag block.
13177      * A game that starts with one of the latter two patterns
13178      * will also have a move number 1, possibly
13179      * following a position diagram.
13180      * 5-4-02: Let's try being more lenient and allowing a game to
13181      * start with an unnumbered move.  Does that break anything?
13182      */
13183     cm = lastLoadGameStart = EndOfFile;
13184     while (gn > 0) {
13185         yyboardindex = forwardMostMove;
13186         cm = (ChessMove) Myylex();
13187         switch (cm) {
13188           case EndOfFile:
13189             if (cmailMsgLoaded) {
13190                 nCmailGames = CMAIL_MAX_GAMES - gn;
13191             } else {
13192                 Reset(TRUE, TRUE);
13193                 DisplayError(_("Game not found in file"), 0);
13194             }
13195             return FALSE;
13196
13197           case GNUChessGame:
13198           case XBoardGame:
13199             gn--;
13200             lastLoadGameStart = cm;
13201             break;
13202
13203           case MoveNumberOne:
13204             switch (lastLoadGameStart) {
13205               case GNUChessGame:
13206               case XBoardGame:
13207               case PGNTag:
13208                 break;
13209               case MoveNumberOne:
13210               case EndOfFile:
13211                 gn--;           /* count this game */
13212                 lastLoadGameStart = cm;
13213                 break;
13214               default:
13215                 /* impossible */
13216                 break;
13217             }
13218             break;
13219
13220           case PGNTag:
13221             switch (lastLoadGameStart) {
13222               case GNUChessGame:
13223               case PGNTag:
13224               case MoveNumberOne:
13225               case EndOfFile:
13226                 gn--;           /* count this game */
13227                 lastLoadGameStart = cm;
13228                 break;
13229               case XBoardGame:
13230                 lastLoadGameStart = cm; /* game counted already */
13231                 break;
13232               default:
13233                 /* impossible */
13234                 break;
13235             }
13236             if (gn > 0) {
13237                 do {
13238                     yyboardindex = forwardMostMove;
13239                     cm = (ChessMove) Myylex();
13240                 } while (cm == PGNTag || cm == Comment);
13241             }
13242             break;
13243
13244           case WhiteWins:
13245           case BlackWins:
13246           case GameIsDrawn:
13247             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13248                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13249                     != CMAIL_OLD_RESULT) {
13250                     nCmailResults ++ ;
13251                     cmailResult[  CMAIL_MAX_GAMES
13252                                 - gn - 1] = CMAIL_OLD_RESULT;
13253                 }
13254             }
13255             break;
13256
13257           case NormalMove:
13258           case FirstLeg:
13259             /* Only a NormalMove can be at the start of a game
13260              * without a position diagram. */
13261             if (lastLoadGameStart == EndOfFile ) {
13262               gn--;
13263               lastLoadGameStart = MoveNumberOne;
13264             }
13265             break;
13266
13267           default:
13268             break;
13269         }
13270     }
13271
13272     if (appData.debugMode)
13273       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13274
13275     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13276
13277     if (cm == XBoardGame) {
13278         /* Skip any header junk before position diagram and/or move 1 */
13279         for (;;) {
13280             yyboardindex = forwardMostMove;
13281             cm = (ChessMove) Myylex();
13282
13283             if (cm == EndOfFile ||
13284                 cm == GNUChessGame || cm == XBoardGame) {
13285                 /* Empty game; pretend end-of-file and handle later */
13286                 cm = EndOfFile;
13287                 break;
13288             }
13289
13290             if (cm == MoveNumberOne || cm == PositionDiagram ||
13291                 cm == PGNTag || cm == Comment)
13292               break;
13293         }
13294     } else if (cm == GNUChessGame) {
13295         if (gameInfo.event != NULL) {
13296             free(gameInfo.event);
13297         }
13298         gameInfo.event = StrSave(yy_text);
13299     }
13300
13301     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13302     while (cm == PGNTag) {
13303         if (appData.debugMode)
13304           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13305         err = ParsePGNTag(yy_text, &gameInfo);
13306         if (!err) numPGNTags++;
13307
13308         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13309         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13310             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13311             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13312             InitPosition(TRUE);
13313             oldVariant = gameInfo.variant;
13314             if (appData.debugMode)
13315               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13316         }
13317
13318
13319         if (gameInfo.fen != NULL) {
13320           Board initial_position;
13321           startedFromSetupPosition = TRUE;
13322           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13323             Reset(TRUE, TRUE);
13324             DisplayError(_("Bad FEN position in file"), 0);
13325             return FALSE;
13326           }
13327           CopyBoard(boards[0], initial_position);
13328           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13329             CopyBoard(initialPosition, initial_position);
13330           if (blackPlaysFirst) {
13331             currentMove = forwardMostMove = backwardMostMove = 1;
13332             CopyBoard(boards[1], initial_position);
13333             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13334             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13335             timeRemaining[0][1] = whiteTimeRemaining;
13336             timeRemaining[1][1] = blackTimeRemaining;
13337             if (commentList[0] != NULL) {
13338               commentList[1] = commentList[0];
13339               commentList[0] = NULL;
13340             }
13341           } else {
13342             currentMove = forwardMostMove = backwardMostMove = 0;
13343           }
13344           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13345           {   int i;
13346               initialRulePlies = FENrulePlies;
13347               for( i=0; i< nrCastlingRights; i++ )
13348                   initialRights[i] = initial_position[CASTLING][i];
13349           }
13350           yyboardindex = forwardMostMove;
13351           free(gameInfo.fen);
13352           gameInfo.fen = NULL;
13353         }
13354
13355         yyboardindex = forwardMostMove;
13356         cm = (ChessMove) Myylex();
13357
13358         /* Handle comments interspersed among the tags */
13359         while (cm == Comment) {
13360             char *p;
13361             if (appData.debugMode)
13362               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13363             p = yy_text;
13364             AppendComment(currentMove, p, FALSE);
13365             yyboardindex = forwardMostMove;
13366             cm = (ChessMove) Myylex();
13367         }
13368     }
13369
13370     /* don't rely on existence of Event tag since if game was
13371      * pasted from clipboard the Event tag may not exist
13372      */
13373     if (numPGNTags > 0){
13374         char *tags;
13375         if (gameInfo.variant == VariantNormal) {
13376           VariantClass v = StringToVariant(gameInfo.event);
13377           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13378           if(v < VariantShogi) gameInfo.variant = v;
13379         }
13380         if (!matchMode) {
13381           if( appData.autoDisplayTags ) {
13382             tags = PGNTags(&gameInfo);
13383             TagsPopUp(tags, CmailMsg());
13384             free(tags);
13385           }
13386         }
13387     } else {
13388         /* Make something up, but don't display it now */
13389         SetGameInfo();
13390         TagsPopDown();
13391     }
13392
13393     if (cm == PositionDiagram) {
13394         int i, j;
13395         char *p;
13396         Board initial_position;
13397
13398         if (appData.debugMode)
13399           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13400
13401         if (!startedFromSetupPosition) {
13402             p = yy_text;
13403             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13404               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13405                 switch (*p) {
13406                   case '{':
13407                   case '[':
13408                   case '-':
13409                   case ' ':
13410                   case '\t':
13411                   case '\n':
13412                   case '\r':
13413                     break;
13414                   default:
13415                     initial_position[i][j++] = CharToPiece(*p);
13416                     break;
13417                 }
13418             while (*p == ' ' || *p == '\t' ||
13419                    *p == '\n' || *p == '\r') p++;
13420
13421             if (strncmp(p, "black", strlen("black"))==0)
13422               blackPlaysFirst = TRUE;
13423             else
13424               blackPlaysFirst = FALSE;
13425             startedFromSetupPosition = TRUE;
13426
13427             CopyBoard(boards[0], initial_position);
13428             if (blackPlaysFirst) {
13429                 currentMove = forwardMostMove = backwardMostMove = 1;
13430                 CopyBoard(boards[1], initial_position);
13431                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13432                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13433                 timeRemaining[0][1] = whiteTimeRemaining;
13434                 timeRemaining[1][1] = blackTimeRemaining;
13435                 if (commentList[0] != NULL) {
13436                     commentList[1] = commentList[0];
13437                     commentList[0] = NULL;
13438                 }
13439             } else {
13440                 currentMove = forwardMostMove = backwardMostMove = 0;
13441             }
13442         }
13443         yyboardindex = forwardMostMove;
13444         cm = (ChessMove) Myylex();
13445     }
13446
13447   if(!creatingBook) {
13448     if (first.pr == NoProc) {
13449         StartChessProgram(&first);
13450     }
13451     InitChessProgram(&first, FALSE);
13452     if(gameInfo.variant == VariantUnknown && *oldName) {
13453         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13454         gameInfo.variant = v;
13455     }
13456     SendToProgram("force\n", &first);
13457     if (startedFromSetupPosition) {
13458         SendBoard(&first, forwardMostMove);
13459     if (appData.debugMode) {
13460         fprintf(debugFP, "Load Game\n");
13461     }
13462         DisplayBothClocks();
13463     }
13464   }
13465
13466     /* [HGM] server: flag to write setup moves in broadcast file as one */
13467     loadFlag = appData.suppressLoadMoves;
13468
13469     while (cm == Comment) {
13470         char *p;
13471         if (appData.debugMode)
13472           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13473         p = yy_text;
13474         AppendComment(currentMove, p, FALSE);
13475         yyboardindex = forwardMostMove;
13476         cm = (ChessMove) Myylex();
13477     }
13478
13479     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13480         cm == WhiteWins || cm == BlackWins ||
13481         cm == GameIsDrawn || cm == GameUnfinished) {
13482         DisplayMessage("", _("No moves in game"));
13483         if (cmailMsgLoaded) {
13484             if (appData.debugMode)
13485               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13486             ClearHighlights();
13487             flipView = FALSE;
13488         }
13489         DrawPosition(FALSE, boards[currentMove]);
13490         DisplayBothClocks();
13491         gameMode = EditGame;
13492         ModeHighlight();
13493         gameFileFP = NULL;
13494         cmailOldMove = 0;
13495         return TRUE;
13496     }
13497
13498     // [HGM] PV info: routine tests if comment empty
13499     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13500         DisplayComment(currentMove - 1, commentList[currentMove]);
13501     }
13502     if (!matchMode && appData.timeDelay != 0)
13503       DrawPosition(FALSE, boards[currentMove]);
13504
13505     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13506       programStats.ok_to_send = 1;
13507     }
13508
13509     /* if the first token after the PGN tags is a move
13510      * and not move number 1, retrieve it from the parser
13511      */
13512     if (cm != MoveNumberOne)
13513         LoadGameOneMove(cm);
13514
13515     /* load the remaining moves from the file */
13516     while (LoadGameOneMove(EndOfFile)) {
13517       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13518       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13519     }
13520
13521     /* rewind to the start of the game */
13522     currentMove = backwardMostMove;
13523
13524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13525
13526     if (oldGameMode == AnalyzeFile) {
13527       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13528       AnalyzeFileEvent();
13529     } else
13530     if (oldGameMode == AnalyzeMode) {
13531       AnalyzeFileEvent();
13532     }
13533
13534     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13535         long int w, b; // [HGM] adjourn: restore saved clock times
13536         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13537         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13538             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13539             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13540         }
13541     }
13542
13543     if(creatingBook) return TRUE;
13544     if (!matchMode && pos > 0) {
13545         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13546     } else
13547     if (matchMode || appData.timeDelay == 0) {
13548       ToEndEvent();
13549     } else if (appData.timeDelay > 0) {
13550       AutoPlayGameLoop();
13551     }
13552
13553     if (appData.debugMode)
13554         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13555
13556     loadFlag = 0; /* [HGM] true game starts */
13557     return TRUE;
13558 }
13559
13560 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13561 int
13562 ReloadPosition (int offset)
13563 {
13564     int positionNumber = lastLoadPositionNumber + offset;
13565     if (lastLoadPositionFP == NULL) {
13566         DisplayError(_("No position has been loaded yet"), 0);
13567         return FALSE;
13568     }
13569     if (positionNumber <= 0) {
13570         DisplayError(_("Can't back up any further"), 0);
13571         return FALSE;
13572     }
13573     return LoadPosition(lastLoadPositionFP, positionNumber,
13574                         lastLoadPositionTitle);
13575 }
13576
13577 /* Load the nth position from the given file */
13578 int
13579 LoadPositionFromFile (char *filename, int n, char *title)
13580 {
13581     FILE *f;
13582     char buf[MSG_SIZ];
13583
13584     if (strcmp(filename, "-") == 0) {
13585         return LoadPosition(stdin, n, "stdin");
13586     } else {
13587         f = fopen(filename, "rb");
13588         if (f == NULL) {
13589             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13590             DisplayError(buf, errno);
13591             return FALSE;
13592         } else {
13593             return LoadPosition(f, n, title);
13594         }
13595     }
13596 }
13597
13598 /* Load the nth position from the given open file, and close it */
13599 int
13600 LoadPosition (FILE *f, int positionNumber, char *title)
13601 {
13602     char *p, line[MSG_SIZ];
13603     Board initial_position;
13604     int i, j, fenMode, pn;
13605
13606     if (gameMode == Training )
13607         SetTrainingModeOff();
13608
13609     if (gameMode != BeginningOfGame) {
13610         Reset(FALSE, TRUE);
13611     }
13612     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13613         fclose(lastLoadPositionFP);
13614     }
13615     if (positionNumber == 0) positionNumber = 1;
13616     lastLoadPositionFP = f;
13617     lastLoadPositionNumber = positionNumber;
13618     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13619     if (first.pr == NoProc && !appData.noChessProgram) {
13620       StartChessProgram(&first);
13621       InitChessProgram(&first, FALSE);
13622     }
13623     pn = positionNumber;
13624     if (positionNumber < 0) {
13625         /* Negative position number means to seek to that byte offset */
13626         if (fseek(f, -positionNumber, 0) == -1) {
13627             DisplayError(_("Can't seek on position file"), 0);
13628             return FALSE;
13629         };
13630         pn = 1;
13631     } else {
13632         if (fseek(f, 0, 0) == -1) {
13633             if (f == lastLoadPositionFP ?
13634                 positionNumber == lastLoadPositionNumber + 1 :
13635                 positionNumber == 1) {
13636                 pn = 1;
13637             } else {
13638                 DisplayError(_("Can't seek on position file"), 0);
13639                 return FALSE;
13640             }
13641         }
13642     }
13643     /* See if this file is FEN or old-style xboard */
13644     if (fgets(line, MSG_SIZ, f) == NULL) {
13645         DisplayError(_("Position not found in file"), 0);
13646         return FALSE;
13647     }
13648     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13649     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13650
13651     if (pn >= 2) {
13652         if (fenMode || line[0] == '#') pn--;
13653         while (pn > 0) {
13654             /* skip positions before number pn */
13655             if (fgets(line, MSG_SIZ, f) == NULL) {
13656                 Reset(TRUE, TRUE);
13657                 DisplayError(_("Position not found in file"), 0);
13658                 return FALSE;
13659             }
13660             if (fenMode || line[0] == '#') pn--;
13661         }
13662     }
13663
13664     if (fenMode) {
13665         char *p;
13666         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13667             DisplayError(_("Bad FEN position in file"), 0);
13668             return FALSE;
13669         }
13670         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13671             sscanf(p+4, "%[^;]", bestMove);
13672         } else *bestMove = NULLCHAR;
13673         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13674             sscanf(p+4, "%[^;]", avoidMove);
13675         } else *avoidMove = NULLCHAR;
13676     } else {
13677         (void) fgets(line, MSG_SIZ, f);
13678         (void) fgets(line, MSG_SIZ, f);
13679
13680         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13681             (void) fgets(line, MSG_SIZ, f);
13682             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13683                 if (*p == ' ')
13684                   continue;
13685                 initial_position[i][j++] = CharToPiece(*p);
13686             }
13687         }
13688
13689         blackPlaysFirst = FALSE;
13690         if (!feof(f)) {
13691             (void) fgets(line, MSG_SIZ, f);
13692             if (strncmp(line, "black", strlen("black"))==0)
13693               blackPlaysFirst = TRUE;
13694         }
13695     }
13696     startedFromSetupPosition = TRUE;
13697
13698     CopyBoard(boards[0], initial_position);
13699     if (blackPlaysFirst) {
13700         currentMove = forwardMostMove = backwardMostMove = 1;
13701         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13702         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13703         CopyBoard(boards[1], initial_position);
13704         DisplayMessage("", _("Black to play"));
13705     } else {
13706         currentMove = forwardMostMove = backwardMostMove = 0;
13707         DisplayMessage("", _("White to play"));
13708     }
13709     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13710     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13711         SendToProgram("force\n", &first);
13712         SendBoard(&first, forwardMostMove);
13713     }
13714     if (appData.debugMode) {
13715 int i, j;
13716   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13717   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13718         fprintf(debugFP, "Load Position\n");
13719     }
13720
13721     if (positionNumber > 1) {
13722       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13723         DisplayTitle(line);
13724     } else {
13725         DisplayTitle(title);
13726     }
13727     gameMode = EditGame;
13728     ModeHighlight();
13729     ResetClocks();
13730     timeRemaining[0][1] = whiteTimeRemaining;
13731     timeRemaining[1][1] = blackTimeRemaining;
13732     DrawPosition(FALSE, boards[currentMove]);
13733
13734     return TRUE;
13735 }
13736
13737
13738 void
13739 CopyPlayerNameIntoFileName (char **dest, char *src)
13740 {
13741     while (*src != NULLCHAR && *src != ',') {
13742         if (*src == ' ') {
13743             *(*dest)++ = '_';
13744             src++;
13745         } else {
13746             *(*dest)++ = *src++;
13747         }
13748     }
13749 }
13750
13751 char *
13752 DefaultFileName (char *ext)
13753 {
13754     static char def[MSG_SIZ];
13755     char *p;
13756
13757     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13758         p = def;
13759         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13760         *p++ = '-';
13761         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13762         *p++ = '.';
13763         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13764     } else {
13765         def[0] = NULLCHAR;
13766     }
13767     return def;
13768 }
13769
13770 /* Save the current game to the given file */
13771 int
13772 SaveGameToFile (char *filename, int append)
13773 {
13774     FILE *f;
13775     char buf[MSG_SIZ];
13776     int result, i, t,tot=0;
13777
13778     if (strcmp(filename, "-") == 0) {
13779         return SaveGame(stdout, 0, NULL);
13780     } else {
13781         for(i=0; i<10; i++) { // upto 10 tries
13782              f = fopen(filename, append ? "a" : "w");
13783              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13784              if(f || errno != 13) break;
13785              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13786              tot += t;
13787         }
13788         if (f == NULL) {
13789             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13790             DisplayError(buf, errno);
13791             return FALSE;
13792         } else {
13793             safeStrCpy(buf, lastMsg, MSG_SIZ);
13794             DisplayMessage(_("Waiting for access to save file"), "");
13795             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13796             DisplayMessage(_("Saving game"), "");
13797             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13798             result = SaveGame(f, 0, NULL);
13799             DisplayMessage(buf, "");
13800             return result;
13801         }
13802     }
13803 }
13804
13805 char *
13806 SavePart (char *str)
13807 {
13808     static char buf[MSG_SIZ];
13809     char *p;
13810
13811     p = strchr(str, ' ');
13812     if (p == NULL) return str;
13813     strncpy(buf, str, p - str);
13814     buf[p - str] = NULLCHAR;
13815     return buf;
13816 }
13817
13818 #define PGN_MAX_LINE 75
13819
13820 #define PGN_SIDE_WHITE  0
13821 #define PGN_SIDE_BLACK  1
13822
13823 static int
13824 FindFirstMoveOutOfBook (int side)
13825 {
13826     int result = -1;
13827
13828     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13829         int index = backwardMostMove;
13830         int has_book_hit = 0;
13831
13832         if( (index % 2) != side ) {
13833             index++;
13834         }
13835
13836         while( index < forwardMostMove ) {
13837             /* Check to see if engine is in book */
13838             int depth = pvInfoList[index].depth;
13839             int score = pvInfoList[index].score;
13840             int in_book = 0;
13841
13842             if( depth <= 2 ) {
13843                 in_book = 1;
13844             }
13845             else if( score == 0 && depth == 63 ) {
13846                 in_book = 1; /* Zappa */
13847             }
13848             else if( score == 2 && depth == 99 ) {
13849                 in_book = 1; /* Abrok */
13850             }
13851
13852             has_book_hit += in_book;
13853
13854             if( ! in_book ) {
13855                 result = index;
13856
13857                 break;
13858             }
13859
13860             index += 2;
13861         }
13862     }
13863
13864     return result;
13865 }
13866
13867 void
13868 GetOutOfBookInfo (char * buf)
13869 {
13870     int oob[2];
13871     int i;
13872     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13873
13874     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13875     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13876
13877     *buf = '\0';
13878
13879     if( oob[0] >= 0 || oob[1] >= 0 ) {
13880         for( i=0; i<2; i++ ) {
13881             int idx = oob[i];
13882
13883             if( idx >= 0 ) {
13884                 if( i > 0 && oob[0] >= 0 ) {
13885                     strcat( buf, "   " );
13886                 }
13887
13888                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13889                 sprintf( buf+strlen(buf), "%s%.2f",
13890                     pvInfoList[idx].score >= 0 ? "+" : "",
13891                     pvInfoList[idx].score / 100.0 );
13892             }
13893         }
13894     }
13895 }
13896
13897 /* Save game in PGN style */
13898 static void
13899 SaveGamePGN2 (FILE *f)
13900 {
13901     int i, offset, linelen, newblock;
13902 //    char *movetext;
13903     char numtext[32];
13904     int movelen, numlen, blank;
13905     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13906
13907     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13908
13909     PrintPGNTags(f, &gameInfo);
13910
13911     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13912
13913     if (backwardMostMove > 0 || startedFromSetupPosition) {
13914         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13915         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13916         fprintf(f, "\n{--------------\n");
13917         PrintPosition(f, backwardMostMove);
13918         fprintf(f, "--------------}\n");
13919         free(fen);
13920     }
13921     else {
13922         /* [AS] Out of book annotation */
13923         if( appData.saveOutOfBookInfo ) {
13924             char buf[64];
13925
13926             GetOutOfBookInfo( buf );
13927
13928             if( buf[0] != '\0' ) {
13929                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13930             }
13931         }
13932
13933         fprintf(f, "\n");
13934     }
13935
13936     i = backwardMostMove;
13937     linelen = 0;
13938     newblock = TRUE;
13939
13940     while (i < forwardMostMove) {
13941         /* Print comments preceding this move */
13942         if (commentList[i] != NULL) {
13943             if (linelen > 0) fprintf(f, "\n");
13944             fprintf(f, "%s", commentList[i]);
13945             linelen = 0;
13946             newblock = TRUE;
13947         }
13948
13949         /* Format move number */
13950         if ((i % 2) == 0)
13951           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13952         else
13953           if (newblock)
13954             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13955           else
13956             numtext[0] = NULLCHAR;
13957
13958         numlen = strlen(numtext);
13959         newblock = FALSE;
13960
13961         /* Print move number */
13962         blank = linelen > 0 && numlen > 0;
13963         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13964             fprintf(f, "\n");
13965             linelen = 0;
13966             blank = 0;
13967         }
13968         if (blank) {
13969             fprintf(f, " ");
13970             linelen++;
13971         }
13972         fprintf(f, "%s", numtext);
13973         linelen += numlen;
13974
13975         /* Get move */
13976         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13977         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13978
13979         /* Print move */
13980         blank = linelen > 0 && movelen > 0;
13981         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13982             fprintf(f, "\n");
13983             linelen = 0;
13984             blank = 0;
13985         }
13986         if (blank) {
13987             fprintf(f, " ");
13988             linelen++;
13989         }
13990         fprintf(f, "%s", move_buffer);
13991         linelen += movelen;
13992
13993         /* [AS] Add PV info if present */
13994         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13995             /* [HGM] add time */
13996             char buf[MSG_SIZ]; int seconds;
13997
13998             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13999
14000             if( seconds <= 0)
14001               buf[0] = 0;
14002             else
14003               if( seconds < 30 )
14004                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14005               else
14006                 {
14007                   seconds = (seconds + 4)/10; // round to full seconds
14008                   if( seconds < 60 )
14009                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14010                   else
14011                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14012                 }
14013
14014             if(appData.cumulativeTimePGN) {
14015                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14016             }
14017
14018             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14019                       pvInfoList[i].score >= 0 ? "+" : "",
14020                       pvInfoList[i].score / 100.0,
14021                       pvInfoList[i].depth,
14022                       buf );
14023
14024             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14025
14026             /* Print score/depth */
14027             blank = linelen > 0 && movelen > 0;
14028             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14029                 fprintf(f, "\n");
14030                 linelen = 0;
14031                 blank = 0;
14032             }
14033             if (blank) {
14034                 fprintf(f, " ");
14035                 linelen++;
14036             }
14037             fprintf(f, "%s", move_buffer);
14038             linelen += movelen;
14039         }
14040
14041         i++;
14042     }
14043
14044     /* Start a new line */
14045     if (linelen > 0) fprintf(f, "\n");
14046
14047     /* Print comments after last move */
14048     if (commentList[i] != NULL) {
14049         fprintf(f, "%s\n", commentList[i]);
14050     }
14051
14052     /* Print result */
14053     if (gameInfo.resultDetails != NULL &&
14054         gameInfo.resultDetails[0] != NULLCHAR) {
14055         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14056         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14057            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14058             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14059         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14060     } else {
14061         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14062     }
14063 }
14064
14065 /* Save game in PGN style and close the file */
14066 int
14067 SaveGamePGN (FILE *f)
14068 {
14069     SaveGamePGN2(f);
14070     fclose(f);
14071     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14072     return TRUE;
14073 }
14074
14075 /* Save game in old style and close the file */
14076 int
14077 SaveGameOldStyle (FILE *f)
14078 {
14079     int i, offset;
14080     time_t tm;
14081
14082     tm = time((time_t *) NULL);
14083
14084     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14085     PrintOpponents(f);
14086
14087     if (backwardMostMove > 0 || startedFromSetupPosition) {
14088         fprintf(f, "\n[--------------\n");
14089         PrintPosition(f, backwardMostMove);
14090         fprintf(f, "--------------]\n");
14091     } else {
14092         fprintf(f, "\n");
14093     }
14094
14095     i = backwardMostMove;
14096     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14097
14098     while (i < forwardMostMove) {
14099         if (commentList[i] != NULL) {
14100             fprintf(f, "[%s]\n", commentList[i]);
14101         }
14102
14103         if ((i % 2) == 1) {
14104             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14105             i++;
14106         } else {
14107             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14108             i++;
14109             if (commentList[i] != NULL) {
14110                 fprintf(f, "\n");
14111                 continue;
14112             }
14113             if (i >= forwardMostMove) {
14114                 fprintf(f, "\n");
14115                 break;
14116             }
14117             fprintf(f, "%s\n", parseList[i]);
14118             i++;
14119         }
14120     }
14121
14122     if (commentList[i] != NULL) {
14123         fprintf(f, "[%s]\n", commentList[i]);
14124     }
14125
14126     /* This isn't really the old style, but it's close enough */
14127     if (gameInfo.resultDetails != NULL &&
14128         gameInfo.resultDetails[0] != NULLCHAR) {
14129         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14130                 gameInfo.resultDetails);
14131     } else {
14132         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14133     }
14134
14135     fclose(f);
14136     return TRUE;
14137 }
14138
14139 /* Save the current game to open file f and close the file */
14140 int
14141 SaveGame (FILE *f, int dummy, char *dummy2)
14142 {
14143     if (gameMode == EditPosition) EditPositionDone(TRUE);
14144     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14145     if (appData.oldSaveStyle)
14146       return SaveGameOldStyle(f);
14147     else
14148       return SaveGamePGN(f);
14149 }
14150
14151 /* Save the current position to the given file */
14152 int
14153 SavePositionToFile (char *filename)
14154 {
14155     FILE *f;
14156     char buf[MSG_SIZ];
14157
14158     if (strcmp(filename, "-") == 0) {
14159         return SavePosition(stdout, 0, NULL);
14160     } else {
14161         f = fopen(filename, "a");
14162         if (f == NULL) {
14163             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14164             DisplayError(buf, errno);
14165             return FALSE;
14166         } else {
14167             safeStrCpy(buf, lastMsg, MSG_SIZ);
14168             DisplayMessage(_("Waiting for access to save file"), "");
14169             flock(fileno(f), LOCK_EX); // [HGM] lock
14170             DisplayMessage(_("Saving position"), "");
14171             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14172             SavePosition(f, 0, NULL);
14173             DisplayMessage(buf, "");
14174             return TRUE;
14175         }
14176     }
14177 }
14178
14179 /* Save the current position to the given open file and close the file */
14180 int
14181 SavePosition (FILE *f, int dummy, char *dummy2)
14182 {
14183     time_t tm;
14184     char *fen;
14185
14186     if (gameMode == EditPosition) EditPositionDone(TRUE);
14187     if (appData.oldSaveStyle) {
14188         tm = time((time_t *) NULL);
14189
14190         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14191         PrintOpponents(f);
14192         fprintf(f, "[--------------\n");
14193         PrintPosition(f, currentMove);
14194         fprintf(f, "--------------]\n");
14195     } else {
14196         fen = PositionToFEN(currentMove, NULL, 1);
14197         fprintf(f, "%s\n", fen);
14198         free(fen);
14199     }
14200     fclose(f);
14201     return TRUE;
14202 }
14203
14204 void
14205 ReloadCmailMsgEvent (int unregister)
14206 {
14207 #if !WIN32
14208     static char *inFilename = NULL;
14209     static char *outFilename;
14210     int i;
14211     struct stat inbuf, outbuf;
14212     int status;
14213
14214     /* Any registered moves are unregistered if unregister is set, */
14215     /* i.e. invoked by the signal handler */
14216     if (unregister) {
14217         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14218             cmailMoveRegistered[i] = FALSE;
14219             if (cmailCommentList[i] != NULL) {
14220                 free(cmailCommentList[i]);
14221                 cmailCommentList[i] = NULL;
14222             }
14223         }
14224         nCmailMovesRegistered = 0;
14225     }
14226
14227     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14228         cmailResult[i] = CMAIL_NOT_RESULT;
14229     }
14230     nCmailResults = 0;
14231
14232     if (inFilename == NULL) {
14233         /* Because the filenames are static they only get malloced once  */
14234         /* and they never get freed                                      */
14235         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14236         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14237
14238         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14239         sprintf(outFilename, "%s.out", appData.cmailGameName);
14240     }
14241
14242     status = stat(outFilename, &outbuf);
14243     if (status < 0) {
14244         cmailMailedMove = FALSE;
14245     } else {
14246         status = stat(inFilename, &inbuf);
14247         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14248     }
14249
14250     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14251        counts the games, notes how each one terminated, etc.
14252
14253        It would be nice to remove this kludge and instead gather all
14254        the information while building the game list.  (And to keep it
14255        in the game list nodes instead of having a bunch of fixed-size
14256        parallel arrays.)  Note this will require getting each game's
14257        termination from the PGN tags, as the game list builder does
14258        not process the game moves.  --mann
14259        */
14260     cmailMsgLoaded = TRUE;
14261     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14262
14263     /* Load first game in the file or popup game menu */
14264     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14265
14266 #endif /* !WIN32 */
14267     return;
14268 }
14269
14270 int
14271 RegisterMove ()
14272 {
14273     FILE *f;
14274     char string[MSG_SIZ];
14275
14276     if (   cmailMailedMove
14277         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14278         return TRUE;            /* Allow free viewing  */
14279     }
14280
14281     /* Unregister move to ensure that we don't leave RegisterMove        */
14282     /* with the move registered when the conditions for registering no   */
14283     /* longer hold                                                       */
14284     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14285         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14286         nCmailMovesRegistered --;
14287
14288         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14289           {
14290               free(cmailCommentList[lastLoadGameNumber - 1]);
14291               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14292           }
14293     }
14294
14295     if (cmailOldMove == -1) {
14296         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14297         return FALSE;
14298     }
14299
14300     if (currentMove > cmailOldMove + 1) {
14301         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14302         return FALSE;
14303     }
14304
14305     if (currentMove < cmailOldMove) {
14306         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14307         return FALSE;
14308     }
14309
14310     if (forwardMostMove > currentMove) {
14311         /* Silently truncate extra moves */
14312         TruncateGame();
14313     }
14314
14315     if (   (currentMove == cmailOldMove + 1)
14316         || (   (currentMove == cmailOldMove)
14317             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14318                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14319         if (gameInfo.result != GameUnfinished) {
14320             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14321         }
14322
14323         if (commentList[currentMove] != NULL) {
14324             cmailCommentList[lastLoadGameNumber - 1]
14325               = StrSave(commentList[currentMove]);
14326         }
14327         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14328
14329         if (appData.debugMode)
14330           fprintf(debugFP, "Saving %s for game %d\n",
14331                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14332
14333         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14334
14335         f = fopen(string, "w");
14336         if (appData.oldSaveStyle) {
14337             SaveGameOldStyle(f); /* also closes the file */
14338
14339             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14340             f = fopen(string, "w");
14341             SavePosition(f, 0, NULL); /* also closes the file */
14342         } else {
14343             fprintf(f, "{--------------\n");
14344             PrintPosition(f, currentMove);
14345             fprintf(f, "--------------}\n\n");
14346
14347             SaveGame(f, 0, NULL); /* also closes the file*/
14348         }
14349
14350         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14351         nCmailMovesRegistered ++;
14352     } else if (nCmailGames == 1) {
14353         DisplayError(_("You have not made a move yet"), 0);
14354         return FALSE;
14355     }
14356
14357     return TRUE;
14358 }
14359
14360 void
14361 MailMoveEvent ()
14362 {
14363 #if !WIN32
14364     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14365     FILE *commandOutput;
14366     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14367     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14368     int nBuffers;
14369     int i;
14370     int archived;
14371     char *arcDir;
14372
14373     if (! cmailMsgLoaded) {
14374         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14375         return;
14376     }
14377
14378     if (nCmailGames == nCmailResults) {
14379         DisplayError(_("No unfinished games"), 0);
14380         return;
14381     }
14382
14383 #if CMAIL_PROHIBIT_REMAIL
14384     if (cmailMailedMove) {
14385       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);
14386         DisplayError(msg, 0);
14387         return;
14388     }
14389 #endif
14390
14391     if (! (cmailMailedMove || RegisterMove())) return;
14392
14393     if (   cmailMailedMove
14394         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14395       snprintf(string, MSG_SIZ, partCommandString,
14396                appData.debugMode ? " -v" : "", appData.cmailGameName);
14397         commandOutput = popen(string, "r");
14398
14399         if (commandOutput == NULL) {
14400             DisplayError(_("Failed to invoke cmail"), 0);
14401         } else {
14402             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14403                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14404             }
14405             if (nBuffers > 1) {
14406                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14407                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14408                 nBytes = MSG_SIZ - 1;
14409             } else {
14410                 (void) memcpy(msg, buffer, nBytes);
14411             }
14412             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14413
14414             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14415                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14416
14417                 archived = TRUE;
14418                 for (i = 0; i < nCmailGames; i ++) {
14419                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14420                         archived = FALSE;
14421                     }
14422                 }
14423                 if (   archived
14424                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14425                         != NULL)) {
14426                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14427                            arcDir,
14428                            appData.cmailGameName,
14429                            gameInfo.date);
14430                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14431                     cmailMsgLoaded = FALSE;
14432                 }
14433             }
14434
14435             DisplayInformation(msg);
14436             pclose(commandOutput);
14437         }
14438     } else {
14439         if ((*cmailMsg) != '\0') {
14440             DisplayInformation(cmailMsg);
14441         }
14442     }
14443
14444     return;
14445 #endif /* !WIN32 */
14446 }
14447
14448 char *
14449 CmailMsg ()
14450 {
14451 #if WIN32
14452     return NULL;
14453 #else
14454     int  prependComma = 0;
14455     char number[5];
14456     char string[MSG_SIZ];       /* Space for game-list */
14457     int  i;
14458
14459     if (!cmailMsgLoaded) return "";
14460
14461     if (cmailMailedMove) {
14462       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14463     } else {
14464         /* Create a list of games left */
14465       snprintf(string, MSG_SIZ, "[");
14466         for (i = 0; i < nCmailGames; i ++) {
14467             if (! (   cmailMoveRegistered[i]
14468                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14469                 if (prependComma) {
14470                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14471                 } else {
14472                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14473                     prependComma = 1;
14474                 }
14475
14476                 strcat(string, number);
14477             }
14478         }
14479         strcat(string, "]");
14480
14481         if (nCmailMovesRegistered + nCmailResults == 0) {
14482             switch (nCmailGames) {
14483               case 1:
14484                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14485                 break;
14486
14487               case 2:
14488                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14489                 break;
14490
14491               default:
14492                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14493                          nCmailGames);
14494                 break;
14495             }
14496         } else {
14497             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14498               case 1:
14499                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14500                          string);
14501                 break;
14502
14503               case 0:
14504                 if (nCmailResults == nCmailGames) {
14505                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14506                 } else {
14507                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14508                 }
14509                 break;
14510
14511               default:
14512                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14513                          string);
14514             }
14515         }
14516     }
14517     return cmailMsg;
14518 #endif /* WIN32 */
14519 }
14520
14521 void
14522 ResetGameEvent ()
14523 {
14524     if (gameMode == Training)
14525       SetTrainingModeOff();
14526
14527     Reset(TRUE, TRUE);
14528     cmailMsgLoaded = FALSE;
14529     if (appData.icsActive) {
14530       SendToICS(ics_prefix);
14531       SendToICS("refresh\n");
14532     }
14533 }
14534
14535 void
14536 ExitEvent (int status)
14537 {
14538     exiting++;
14539     if (exiting > 2) {
14540       /* Give up on clean exit */
14541       exit(status);
14542     }
14543     if (exiting > 1) {
14544       /* Keep trying for clean exit */
14545       return;
14546     }
14547
14548     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14549     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14550
14551     if (telnetISR != NULL) {
14552       RemoveInputSource(telnetISR);
14553     }
14554     if (icsPR != NoProc) {
14555       DestroyChildProcess(icsPR, TRUE);
14556     }
14557
14558     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14559     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14560
14561     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14562     /* make sure this other one finishes before killing it!                  */
14563     if(endingGame) { int count = 0;
14564         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14565         while(endingGame && count++ < 10) DoSleep(1);
14566         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14567     }
14568
14569     /* Kill off chess programs */
14570     if (first.pr != NoProc) {
14571         ExitAnalyzeMode();
14572
14573         DoSleep( appData.delayBeforeQuit );
14574         SendToProgram("quit\n", &first);
14575         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14576     }
14577     if (second.pr != NoProc) {
14578         DoSleep( appData.delayBeforeQuit );
14579         SendToProgram("quit\n", &second);
14580         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14581     }
14582     if (first.isr != NULL) {
14583         RemoveInputSource(first.isr);
14584     }
14585     if (second.isr != NULL) {
14586         RemoveInputSource(second.isr);
14587     }
14588
14589     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14590     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14591
14592     ShutDownFrontEnd();
14593     exit(status);
14594 }
14595
14596 void
14597 PauseEngine (ChessProgramState *cps)
14598 {
14599     SendToProgram("pause\n", cps);
14600     cps->pause = 2;
14601 }
14602
14603 void
14604 UnPauseEngine (ChessProgramState *cps)
14605 {
14606     SendToProgram("resume\n", cps);
14607     cps->pause = 1;
14608 }
14609
14610 void
14611 PauseEvent ()
14612 {
14613     if (appData.debugMode)
14614         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14615     if (pausing) {
14616         pausing = FALSE;
14617         ModeHighlight();
14618         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14619             StartClocks();
14620             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14621                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14622                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14623             }
14624             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14625             HandleMachineMove(stashedInputMove, stalledEngine);
14626             stalledEngine = NULL;
14627             return;
14628         }
14629         if (gameMode == MachinePlaysWhite ||
14630             gameMode == TwoMachinesPlay   ||
14631             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14632             if(first.pause)  UnPauseEngine(&first);
14633             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14634             if(second.pause) UnPauseEngine(&second);
14635             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14636             StartClocks();
14637         } else {
14638             DisplayBothClocks();
14639         }
14640         if (gameMode == PlayFromGameFile) {
14641             if (appData.timeDelay >= 0)
14642                 AutoPlayGameLoop();
14643         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14644             Reset(FALSE, TRUE);
14645             SendToICS(ics_prefix);
14646             SendToICS("refresh\n");
14647         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14648             ForwardInner(forwardMostMove);
14649         }
14650         pauseExamInvalid = FALSE;
14651     } else {
14652         switch (gameMode) {
14653           default:
14654             return;
14655           case IcsExamining:
14656             pauseExamForwardMostMove = forwardMostMove;
14657             pauseExamInvalid = FALSE;
14658             /* fall through */
14659           case IcsObserving:
14660           case IcsPlayingWhite:
14661           case IcsPlayingBlack:
14662             pausing = TRUE;
14663             ModeHighlight();
14664             return;
14665           case PlayFromGameFile:
14666             (void) StopLoadGameTimer();
14667             pausing = TRUE;
14668             ModeHighlight();
14669             break;
14670           case BeginningOfGame:
14671             if (appData.icsActive) return;
14672             /* else fall through */
14673           case MachinePlaysWhite:
14674           case MachinePlaysBlack:
14675           case TwoMachinesPlay:
14676             if (forwardMostMove == 0)
14677               return;           /* don't pause if no one has moved */
14678             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14679                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14680                 if(onMove->pause) {           // thinking engine can be paused
14681                     PauseEngine(onMove);      // do it
14682                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14683                         PauseEngine(onMove->other);
14684                     else
14685                         SendToProgram("easy\n", onMove->other);
14686                     StopClocks();
14687                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14688             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14689                 if(first.pause) {
14690                     PauseEngine(&first);
14691                     StopClocks();
14692                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14693             } else { // human on move, pause pondering by either method
14694                 if(first.pause)
14695                     PauseEngine(&first);
14696                 else if(appData.ponderNextMove)
14697                     SendToProgram("easy\n", &first);
14698                 StopClocks();
14699             }
14700             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14701           case AnalyzeMode:
14702             pausing = TRUE;
14703             ModeHighlight();
14704             break;
14705         }
14706     }
14707 }
14708
14709 void
14710 EditCommentEvent ()
14711 {
14712     char title[MSG_SIZ];
14713
14714     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14715       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14716     } else {
14717       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14718                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14719                parseList[currentMove - 1]);
14720     }
14721
14722     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14723 }
14724
14725
14726 void
14727 EditTagsEvent ()
14728 {
14729     char *tags = PGNTags(&gameInfo);
14730     bookUp = FALSE;
14731     EditTagsPopUp(tags, NULL);
14732     free(tags);
14733 }
14734
14735 void
14736 ToggleSecond ()
14737 {
14738   if(second.analyzing) {
14739     SendToProgram("exit\n", &second);
14740     second.analyzing = FALSE;
14741   } else {
14742     if (second.pr == NoProc) StartChessProgram(&second);
14743     InitChessProgram(&second, FALSE);
14744     FeedMovesToProgram(&second, currentMove);
14745
14746     SendToProgram("analyze\n", &second);
14747     second.analyzing = TRUE;
14748   }
14749 }
14750
14751 /* Toggle ShowThinking */
14752 void
14753 ToggleShowThinking()
14754 {
14755   appData.showThinking = !appData.showThinking;
14756   ShowThinkingEvent();
14757 }
14758
14759 int
14760 AnalyzeModeEvent ()
14761 {
14762     char buf[MSG_SIZ];
14763
14764     if (!first.analysisSupport) {
14765       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14766       DisplayError(buf, 0);
14767       return 0;
14768     }
14769     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14770     if (appData.icsActive) {
14771         if (gameMode != IcsObserving) {
14772           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14773             DisplayError(buf, 0);
14774             /* secure check */
14775             if (appData.icsEngineAnalyze) {
14776                 if (appData.debugMode)
14777                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14778                 ExitAnalyzeMode();
14779                 ModeHighlight();
14780             }
14781             return 0;
14782         }
14783         /* if enable, user wants to disable icsEngineAnalyze */
14784         if (appData.icsEngineAnalyze) {
14785                 ExitAnalyzeMode();
14786                 ModeHighlight();
14787                 return 0;
14788         }
14789         appData.icsEngineAnalyze = TRUE;
14790         if (appData.debugMode)
14791             fprintf(debugFP, "ICS engine analyze starting... \n");
14792     }
14793
14794     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14795     if (appData.noChessProgram || gameMode == AnalyzeMode)
14796       return 0;
14797
14798     if (gameMode != AnalyzeFile) {
14799         if (!appData.icsEngineAnalyze) {
14800                EditGameEvent();
14801                if (gameMode != EditGame) return 0;
14802         }
14803         if (!appData.showThinking) ToggleShowThinking();
14804         ResurrectChessProgram();
14805         SendToProgram("analyze\n", &first);
14806         first.analyzing = TRUE;
14807         /*first.maybeThinking = TRUE;*/
14808         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14809         EngineOutputPopUp();
14810     }
14811     if (!appData.icsEngineAnalyze) {
14812         gameMode = AnalyzeMode;
14813         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14814     }
14815     pausing = FALSE;
14816     ModeHighlight();
14817     SetGameInfo();
14818
14819     StartAnalysisClock();
14820     GetTimeMark(&lastNodeCountTime);
14821     lastNodeCount = 0;
14822     return 1;
14823 }
14824
14825 void
14826 AnalyzeFileEvent ()
14827 {
14828     if (appData.noChessProgram || gameMode == AnalyzeFile)
14829       return;
14830
14831     if (!first.analysisSupport) {
14832       char buf[MSG_SIZ];
14833       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14834       DisplayError(buf, 0);
14835       return;
14836     }
14837
14838     if (gameMode != AnalyzeMode) {
14839         keepInfo = 1; // mere annotating should not alter PGN tags
14840         EditGameEvent();
14841         keepInfo = 0;
14842         if (gameMode != EditGame) return;
14843         if (!appData.showThinking) ToggleShowThinking();
14844         ResurrectChessProgram();
14845         SendToProgram("analyze\n", &first);
14846         first.analyzing = TRUE;
14847         /*first.maybeThinking = TRUE;*/
14848         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14849         EngineOutputPopUp();
14850     }
14851     gameMode = AnalyzeFile;
14852     pausing = FALSE;
14853     ModeHighlight();
14854
14855     StartAnalysisClock();
14856     GetTimeMark(&lastNodeCountTime);
14857     lastNodeCount = 0;
14858     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14859     AnalysisPeriodicEvent(1);
14860 }
14861
14862 void
14863 MachineWhiteEvent ()
14864 {
14865     char buf[MSG_SIZ];
14866     char *bookHit = NULL;
14867
14868     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14869       return;
14870
14871
14872     if (gameMode == PlayFromGameFile ||
14873         gameMode == TwoMachinesPlay  ||
14874         gameMode == Training         ||
14875         gameMode == AnalyzeMode      ||
14876         gameMode == EndOfGame)
14877         EditGameEvent();
14878
14879     if (gameMode == EditPosition)
14880         EditPositionDone(TRUE);
14881
14882     if (!WhiteOnMove(currentMove)) {
14883         DisplayError(_("It is not White's turn"), 0);
14884         return;
14885     }
14886
14887     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14888       ExitAnalyzeMode();
14889
14890     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14891         gameMode == AnalyzeFile)
14892         TruncateGame();
14893
14894     ResurrectChessProgram();    /* in case it isn't running */
14895     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14896         gameMode = MachinePlaysWhite;
14897         ResetClocks();
14898     } else
14899     gameMode = MachinePlaysWhite;
14900     pausing = FALSE;
14901     ModeHighlight();
14902     SetGameInfo();
14903     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14904     DisplayTitle(buf);
14905     if (first.sendName) {
14906       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14907       SendToProgram(buf, &first);
14908     }
14909     if (first.sendTime) {
14910       if (first.useColors) {
14911         SendToProgram("black\n", &first); /*gnu kludge*/
14912       }
14913       SendTimeRemaining(&first, TRUE);
14914     }
14915     if (first.useColors) {
14916       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14917     }
14918     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14919     SetMachineThinkingEnables();
14920     first.maybeThinking = TRUE;
14921     StartClocks();
14922     firstMove = FALSE;
14923
14924     if (appData.autoFlipView && !flipView) {
14925       flipView = !flipView;
14926       DrawPosition(FALSE, NULL);
14927       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14928     }
14929
14930     if(bookHit) { // [HGM] book: simulate book reply
14931         static char bookMove[MSG_SIZ]; // a bit generous?
14932
14933         programStats.nodes = programStats.depth = programStats.time =
14934         programStats.score = programStats.got_only_move = 0;
14935         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14936
14937         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14938         strcat(bookMove, bookHit);
14939         savedMessage = bookMove; // args for deferred call
14940         savedState = &first;
14941         ScheduleDelayedEvent(DeferredBookMove, 1);
14942     }
14943 }
14944
14945 void
14946 MachineBlackEvent ()
14947 {
14948   char buf[MSG_SIZ];
14949   char *bookHit = NULL;
14950
14951     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14952         return;
14953
14954
14955     if (gameMode == PlayFromGameFile ||
14956         gameMode == TwoMachinesPlay  ||
14957         gameMode == Training         ||
14958         gameMode == AnalyzeMode      ||
14959         gameMode == EndOfGame)
14960         EditGameEvent();
14961
14962     if (gameMode == EditPosition)
14963         EditPositionDone(TRUE);
14964
14965     if (WhiteOnMove(currentMove)) {
14966         DisplayError(_("It is not Black's turn"), 0);
14967         return;
14968     }
14969
14970     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14971       ExitAnalyzeMode();
14972
14973     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14974         gameMode == AnalyzeFile)
14975         TruncateGame();
14976
14977     ResurrectChessProgram();    /* in case it isn't running */
14978     gameMode = MachinePlaysBlack;
14979     pausing = FALSE;
14980     ModeHighlight();
14981     SetGameInfo();
14982     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14983     DisplayTitle(buf);
14984     if (first.sendName) {
14985       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14986       SendToProgram(buf, &first);
14987     }
14988     if (first.sendTime) {
14989       if (first.useColors) {
14990         SendToProgram("white\n", &first); /*gnu kludge*/
14991       }
14992       SendTimeRemaining(&first, FALSE);
14993     }
14994     if (first.useColors) {
14995       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14996     }
14997     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14998     SetMachineThinkingEnables();
14999     first.maybeThinking = TRUE;
15000     StartClocks();
15001
15002     if (appData.autoFlipView && flipView) {
15003       flipView = !flipView;
15004       DrawPosition(FALSE, NULL);
15005       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15006     }
15007     if(bookHit) { // [HGM] book: simulate book reply
15008         static char bookMove[MSG_SIZ]; // a bit generous?
15009
15010         programStats.nodes = programStats.depth = programStats.time =
15011         programStats.score = programStats.got_only_move = 0;
15012         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15013
15014         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15015         strcat(bookMove, bookHit);
15016         savedMessage = bookMove; // args for deferred call
15017         savedState = &first;
15018         ScheduleDelayedEvent(DeferredBookMove, 1);
15019     }
15020 }
15021
15022
15023 void
15024 DisplayTwoMachinesTitle ()
15025 {
15026     char buf[MSG_SIZ];
15027     if (appData.matchGames > 0) {
15028         if(appData.tourneyFile[0]) {
15029           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15030                    gameInfo.white, _("vs."), gameInfo.black,
15031                    nextGame+1, appData.matchGames+1,
15032                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15033         } else
15034         if (first.twoMachinesColor[0] == 'w') {
15035           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15036                    gameInfo.white, _("vs."),  gameInfo.black,
15037                    first.matchWins, second.matchWins,
15038                    matchGame - 1 - (first.matchWins + second.matchWins));
15039         } else {
15040           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15041                    gameInfo.white, _("vs."), gameInfo.black,
15042                    second.matchWins, first.matchWins,
15043                    matchGame - 1 - (first.matchWins + second.matchWins));
15044         }
15045     } else {
15046       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15047     }
15048     DisplayTitle(buf);
15049 }
15050
15051 void
15052 SettingsMenuIfReady ()
15053 {
15054   if (second.lastPing != second.lastPong) {
15055     DisplayMessage("", _("Waiting for second chess program"));
15056     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15057     return;
15058   }
15059   ThawUI();
15060   DisplayMessage("", "");
15061   SettingsPopUp(&second);
15062 }
15063
15064 int
15065 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15066 {
15067     char buf[MSG_SIZ];
15068     if (cps->pr == NoProc) {
15069         StartChessProgram(cps);
15070         if (cps->protocolVersion == 1) {
15071           retry();
15072           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15073         } else {
15074           /* kludge: allow timeout for initial "feature" command */
15075           if(retry != TwoMachinesEventIfReady) FreezeUI();
15076           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15077           DisplayMessage("", buf);
15078           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15079         }
15080         return 1;
15081     }
15082     return 0;
15083 }
15084
15085 void
15086 TwoMachinesEvent P((void))
15087 {
15088     int i, move = forwardMostMove;
15089     char buf[MSG_SIZ];
15090     ChessProgramState *onmove;
15091     char *bookHit = NULL;
15092     static int stalling = 0;
15093     TimeMark now;
15094     long wait;
15095
15096     if (appData.noChessProgram) return;
15097
15098     switch (gameMode) {
15099       case TwoMachinesPlay:
15100         return;
15101       case MachinePlaysWhite:
15102       case MachinePlaysBlack:
15103         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15104             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15105             return;
15106         }
15107         /* fall through */
15108       case BeginningOfGame:
15109       case PlayFromGameFile:
15110       case EndOfGame:
15111         EditGameEvent();
15112         if (gameMode != EditGame) return;
15113         break;
15114       case EditPosition:
15115         EditPositionDone(TRUE);
15116         break;
15117       case AnalyzeMode:
15118       case AnalyzeFile:
15119         ExitAnalyzeMode();
15120         break;
15121       case EditGame:
15122       default:
15123         break;
15124     }
15125
15126 //    forwardMostMove = currentMove;
15127     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15128     startingEngine = TRUE;
15129
15130     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15131
15132     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15133     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15134       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15135       return;
15136     }
15137   if(!appData.epd) {
15138     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15139
15140     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15141                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15142         startingEngine = matchMode = FALSE;
15143         DisplayError("second engine does not play this", 0);
15144         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15145         EditGameEvent(); // switch back to EditGame mode
15146         return;
15147     }
15148
15149     if(!stalling) {
15150       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15151       SendToProgram("force\n", &second);
15152       stalling = 1;
15153       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15154       return;
15155     }
15156   }
15157     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15158     if(appData.matchPause>10000 || appData.matchPause<10)
15159                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15160     wait = SubtractTimeMarks(&now, &pauseStart);
15161     if(wait < appData.matchPause) {
15162         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15163         return;
15164     }
15165     // we are now committed to starting the game
15166     stalling = 0;
15167     DisplayMessage("", "");
15168   if(!appData.epd) {
15169     if (startedFromSetupPosition) {
15170         SendBoard(&second, backwardMostMove);
15171     if (appData.debugMode) {
15172         fprintf(debugFP, "Two Machines\n");
15173     }
15174     }
15175     for (i = backwardMostMove; i < forwardMostMove; i++) {
15176         SendMoveToProgram(i, &second);
15177     }
15178   }
15179
15180     gameMode = TwoMachinesPlay;
15181     pausing = startingEngine = FALSE;
15182     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15183     SetGameInfo();
15184     DisplayTwoMachinesTitle();
15185     firstMove = TRUE;
15186     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15187         onmove = &first;
15188     } else {
15189         onmove = &second;
15190     }
15191     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15192     SendToProgram(first.computerString, &first);
15193     if (first.sendName) {
15194       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15195       SendToProgram(buf, &first);
15196     }
15197   if(!appData.epd) {
15198     SendToProgram(second.computerString, &second);
15199     if (second.sendName) {
15200       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15201       SendToProgram(buf, &second);
15202     }
15203   }
15204
15205     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15206         ResetClocks();
15207         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15208         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15209     }
15210     if (onmove->sendTime) {
15211       if (onmove->useColors) {
15212         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15213       }
15214       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15215     }
15216     if (onmove->useColors) {
15217       SendToProgram(onmove->twoMachinesColor, onmove);
15218     }
15219     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15220 //    SendToProgram("go\n", onmove);
15221     onmove->maybeThinking = TRUE;
15222     SetMachineThinkingEnables();
15223
15224     StartClocks();
15225
15226     if(bookHit) { // [HGM] book: simulate book reply
15227         static char bookMove[MSG_SIZ]; // a bit generous?
15228
15229         programStats.nodes = programStats.depth = programStats.time =
15230         programStats.score = programStats.got_only_move = 0;
15231         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15232
15233         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15234         strcat(bookMove, bookHit);
15235         savedMessage = bookMove; // args for deferred call
15236         savedState = onmove;
15237         ScheduleDelayedEvent(DeferredBookMove, 1);
15238     }
15239 }
15240
15241 void
15242 TrainingEvent ()
15243 {
15244     if (gameMode == Training) {
15245       SetTrainingModeOff();
15246       gameMode = PlayFromGameFile;
15247       DisplayMessage("", _("Training mode off"));
15248     } else {
15249       gameMode = Training;
15250       animateTraining = appData.animate;
15251
15252       /* make sure we are not already at the end of the game */
15253       if (currentMove < forwardMostMove) {
15254         SetTrainingModeOn();
15255         DisplayMessage("", _("Training mode on"));
15256       } else {
15257         gameMode = PlayFromGameFile;
15258         DisplayError(_("Already at end of game"), 0);
15259       }
15260     }
15261     ModeHighlight();
15262 }
15263
15264 void
15265 IcsClientEvent ()
15266 {
15267     if (!appData.icsActive) return;
15268     switch (gameMode) {
15269       case IcsPlayingWhite:
15270       case IcsPlayingBlack:
15271       case IcsObserving:
15272       case IcsIdle:
15273       case BeginningOfGame:
15274       case IcsExamining:
15275         return;
15276
15277       case EditGame:
15278         break;
15279
15280       case EditPosition:
15281         EditPositionDone(TRUE);
15282         break;
15283
15284       case AnalyzeMode:
15285       case AnalyzeFile:
15286         ExitAnalyzeMode();
15287         break;
15288
15289       default:
15290         EditGameEvent();
15291         break;
15292     }
15293
15294     gameMode = IcsIdle;
15295     ModeHighlight();
15296     return;
15297 }
15298
15299 void
15300 EditGameEvent ()
15301 {
15302     int i;
15303
15304     switch (gameMode) {
15305       case Training:
15306         SetTrainingModeOff();
15307         break;
15308       case MachinePlaysWhite:
15309       case MachinePlaysBlack:
15310       case BeginningOfGame:
15311         SendToProgram("force\n", &first);
15312         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15313             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15314                 char buf[MSG_SIZ];
15315                 abortEngineThink = TRUE;
15316                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15317                 SendToProgram(buf, &first);
15318                 DisplayMessage("Aborting engine think", "");
15319                 FreezeUI();
15320             }
15321         }
15322         SetUserThinkingEnables();
15323         break;
15324       case PlayFromGameFile:
15325         (void) StopLoadGameTimer();
15326         if (gameFileFP != NULL) {
15327             gameFileFP = NULL;
15328         }
15329         break;
15330       case EditPosition:
15331         EditPositionDone(TRUE);
15332         break;
15333       case AnalyzeMode:
15334       case AnalyzeFile:
15335         ExitAnalyzeMode();
15336         SendToProgram("force\n", &first);
15337         break;
15338       case TwoMachinesPlay:
15339         GameEnds(EndOfFile, NULL, GE_PLAYER);
15340         ResurrectChessProgram();
15341         SetUserThinkingEnables();
15342         break;
15343       case EndOfGame:
15344         ResurrectChessProgram();
15345         break;
15346       case IcsPlayingBlack:
15347       case IcsPlayingWhite:
15348         DisplayError(_("Warning: You are still playing a game"), 0);
15349         break;
15350       case IcsObserving:
15351         DisplayError(_("Warning: You are still observing a game"), 0);
15352         break;
15353       case IcsExamining:
15354         DisplayError(_("Warning: You are still examining a game"), 0);
15355         break;
15356       case IcsIdle:
15357         break;
15358       case EditGame:
15359       default:
15360         return;
15361     }
15362
15363     pausing = FALSE;
15364     StopClocks();
15365     first.offeredDraw = second.offeredDraw = 0;
15366
15367     if (gameMode == PlayFromGameFile) {
15368         whiteTimeRemaining = timeRemaining[0][currentMove];
15369         blackTimeRemaining = timeRemaining[1][currentMove];
15370         DisplayTitle("");
15371     }
15372
15373     if (gameMode == MachinePlaysWhite ||
15374         gameMode == MachinePlaysBlack ||
15375         gameMode == TwoMachinesPlay ||
15376         gameMode == EndOfGame) {
15377         i = forwardMostMove;
15378         while (i > currentMove) {
15379             SendToProgram("undo\n", &first);
15380             i--;
15381         }
15382         if(!adjustedClock) {
15383         whiteTimeRemaining = timeRemaining[0][currentMove];
15384         blackTimeRemaining = timeRemaining[1][currentMove];
15385         DisplayBothClocks();
15386         }
15387         if (whiteFlag || blackFlag) {
15388             whiteFlag = blackFlag = 0;
15389         }
15390         DisplayTitle("");
15391     }
15392
15393     gameMode = EditGame;
15394     ModeHighlight();
15395     SetGameInfo();
15396 }
15397
15398 void
15399 EditPositionEvent ()
15400 {
15401     int i;
15402     if (gameMode == EditPosition) {
15403         EditGameEvent();
15404         return;
15405     }
15406
15407     EditGameEvent();
15408     if (gameMode != EditGame) return;
15409
15410     gameMode = EditPosition;
15411     ModeHighlight();
15412     SetGameInfo();
15413     CopyBoard(rightsBoard, nullBoard);
15414     if (currentMove > 0)
15415       CopyBoard(boards[0], boards[currentMove]);
15416     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15417       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15418
15419     blackPlaysFirst = !WhiteOnMove(currentMove);
15420     ResetClocks();
15421     currentMove = forwardMostMove = backwardMostMove = 0;
15422     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15423     DisplayMove(-1);
15424     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15425 }
15426
15427 void
15428 ExitAnalyzeMode ()
15429 {
15430     /* [DM] icsEngineAnalyze - possible call from other functions */
15431     if (appData.icsEngineAnalyze) {
15432         appData.icsEngineAnalyze = FALSE;
15433
15434         DisplayMessage("",_("Close ICS engine analyze..."));
15435     }
15436     if (first.analysisSupport && first.analyzing) {
15437       SendToBoth("exit\n");
15438       first.analyzing = second.analyzing = FALSE;
15439     }
15440     thinkOutput[0] = NULLCHAR;
15441 }
15442
15443 void
15444 EditPositionDone (Boolean fakeRights)
15445 {
15446     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15447
15448     startedFromSetupPosition = TRUE;
15449     InitChessProgram(&first, FALSE);
15450     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15451       int r, f;
15452       boards[0][EP_STATUS] = EP_NONE;
15453       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15454       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15455         if(rightsBoard[r][f]) {
15456           ChessSquare p = boards[0][r][f];
15457           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15458           else if(p == king) boards[0][CASTLING][2] = f;
15459           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15460           else rightsBoard[r][f] = 2; // mark for second pass
15461         }
15462       }
15463       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15464         if(rightsBoard[r][f] == 2) {
15465           ChessSquare p = boards[0][r][f];
15466           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15467           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15468         }
15469       }
15470     }
15471     SendToProgram("force\n", &first);
15472     if (blackPlaysFirst) {
15473         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15474         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15475         currentMove = forwardMostMove = backwardMostMove = 1;
15476         CopyBoard(boards[1], boards[0]);
15477     } else {
15478         currentMove = forwardMostMove = backwardMostMove = 0;
15479     }
15480     SendBoard(&first, forwardMostMove);
15481     if (appData.debugMode) {
15482         fprintf(debugFP, "EditPosDone\n");
15483     }
15484     DisplayTitle("");
15485     DisplayMessage("", "");
15486     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15487     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15488     gameMode = EditGame;
15489     ModeHighlight();
15490     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15491     ClearHighlights(); /* [AS] */
15492 }
15493
15494 /* Pause for `ms' milliseconds */
15495 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15496 void
15497 TimeDelay (long ms)
15498 {
15499     TimeMark m1, m2;
15500
15501     GetTimeMark(&m1);
15502     do {
15503         GetTimeMark(&m2);
15504     } while (SubtractTimeMarks(&m2, &m1) < ms);
15505 }
15506
15507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15508 void
15509 SendMultiLineToICS (char *buf)
15510 {
15511     char temp[MSG_SIZ+1], *p;
15512     int len;
15513
15514     len = strlen(buf);
15515     if (len > MSG_SIZ)
15516       len = MSG_SIZ;
15517
15518     strncpy(temp, buf, len);
15519     temp[len] = 0;
15520
15521     p = temp;
15522     while (*p) {
15523         if (*p == '\n' || *p == '\r')
15524           *p = ' ';
15525         ++p;
15526     }
15527
15528     strcat(temp, "\n");
15529     SendToICS(temp);
15530     SendToPlayer(temp, strlen(temp));
15531 }
15532
15533 void
15534 SetWhiteToPlayEvent ()
15535 {
15536     if (gameMode == EditPosition) {
15537         blackPlaysFirst = FALSE;
15538         DisplayBothClocks();    /* works because currentMove is 0 */
15539     } else if (gameMode == IcsExamining) {
15540         SendToICS(ics_prefix);
15541         SendToICS("tomove white\n");
15542     }
15543 }
15544
15545 void
15546 SetBlackToPlayEvent ()
15547 {
15548     if (gameMode == EditPosition) {
15549         blackPlaysFirst = TRUE;
15550         currentMove = 1;        /* kludge */
15551         DisplayBothClocks();
15552         currentMove = 0;
15553     } else if (gameMode == IcsExamining) {
15554         SendToICS(ics_prefix);
15555         SendToICS("tomove black\n");
15556     }
15557 }
15558
15559 void
15560 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15561 {
15562     char buf[MSG_SIZ];
15563     ChessSquare piece = boards[0][y][x];
15564     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15565     static int lastVariant;
15566     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15567
15568     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15569
15570     switch (selection) {
15571       case ClearBoard:
15572         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15573         MarkTargetSquares(1);
15574         CopyBoard(currentBoard, boards[0]);
15575         CopyBoard(menuBoard, initialPosition);
15576         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15577             SendToICS(ics_prefix);
15578             SendToICS("bsetup clear\n");
15579         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15580             SendToICS(ics_prefix);
15581             SendToICS("clearboard\n");
15582         } else {
15583             int nonEmpty = 0;
15584             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15585                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15586                 for (y = 0; y < BOARD_HEIGHT; y++) {
15587                     if (gameMode == IcsExamining) {
15588                         if (boards[currentMove][y][x] != EmptySquare) {
15589                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15590                                     AAA + x, ONE + y);
15591                             SendToICS(buf);
15592                         }
15593                     } else if(boards[0][y][x] != DarkSquare) {
15594                         if(boards[0][y][x] != p) nonEmpty++;
15595                         boards[0][y][x] = p;
15596                     }
15597                 }
15598             }
15599             CopyBoard(rightsBoard, nullBoard);
15600             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15601                 int r, i;
15602                 for(r = 0; r < BOARD_HEIGHT; r++) {
15603                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15604                     ChessSquare p = menuBoard[r][x];
15605                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15606                   }
15607                 }
15608                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15609                 DisplayMessage("Clicking clock again restores position", "");
15610                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15611                 if(!nonEmpty) { // asked to clear an empty board
15612                     CopyBoard(boards[0], menuBoard);
15613                 } else
15614                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15615                     CopyBoard(boards[0], initialPosition);
15616                 } else
15617                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15618                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15619                     CopyBoard(boards[0], erasedBoard);
15620                 } else
15621                     CopyBoard(erasedBoard, currentBoard);
15622
15623                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15624                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15625             }
15626         }
15627         if (gameMode == EditPosition) {
15628             DrawPosition(FALSE, boards[0]);
15629         }
15630         break;
15631
15632       case WhitePlay:
15633         SetWhiteToPlayEvent();
15634         break;
15635
15636       case BlackPlay:
15637         SetBlackToPlayEvent();
15638         break;
15639
15640       case EmptySquare:
15641         if (gameMode == IcsExamining) {
15642             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15643             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15644             SendToICS(buf);
15645         } else {
15646             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15647                 if(x == BOARD_LEFT-2) {
15648                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15649                     boards[0][y][1] = 0;
15650                 } else
15651                 if(x == BOARD_RGHT+1) {
15652                     if(y >= gameInfo.holdingsSize) break;
15653                     boards[0][y][BOARD_WIDTH-2] = 0;
15654                 } else break;
15655             }
15656             boards[0][y][x] = EmptySquare;
15657             DrawPosition(FALSE, boards[0]);
15658         }
15659         break;
15660
15661       case PromotePiece:
15662         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15663            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15664             selection = (ChessSquare) (PROMOTED(piece));
15665         } else if(piece == EmptySquare) selection = WhiteSilver;
15666         else selection = (ChessSquare)((int)piece - 1);
15667         goto defaultlabel;
15668
15669       case DemotePiece:
15670         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15671            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15672             selection = (ChessSquare) (DEMOTED(piece));
15673         } else if(piece == EmptySquare) selection = BlackSilver;
15674         else selection = (ChessSquare)((int)piece + 1);
15675         goto defaultlabel;
15676
15677       case WhiteQueen:
15678       case BlackQueen:
15679         if(gameInfo.variant == VariantShatranj ||
15680            gameInfo.variant == VariantXiangqi  ||
15681            gameInfo.variant == VariantCourier  ||
15682            gameInfo.variant == VariantASEAN    ||
15683            gameInfo.variant == VariantMakruk     )
15684             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15685         goto defaultlabel;
15686
15687       case WhiteRook:
15688         baseRank = 0;
15689       case BlackRook:
15690         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15691         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15692         goto defaultlabel;
15693
15694       case WhiteKing:
15695         baseRank = 0;
15696       case BlackKing:
15697         if(gameInfo.variant == VariantXiangqi)
15698             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15699         if(gameInfo.variant == VariantKnightmate)
15700             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15701         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15702       default:
15703         defaultlabel:
15704         if (gameMode == IcsExamining) {
15705             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15706             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15707                      PieceToChar(selection), AAA + x, ONE + y);
15708             SendToICS(buf);
15709         } else {
15710             rightsBoard[y][x] = hasRights;
15711             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15712                 int n;
15713                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15714                     n = PieceToNumber(selection - BlackPawn);
15715                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15716                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15717                     boards[0][BOARD_HEIGHT-1-n][1]++;
15718                 } else
15719                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15720                     n = PieceToNumber(selection);
15721                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15722                     boards[0][n][BOARD_WIDTH-1] = selection;
15723                     boards[0][n][BOARD_WIDTH-2]++;
15724                 }
15725             } else
15726             boards[0][y][x] = selection;
15727             DrawPosition(TRUE, boards[0]);
15728             ClearHighlights();
15729             fromX = fromY = -1;
15730         }
15731         break;
15732     }
15733 }
15734
15735
15736 void
15737 DropMenuEvent (ChessSquare selection, int x, int y)
15738 {
15739     ChessMove moveType;
15740
15741     switch (gameMode) {
15742       case IcsPlayingWhite:
15743       case MachinePlaysBlack:
15744         if (!WhiteOnMove(currentMove)) {
15745             DisplayMoveError(_("It is Black's turn"));
15746             return;
15747         }
15748         moveType = WhiteDrop;
15749         break;
15750       case IcsPlayingBlack:
15751       case MachinePlaysWhite:
15752         if (WhiteOnMove(currentMove)) {
15753             DisplayMoveError(_("It is White's turn"));
15754             return;
15755         }
15756         moveType = BlackDrop;
15757         break;
15758       case EditGame:
15759         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15760         break;
15761       default:
15762         return;
15763     }
15764
15765     if (moveType == BlackDrop && selection < BlackPawn) {
15766       selection = (ChessSquare) ((int) selection
15767                                  + (int) BlackPawn - (int) WhitePawn);
15768     }
15769     if (boards[currentMove][y][x] != EmptySquare) {
15770         DisplayMoveError(_("That square is occupied"));
15771         return;
15772     }
15773
15774     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15775 }
15776
15777 void
15778 AcceptEvent ()
15779 {
15780     /* Accept a pending offer of any kind from opponent */
15781
15782     if (appData.icsActive) {
15783         SendToICS(ics_prefix);
15784         SendToICS("accept\n");
15785     } else if (cmailMsgLoaded) {
15786         if (currentMove == cmailOldMove &&
15787             commentList[cmailOldMove] != NULL &&
15788             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15789                    "Black offers a draw" : "White offers a draw")) {
15790             TruncateGame();
15791             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15792             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15793         } else {
15794             DisplayError(_("There is no pending offer on this move"), 0);
15795             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15796         }
15797     } else {
15798         /* Not used for offers from chess program */
15799     }
15800 }
15801
15802 void
15803 DeclineEvent ()
15804 {
15805     /* Decline a pending offer of any kind from opponent */
15806
15807     if (appData.icsActive) {
15808         SendToICS(ics_prefix);
15809         SendToICS("decline\n");
15810     } else if (cmailMsgLoaded) {
15811         if (currentMove == cmailOldMove &&
15812             commentList[cmailOldMove] != NULL &&
15813             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15814                    "Black offers a draw" : "White offers a draw")) {
15815 #ifdef NOTDEF
15816             AppendComment(cmailOldMove, "Draw declined", TRUE);
15817             DisplayComment(cmailOldMove - 1, "Draw declined");
15818 #endif /*NOTDEF*/
15819         } else {
15820             DisplayError(_("There is no pending offer on this move"), 0);
15821         }
15822     } else {
15823         /* Not used for offers from chess program */
15824     }
15825 }
15826
15827 void
15828 RematchEvent ()
15829 {
15830     /* Issue ICS rematch command */
15831     if (appData.icsActive) {
15832         SendToICS(ics_prefix);
15833         SendToICS("rematch\n");
15834     }
15835 }
15836
15837 void
15838 CallFlagEvent ()
15839 {
15840     /* Call your opponent's flag (claim a win on time) */
15841     if (appData.icsActive) {
15842         SendToICS(ics_prefix);
15843         SendToICS("flag\n");
15844     } else {
15845         switch (gameMode) {
15846           default:
15847             return;
15848           case MachinePlaysWhite:
15849             if (whiteFlag) {
15850                 if (blackFlag)
15851                   GameEnds(GameIsDrawn, "Both players ran out of time",
15852                            GE_PLAYER);
15853                 else
15854                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15855             } else {
15856                 DisplayError(_("Your opponent is not out of time"), 0);
15857             }
15858             break;
15859           case MachinePlaysBlack:
15860             if (blackFlag) {
15861                 if (whiteFlag)
15862                   GameEnds(GameIsDrawn, "Both players ran out of time",
15863                            GE_PLAYER);
15864                 else
15865                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15866             } else {
15867                 DisplayError(_("Your opponent is not out of time"), 0);
15868             }
15869             break;
15870         }
15871     }
15872 }
15873
15874 void
15875 ClockClick (int which)
15876 {       // [HGM] code moved to back-end from winboard.c
15877         if(which) { // black clock
15878           if (gameMode == EditPosition || gameMode == IcsExamining) {
15879             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15880             SetBlackToPlayEvent();
15881           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15882                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15883           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15884           } else if (shiftKey) {
15885             AdjustClock(which, -1);
15886           } else if (gameMode == IcsPlayingWhite ||
15887                      gameMode == MachinePlaysBlack) {
15888             CallFlagEvent();
15889           }
15890         } else { // white clock
15891           if (gameMode == EditPosition || gameMode == IcsExamining) {
15892             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15893             SetWhiteToPlayEvent();
15894           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15895                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15896           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15897           } else if (shiftKey) {
15898             AdjustClock(which, -1);
15899           } else if (gameMode == IcsPlayingBlack ||
15900                    gameMode == MachinePlaysWhite) {
15901             CallFlagEvent();
15902           }
15903         }
15904 }
15905
15906 void
15907 DrawEvent ()
15908 {
15909     /* Offer draw or accept pending draw offer from opponent */
15910
15911     if (appData.icsActive) {
15912         /* Note: tournament rules require draw offers to be
15913            made after you make your move but before you punch
15914            your clock.  Currently ICS doesn't let you do that;
15915            instead, you immediately punch your clock after making
15916            a move, but you can offer a draw at any time. */
15917
15918         SendToICS(ics_prefix);
15919         SendToICS("draw\n");
15920         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15921     } else if (cmailMsgLoaded) {
15922         if (currentMove == cmailOldMove &&
15923             commentList[cmailOldMove] != NULL &&
15924             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15925                    "Black offers a draw" : "White offers a draw")) {
15926             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15927             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15928         } else if (currentMove == cmailOldMove + 1) {
15929             char *offer = WhiteOnMove(cmailOldMove) ?
15930               "White offers a draw" : "Black offers a draw";
15931             AppendComment(currentMove, offer, TRUE);
15932             DisplayComment(currentMove - 1, offer);
15933             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15934         } else {
15935             DisplayError(_("You must make your move before offering a draw"), 0);
15936             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15937         }
15938     } else if (first.offeredDraw) {
15939         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15940     } else {
15941         if (first.sendDrawOffers) {
15942             SendToProgram("draw\n", &first);
15943             userOfferedDraw = TRUE;
15944         }
15945     }
15946 }
15947
15948 void
15949 AdjournEvent ()
15950 {
15951     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15952
15953     if (appData.icsActive) {
15954         SendToICS(ics_prefix);
15955         SendToICS("adjourn\n");
15956     } else {
15957         /* Currently GNU Chess doesn't offer or accept Adjourns */
15958     }
15959 }
15960
15961
15962 void
15963 AbortEvent ()
15964 {
15965     /* Offer Abort or accept pending Abort offer from opponent */
15966
15967     if (appData.icsActive) {
15968         SendToICS(ics_prefix);
15969         SendToICS("abort\n");
15970     } else {
15971         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15972     }
15973 }
15974
15975 void
15976 ResignEvent ()
15977 {
15978     /* Resign.  You can do this even if it's not your turn. */
15979
15980     if (appData.icsActive) {
15981         SendToICS(ics_prefix);
15982         SendToICS("resign\n");
15983     } else {
15984         switch (gameMode) {
15985           case MachinePlaysWhite:
15986             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15987             break;
15988           case MachinePlaysBlack:
15989             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15990             break;
15991           case EditGame:
15992             if (cmailMsgLoaded) {
15993                 TruncateGame();
15994                 if (WhiteOnMove(cmailOldMove)) {
15995                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15996                 } else {
15997                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15998                 }
15999                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16000             }
16001             break;
16002           default:
16003             break;
16004         }
16005     }
16006 }
16007
16008
16009 void
16010 StopObservingEvent ()
16011 {
16012     /* Stop observing current games */
16013     SendToICS(ics_prefix);
16014     SendToICS("unobserve\n");
16015 }
16016
16017 void
16018 StopExaminingEvent ()
16019 {
16020     /* Stop observing current game */
16021     SendToICS(ics_prefix);
16022     SendToICS("unexamine\n");
16023 }
16024
16025 void
16026 ForwardInner (int target)
16027 {
16028     int limit; int oldSeekGraphUp = seekGraphUp;
16029
16030     if (appData.debugMode)
16031         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16032                 target, currentMove, forwardMostMove);
16033
16034     if (gameMode == EditPosition)
16035       return;
16036
16037     seekGraphUp = FALSE;
16038     MarkTargetSquares(1);
16039     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16040
16041     if (gameMode == PlayFromGameFile && !pausing)
16042       PauseEvent();
16043
16044     if (gameMode == IcsExamining && pausing)
16045       limit = pauseExamForwardMostMove;
16046     else
16047       limit = forwardMostMove;
16048
16049     if (target > limit) target = limit;
16050
16051     if (target > 0 && moveList[target - 1][0]) {
16052         int fromX, fromY, toX, toY;
16053         toX = moveList[target - 1][2] - AAA;
16054         toY = moveList[target - 1][3] - ONE;
16055         if (moveList[target - 1][1] == '@') {
16056             if (appData.highlightLastMove) {
16057                 SetHighlights(-1, -1, toX, toY);
16058             }
16059         } else {
16060             fromX = moveList[target - 1][0] - AAA;
16061             fromY = moveList[target - 1][1] - ONE;
16062             if (target == currentMove + 1) {
16063                 if(moveList[target - 1][4] == ';') { // multi-leg
16064                     killX = moveList[target - 1][5] - AAA;
16065                     killY = moveList[target - 1][6] - ONE;
16066                 }
16067                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16068                 killX = killY = -1;
16069             }
16070             if (appData.highlightLastMove) {
16071                 SetHighlights(fromX, fromY, toX, toY);
16072             }
16073         }
16074     }
16075     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16076         gameMode == Training || gameMode == PlayFromGameFile ||
16077         gameMode == AnalyzeFile) {
16078         while (currentMove < target) {
16079             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16080             SendMoveToProgram(currentMove++, &first);
16081         }
16082     } else {
16083         currentMove = target;
16084     }
16085
16086     if (gameMode == EditGame || gameMode == EndOfGame) {
16087         whiteTimeRemaining = timeRemaining[0][currentMove];
16088         blackTimeRemaining = timeRemaining[1][currentMove];
16089     }
16090     DisplayBothClocks();
16091     DisplayMove(currentMove - 1);
16092     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16093     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16094     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16095         DisplayComment(currentMove - 1, commentList[currentMove]);
16096     }
16097     ClearMap(); // [HGM] exclude: invalidate map
16098 }
16099
16100
16101 void
16102 ForwardEvent ()
16103 {
16104     if (gameMode == IcsExamining && !pausing) {
16105         SendToICS(ics_prefix);
16106         SendToICS("forward\n");
16107     } else {
16108         ForwardInner(currentMove + 1);
16109     }
16110 }
16111
16112 void
16113 ToEndEvent ()
16114 {
16115     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16116         /* to optimze, we temporarily turn off analysis mode while we feed
16117          * the remaining moves to the engine. Otherwise we get analysis output
16118          * after each move.
16119          */
16120         if (first.analysisSupport) {
16121           SendToProgram("exit\nforce\n", &first);
16122           first.analyzing = FALSE;
16123         }
16124     }
16125
16126     if (gameMode == IcsExamining && !pausing) {
16127         SendToICS(ics_prefix);
16128         SendToICS("forward 999999\n");
16129     } else {
16130         ForwardInner(forwardMostMove);
16131     }
16132
16133     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16134         /* we have fed all the moves, so reactivate analysis mode */
16135         SendToProgram("analyze\n", &first);
16136         first.analyzing = TRUE;
16137         /*first.maybeThinking = TRUE;*/
16138         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16139     }
16140 }
16141
16142 void
16143 BackwardInner (int target)
16144 {
16145     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16146
16147     if (appData.debugMode)
16148         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16149                 target, currentMove, forwardMostMove);
16150
16151     if (gameMode == EditPosition) return;
16152     seekGraphUp = FALSE;
16153     MarkTargetSquares(1);
16154     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16155     if (currentMove <= backwardMostMove) {
16156         ClearHighlights();
16157         DrawPosition(full_redraw, boards[currentMove]);
16158         return;
16159     }
16160     if (gameMode == PlayFromGameFile && !pausing)
16161       PauseEvent();
16162
16163     if (moveList[target][0]) {
16164         int fromX, fromY, toX, toY;
16165         toX = moveList[target][2] - AAA;
16166         toY = moveList[target][3] - ONE;
16167         if (moveList[target][1] == '@') {
16168             if (appData.highlightLastMove) {
16169                 SetHighlights(-1, -1, toX, toY);
16170             }
16171         } else {
16172             fromX = moveList[target][0] - AAA;
16173             fromY = moveList[target][1] - ONE;
16174             if (target == currentMove - 1) {
16175                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16176             }
16177             if (appData.highlightLastMove) {
16178                 SetHighlights(fromX, fromY, toX, toY);
16179             }
16180         }
16181     }
16182     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16183         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16184         while (currentMove > target) {
16185             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16186                 // null move cannot be undone. Reload program with move history before it.
16187                 int i;
16188                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16189                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16190                 }
16191                 SendBoard(&first, i);
16192               if(second.analyzing) SendBoard(&second, i);
16193                 for(currentMove=i; currentMove<target; currentMove++) {
16194                     SendMoveToProgram(currentMove, &first);
16195                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16196                 }
16197                 break;
16198             }
16199             SendToBoth("undo\n");
16200             currentMove--;
16201         }
16202     } else {
16203         currentMove = target;
16204     }
16205
16206     if (gameMode == EditGame || gameMode == EndOfGame) {
16207         whiteTimeRemaining = timeRemaining[0][currentMove];
16208         blackTimeRemaining = timeRemaining[1][currentMove];
16209     }
16210     DisplayBothClocks();
16211     DisplayMove(currentMove - 1);
16212     DrawPosition(full_redraw, boards[currentMove]);
16213     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16214     // [HGM] PV info: routine tests if comment empty
16215     DisplayComment(currentMove - 1, commentList[currentMove]);
16216     ClearMap(); // [HGM] exclude: invalidate map
16217 }
16218
16219 void
16220 BackwardEvent ()
16221 {
16222     if (gameMode == IcsExamining && !pausing) {
16223         SendToICS(ics_prefix);
16224         SendToICS("backward\n");
16225     } else {
16226         BackwardInner(currentMove - 1);
16227     }
16228 }
16229
16230 void
16231 ToStartEvent ()
16232 {
16233     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16234         /* to optimize, we temporarily turn off analysis mode while we undo
16235          * all the moves. Otherwise we get analysis output after each undo.
16236          */
16237         if (first.analysisSupport) {
16238           SendToProgram("exit\nforce\n", &first);
16239           first.analyzing = FALSE;
16240         }
16241     }
16242
16243     if (gameMode == IcsExamining && !pausing) {
16244         SendToICS(ics_prefix);
16245         SendToICS("backward 999999\n");
16246     } else {
16247         BackwardInner(backwardMostMove);
16248     }
16249
16250     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16251         /* we have fed all the moves, so reactivate analysis mode */
16252         SendToProgram("analyze\n", &first);
16253         first.analyzing = TRUE;
16254         /*first.maybeThinking = TRUE;*/
16255         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16256     }
16257 }
16258
16259 void
16260 ToNrEvent (int to)
16261 {
16262   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16263   if (to >= forwardMostMove) to = forwardMostMove;
16264   if (to <= backwardMostMove) to = backwardMostMove;
16265   if (to < currentMove) {
16266     BackwardInner(to);
16267   } else {
16268     ForwardInner(to);
16269   }
16270 }
16271
16272 void
16273 RevertEvent (Boolean annotate)
16274 {
16275     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16276         return;
16277     }
16278     if (gameMode != IcsExamining) {
16279         DisplayError(_("You are not examining a game"), 0);
16280         return;
16281     }
16282     if (pausing) {
16283         DisplayError(_("You can't revert while pausing"), 0);
16284         return;
16285     }
16286     SendToICS(ics_prefix);
16287     SendToICS("revert\n");
16288 }
16289
16290 void
16291 RetractMoveEvent ()
16292 {
16293     switch (gameMode) {
16294       case MachinePlaysWhite:
16295       case MachinePlaysBlack:
16296         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16297             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16298             return;
16299         }
16300         if (forwardMostMove < 2) return;
16301         currentMove = forwardMostMove = forwardMostMove - 2;
16302         whiteTimeRemaining = timeRemaining[0][currentMove];
16303         blackTimeRemaining = timeRemaining[1][currentMove];
16304         DisplayBothClocks();
16305         DisplayMove(currentMove - 1);
16306         ClearHighlights();/*!! could figure this out*/
16307         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16308         SendToProgram("remove\n", &first);
16309         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16310         break;
16311
16312       case BeginningOfGame:
16313       default:
16314         break;
16315
16316       case IcsPlayingWhite:
16317       case IcsPlayingBlack:
16318         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16319             SendToICS(ics_prefix);
16320             SendToICS("takeback 2\n");
16321         } else {
16322             SendToICS(ics_prefix);
16323             SendToICS("takeback 1\n");
16324         }
16325         break;
16326     }
16327 }
16328
16329 void
16330 MoveNowEvent ()
16331 {
16332     ChessProgramState *cps;
16333
16334     switch (gameMode) {
16335       case MachinePlaysWhite:
16336         if (!WhiteOnMove(forwardMostMove)) {
16337             DisplayError(_("It is your turn"), 0);
16338             return;
16339         }
16340         cps = &first;
16341         break;
16342       case MachinePlaysBlack:
16343         if (WhiteOnMove(forwardMostMove)) {
16344             DisplayError(_("It is your turn"), 0);
16345             return;
16346         }
16347         cps = &first;
16348         break;
16349       case TwoMachinesPlay:
16350         if (WhiteOnMove(forwardMostMove) ==
16351             (first.twoMachinesColor[0] == 'w')) {
16352             cps = &first;
16353         } else {
16354             cps = &second;
16355         }
16356         break;
16357       case BeginningOfGame:
16358       default:
16359         return;
16360     }
16361     SendToProgram("?\n", cps);
16362 }
16363
16364 void
16365 TruncateGameEvent ()
16366 {
16367     EditGameEvent();
16368     if (gameMode != EditGame) return;
16369     TruncateGame();
16370 }
16371
16372 void
16373 TruncateGame ()
16374 {
16375     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16376     if (forwardMostMove > currentMove) {
16377         if (gameInfo.resultDetails != NULL) {
16378             free(gameInfo.resultDetails);
16379             gameInfo.resultDetails = NULL;
16380             gameInfo.result = GameUnfinished;
16381         }
16382         forwardMostMove = currentMove;
16383         HistorySet(parseList, backwardMostMove, forwardMostMove,
16384                    currentMove-1);
16385     }
16386 }
16387
16388 void
16389 HintEvent ()
16390 {
16391     if (appData.noChessProgram) return;
16392     switch (gameMode) {
16393       case MachinePlaysWhite:
16394         if (WhiteOnMove(forwardMostMove)) {
16395             DisplayError(_("Wait until your turn."), 0);
16396             return;
16397         }
16398         break;
16399       case BeginningOfGame:
16400       case MachinePlaysBlack:
16401         if (!WhiteOnMove(forwardMostMove)) {
16402             DisplayError(_("Wait until your turn."), 0);
16403             return;
16404         }
16405         break;
16406       default:
16407         DisplayError(_("No hint available"), 0);
16408         return;
16409     }
16410     SendToProgram("hint\n", &first);
16411     hintRequested = TRUE;
16412 }
16413
16414 int
16415 SaveSelected (FILE *g, int dummy, char *dummy2)
16416 {
16417     ListGame * lg = (ListGame *) gameList.head;
16418     int nItem, cnt=0;
16419     FILE *f;
16420
16421     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16422         DisplayError(_("Game list not loaded or empty"), 0);
16423         return 0;
16424     }
16425
16426     creatingBook = TRUE; // suppresses stuff during load game
16427
16428     /* Get list size */
16429     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16430         if(lg->position >= 0) { // selected?
16431             LoadGame(f, nItem, "", TRUE);
16432             SaveGamePGN2(g); // leaves g open
16433             cnt++; DoEvents();
16434         }
16435         lg = (ListGame *) lg->node.succ;
16436     }
16437
16438     fclose(g);
16439     creatingBook = FALSE;
16440
16441     return cnt;
16442 }
16443
16444 void
16445 CreateBookEvent ()
16446 {
16447     ListGame * lg = (ListGame *) gameList.head;
16448     FILE *f, *g;
16449     int nItem;
16450     static int secondTime = FALSE;
16451
16452     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16453         DisplayError(_("Game list not loaded or empty"), 0);
16454         return;
16455     }
16456
16457     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16458         fclose(g);
16459         secondTime++;
16460         DisplayNote(_("Book file exists! Try again for overwrite."));
16461         return;
16462     }
16463
16464     creatingBook = TRUE;
16465     secondTime = FALSE;
16466
16467     /* Get list size */
16468     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16469         if(lg->position >= 0) {
16470             LoadGame(f, nItem, "", TRUE);
16471             AddGameToBook(TRUE);
16472             DoEvents();
16473         }
16474         lg = (ListGame *) lg->node.succ;
16475     }
16476
16477     creatingBook = FALSE;
16478     FlushBook();
16479 }
16480
16481 void
16482 BookEvent ()
16483 {
16484     if (appData.noChessProgram) return;
16485     switch (gameMode) {
16486       case MachinePlaysWhite:
16487         if (WhiteOnMove(forwardMostMove)) {
16488             DisplayError(_("Wait until your turn."), 0);
16489             return;
16490         }
16491         break;
16492       case BeginningOfGame:
16493       case MachinePlaysBlack:
16494         if (!WhiteOnMove(forwardMostMove)) {
16495             DisplayError(_("Wait until your turn."), 0);
16496             return;
16497         }
16498         break;
16499       case EditPosition:
16500         EditPositionDone(TRUE);
16501         break;
16502       case TwoMachinesPlay:
16503         return;
16504       default:
16505         break;
16506     }
16507     SendToProgram("bk\n", &first);
16508     bookOutput[0] = NULLCHAR;
16509     bookRequested = TRUE;
16510 }
16511
16512 void
16513 AboutGameEvent ()
16514 {
16515     char *tags = PGNTags(&gameInfo);
16516     TagsPopUp(tags, CmailMsg());
16517     free(tags);
16518 }
16519
16520 /* end button procedures */
16521
16522 void
16523 PrintPosition (FILE *fp, int move)
16524 {
16525     int i, j;
16526
16527     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16528         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16529             char c = PieceToChar(boards[move][i][j]);
16530             fputc(c == '?' ? '.' : c, fp);
16531             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16532         }
16533     }
16534     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16535       fprintf(fp, "white to play\n");
16536     else
16537       fprintf(fp, "black to play\n");
16538 }
16539
16540 void
16541 PrintOpponents (FILE *fp)
16542 {
16543     if (gameInfo.white != NULL) {
16544         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16545     } else {
16546         fprintf(fp, "\n");
16547     }
16548 }
16549
16550 /* Find last component of program's own name, using some heuristics */
16551 void
16552 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16553 {
16554     char *p, *q, c;
16555     int local = (strcmp(host, "localhost") == 0);
16556     while (!local && (p = strchr(prog, ';')) != NULL) {
16557         p++;
16558         while (*p == ' ') p++;
16559         prog = p;
16560     }
16561     if (*prog == '"' || *prog == '\'') {
16562         q = strchr(prog + 1, *prog);
16563     } else {
16564         q = strchr(prog, ' ');
16565     }
16566     if (q == NULL) q = prog + strlen(prog);
16567     p = q;
16568     while (p >= prog && *p != '/' && *p != '\\') p--;
16569     p++;
16570     if(p == prog && *p == '"') p++;
16571     c = *q; *q = 0;
16572     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16573     memcpy(buf, p, q - p);
16574     buf[q - p] = NULLCHAR;
16575     if (!local) {
16576         strcat(buf, "@");
16577         strcat(buf, host);
16578     }
16579 }
16580
16581 char *
16582 TimeControlTagValue ()
16583 {
16584     char buf[MSG_SIZ];
16585     if (!appData.clockMode) {
16586       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16587     } else if (movesPerSession > 0) {
16588       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16589     } else if (timeIncrement == 0) {
16590       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16591     } else {
16592       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16593     }
16594     return StrSave(buf);
16595 }
16596
16597 void
16598 SetGameInfo ()
16599 {
16600     /* This routine is used only for certain modes */
16601     VariantClass v = gameInfo.variant;
16602     ChessMove r = GameUnfinished;
16603     char *p = NULL;
16604
16605     if(keepInfo) return;
16606
16607     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16608         r = gameInfo.result;
16609         p = gameInfo.resultDetails;
16610         gameInfo.resultDetails = NULL;
16611     }
16612     ClearGameInfo(&gameInfo);
16613     gameInfo.variant = v;
16614
16615     switch (gameMode) {
16616       case MachinePlaysWhite:
16617         gameInfo.event = StrSave( appData.pgnEventHeader );
16618         gameInfo.site = StrSave(HostName());
16619         gameInfo.date = PGNDate();
16620         gameInfo.round = StrSave("-");
16621         gameInfo.white = StrSave(first.tidy);
16622         gameInfo.black = StrSave(UserName());
16623         gameInfo.timeControl = TimeControlTagValue();
16624         break;
16625
16626       case MachinePlaysBlack:
16627         gameInfo.event = StrSave( appData.pgnEventHeader );
16628         gameInfo.site = StrSave(HostName());
16629         gameInfo.date = PGNDate();
16630         gameInfo.round = StrSave("-");
16631         gameInfo.white = StrSave(UserName());
16632         gameInfo.black = StrSave(first.tidy);
16633         gameInfo.timeControl = TimeControlTagValue();
16634         break;
16635
16636       case TwoMachinesPlay:
16637         gameInfo.event = StrSave( appData.pgnEventHeader );
16638         gameInfo.site = StrSave(HostName());
16639         gameInfo.date = PGNDate();
16640         if (roundNr > 0) {
16641             char buf[MSG_SIZ];
16642             snprintf(buf, MSG_SIZ, "%d", roundNr);
16643             gameInfo.round = StrSave(buf);
16644         } else {
16645             gameInfo.round = StrSave("-");
16646         }
16647         if (first.twoMachinesColor[0] == 'w') {
16648             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16649             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16650         } else {
16651             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16652             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16653         }
16654         gameInfo.timeControl = TimeControlTagValue();
16655         break;
16656
16657       case EditGame:
16658         gameInfo.event = StrSave("Edited game");
16659         gameInfo.site = StrSave(HostName());
16660         gameInfo.date = PGNDate();
16661         gameInfo.round = StrSave("-");
16662         gameInfo.white = StrSave("-");
16663         gameInfo.black = StrSave("-");
16664         gameInfo.result = r;
16665         gameInfo.resultDetails = p;
16666         break;
16667
16668       case EditPosition:
16669         gameInfo.event = StrSave("Edited position");
16670         gameInfo.site = StrSave(HostName());
16671         gameInfo.date = PGNDate();
16672         gameInfo.round = StrSave("-");
16673         gameInfo.white = StrSave("-");
16674         gameInfo.black = StrSave("-");
16675         break;
16676
16677       case IcsPlayingWhite:
16678       case IcsPlayingBlack:
16679       case IcsObserving:
16680       case IcsExamining:
16681         break;
16682
16683       case PlayFromGameFile:
16684         gameInfo.event = StrSave("Game from non-PGN file");
16685         gameInfo.site = StrSave(HostName());
16686         gameInfo.date = PGNDate();
16687         gameInfo.round = StrSave("-");
16688         gameInfo.white = StrSave("?");
16689         gameInfo.black = StrSave("?");
16690         break;
16691
16692       default:
16693         break;
16694     }
16695 }
16696
16697 void
16698 ReplaceComment (int index, char *text)
16699 {
16700     int len;
16701     char *p;
16702     float score;
16703
16704     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16705        pvInfoList[index-1].depth == len &&
16706        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16707        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16708     while (*text == '\n') text++;
16709     len = strlen(text);
16710     while (len > 0 && text[len - 1] == '\n') len--;
16711
16712     if (commentList[index] != NULL)
16713       free(commentList[index]);
16714
16715     if (len == 0) {
16716         commentList[index] = NULL;
16717         return;
16718     }
16719   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16720       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16721       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16722     commentList[index] = (char *) malloc(len + 2);
16723     strncpy(commentList[index], text, len);
16724     commentList[index][len] = '\n';
16725     commentList[index][len + 1] = NULLCHAR;
16726   } else {
16727     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16728     char *p;
16729     commentList[index] = (char *) malloc(len + 7);
16730     safeStrCpy(commentList[index], "{\n", 3);
16731     safeStrCpy(commentList[index]+2, text, len+1);
16732     commentList[index][len+2] = NULLCHAR;
16733     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16734     strcat(commentList[index], "\n}\n");
16735   }
16736 }
16737
16738 void
16739 CrushCRs (char *text)
16740 {
16741   char *p = text;
16742   char *q = text;
16743   char ch;
16744
16745   do {
16746     ch = *p++;
16747     if (ch == '\r') continue;
16748     *q++ = ch;
16749   } while (ch != '\0');
16750 }
16751
16752 void
16753 AppendComment (int index, char *text, Boolean addBraces)
16754 /* addBraces  tells if we should add {} */
16755 {
16756     int oldlen, len;
16757     char *old;
16758
16759 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16760     if(addBraces == 3) addBraces = 0; else // force appending literally
16761     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16762
16763     CrushCRs(text);
16764     while (*text == '\n') text++;
16765     len = strlen(text);
16766     while (len > 0 && text[len - 1] == '\n') len--;
16767     text[len] = NULLCHAR;
16768
16769     if (len == 0) return;
16770
16771     if (commentList[index] != NULL) {
16772       Boolean addClosingBrace = addBraces;
16773         old = commentList[index];
16774         oldlen = strlen(old);
16775         while(commentList[index][oldlen-1] ==  '\n')
16776           commentList[index][--oldlen] = NULLCHAR;
16777         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16778         safeStrCpy(commentList[index], old, oldlen + len + 6);
16779         free(old);
16780         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16781         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16782           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16783           while (*text == '\n') { text++; len--; }
16784           commentList[index][--oldlen] = NULLCHAR;
16785       }
16786         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16787         else          strcat(commentList[index], "\n");
16788         strcat(commentList[index], text);
16789         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16790         else          strcat(commentList[index], "\n");
16791     } else {
16792         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16793         if(addBraces)
16794           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16795         else commentList[index][0] = NULLCHAR;
16796         strcat(commentList[index], text);
16797         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16798         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16799     }
16800 }
16801
16802 static char *
16803 FindStr (char * text, char * sub_text)
16804 {
16805     char * result = strstr( text, sub_text );
16806
16807     if( result != NULL ) {
16808         result += strlen( sub_text );
16809     }
16810
16811     return result;
16812 }
16813
16814 /* [AS] Try to extract PV info from PGN comment */
16815 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16816 char *
16817 GetInfoFromComment (int index, char * text)
16818 {
16819     char * sep = text, *p;
16820
16821     if( text != NULL && index > 0 ) {
16822         int score = 0;
16823         int depth = 0;
16824         int time = -1, sec = 0, deci;
16825         char * s_eval = FindStr( text, "[%eval " );
16826         char * s_emt = FindStr( text, "[%emt " );
16827 #if 0
16828         if( s_eval != NULL || s_emt != NULL ) {
16829 #else
16830         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16831 #endif
16832             /* New style */
16833             char delim;
16834
16835             if( s_eval != NULL ) {
16836                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16837                     return text;
16838                 }
16839
16840                 if( delim != ']' ) {
16841                     return text;
16842                 }
16843             }
16844
16845             if( s_emt != NULL ) {
16846             }
16847                 return text;
16848         }
16849         else {
16850             /* We expect something like: [+|-]nnn.nn/dd */
16851             int score_lo = 0;
16852
16853             if(*text != '{') return text; // [HGM] braces: must be normal comment
16854
16855             sep = strchr( text, '/' );
16856             if( sep == NULL || sep < (text+4) ) {
16857                 return text;
16858             }
16859
16860             p = text;
16861             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16862             if(p[1] == '(') { // comment starts with PV
16863                p = strchr(p, ')'); // locate end of PV
16864                if(p == NULL || sep < p+5) return text;
16865                // at this point we have something like "{(.*) +0.23/6 ..."
16866                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16867                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16868                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16869             }
16870             time = -1; sec = -1; deci = -1;
16871             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16872                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16873                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16874                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16875                 return text;
16876             }
16877
16878             if( score_lo < 0 || score_lo >= 100 ) {
16879                 return text;
16880             }
16881
16882             if(sec >= 0) time = 600*time + 10*sec; else
16883             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16884
16885             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16886
16887             /* [HGM] PV time: now locate end of PV info */
16888             while( *++sep >= '0' && *sep <= '9'); // strip depth
16889             if(time >= 0)
16890             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16891             if(sec >= 0)
16892             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16893             if(deci >= 0)
16894             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16895             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16896         }
16897
16898         if( depth <= 0 ) {
16899             return text;
16900         }
16901
16902         if( time < 0 ) {
16903             time = -1;
16904         }
16905
16906         pvInfoList[index-1].depth = depth;
16907         pvInfoList[index-1].score = score;
16908         pvInfoList[index-1].time  = 10*time; // centi-sec
16909         if(*sep == '}') *sep = 0; else *--sep = '{';
16910         if(p != text) {
16911             while(*p++ = *sep++)
16912                                 ;
16913             sep = text;
16914         } // squeeze out space between PV and comment, and return both
16915     }
16916     return sep;
16917 }
16918
16919 void
16920 SendToProgram (char *message, ChessProgramState *cps)
16921 {
16922     int count, outCount, error;
16923     char buf[MSG_SIZ];
16924
16925     if (cps->pr == NoProc) return;
16926     Attention(cps);
16927
16928     if (appData.debugMode) {
16929         TimeMark now;
16930         GetTimeMark(&now);
16931         fprintf(debugFP, "%ld >%-6s: %s",
16932                 SubtractTimeMarks(&now, &programStartTime),
16933                 cps->which, message);
16934         if(serverFP)
16935             fprintf(serverFP, "%ld >%-6s: %s",
16936                 SubtractTimeMarks(&now, &programStartTime),
16937                 cps->which, message), fflush(serverFP);
16938     }
16939
16940     count = strlen(message);
16941     outCount = OutputToProcess(cps->pr, message, count, &error);
16942     if (outCount < count && !exiting
16943                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16944       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16945       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16946         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16947             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16948                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16949                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16950                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16951             } else {
16952                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16953                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16954                 gameInfo.result = res;
16955             }
16956             gameInfo.resultDetails = StrSave(buf);
16957         }
16958         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16959         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16960     }
16961 }
16962
16963 void
16964 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16965 {
16966     char *end_str;
16967     char buf[MSG_SIZ];
16968     ChessProgramState *cps = (ChessProgramState *)closure;
16969
16970     if (isr != cps->isr) return; /* Killed intentionally */
16971     if (count <= 0) {
16972         if (count == 0) {
16973             RemoveInputSource(cps->isr);
16974             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16975                     _(cps->which), cps->program);
16976             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16977             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16978                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16979                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16980                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16981                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16982                 } else {
16983                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16984                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16985                     gameInfo.result = res;
16986                 }
16987                 gameInfo.resultDetails = StrSave(buf);
16988             }
16989             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16990             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16991         } else {
16992             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16993                     _(cps->which), cps->program);
16994             RemoveInputSource(cps->isr);
16995
16996             /* [AS] Program is misbehaving badly... kill it */
16997             if( count == -2 ) {
16998                 DestroyChildProcess( cps->pr, 9 );
16999                 cps->pr = NoProc;
17000             }
17001
17002             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17003         }
17004         return;
17005     }
17006
17007     if ((end_str = strchr(message, '\r')) != NULL)
17008       *end_str = NULLCHAR;
17009     if ((end_str = strchr(message, '\n')) != NULL)
17010       *end_str = NULLCHAR;
17011
17012     if (appData.debugMode) {
17013         TimeMark now; int print = 1;
17014         char *quote = ""; char c; int i;
17015
17016         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17017                 char start = message[0];
17018                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17019                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17020                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17021                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17022                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17023                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17024                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17025                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17026                    sscanf(message, "hint: %c", &c)!=1 &&
17027                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17028                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17029                     print = (appData.engineComments >= 2);
17030                 }
17031                 message[0] = start; // restore original message
17032         }
17033         if(print) {
17034                 GetTimeMark(&now);
17035                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17036                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17037                         quote,
17038                         message);
17039                 if(serverFP)
17040                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17041                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17042                         quote,
17043                         message), fflush(serverFP);
17044         }
17045     }
17046
17047     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17048     if (appData.icsEngineAnalyze) {
17049         if (strstr(message, "whisper") != NULL ||
17050              strstr(message, "kibitz") != NULL ||
17051             strstr(message, "tellics") != NULL) return;
17052     }
17053
17054     HandleMachineMove(message, cps);
17055 }
17056
17057
17058 void
17059 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17060 {
17061     char buf[MSG_SIZ];
17062     int seconds;
17063
17064     if( timeControl_2 > 0 ) {
17065         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17066             tc = timeControl_2;
17067         }
17068     }
17069     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17070     inc /= cps->timeOdds;
17071     st  /= cps->timeOdds;
17072
17073     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17074
17075     if (st > 0) {
17076       /* Set exact time per move, normally using st command */
17077       if (cps->stKludge) {
17078         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17079         seconds = st % 60;
17080         if (seconds == 0) {
17081           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17082         } else {
17083           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17084         }
17085       } else {
17086         snprintf(buf, MSG_SIZ, "st %d\n", st);
17087       }
17088     } else {
17089       /* Set conventional or incremental time control, using level command */
17090       if (seconds == 0) {
17091         /* Note old gnuchess bug -- minutes:seconds used to not work.
17092            Fixed in later versions, but still avoid :seconds
17093            when seconds is 0. */
17094         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17095       } else {
17096         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17097                  seconds, inc/1000.);
17098       }
17099     }
17100     SendToProgram(buf, cps);
17101
17102     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17103     /* Orthogonally, limit search to given depth */
17104     if (sd > 0) {
17105       if (cps->sdKludge) {
17106         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17107       } else {
17108         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17109       }
17110       SendToProgram(buf, cps);
17111     }
17112
17113     if(cps->nps >= 0) { /* [HGM] nps */
17114         if(cps->supportsNPS == FALSE)
17115           cps->nps = -1; // don't use if engine explicitly says not supported!
17116         else {
17117           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17118           SendToProgram(buf, cps);
17119         }
17120     }
17121 }
17122
17123 ChessProgramState *
17124 WhitePlayer ()
17125 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17126 {
17127     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17128        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17129         return &second;
17130     return &first;
17131 }
17132
17133 void
17134 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17135 {
17136     char message[MSG_SIZ];
17137     long time, otime;
17138
17139     /* Note: this routine must be called when the clocks are stopped
17140        or when they have *just* been set or switched; otherwise
17141        it will be off by the time since the current tick started.
17142     */
17143     if (machineWhite) {
17144         time = whiteTimeRemaining / 10;
17145         otime = blackTimeRemaining / 10;
17146     } else {
17147         time = blackTimeRemaining / 10;
17148         otime = whiteTimeRemaining / 10;
17149     }
17150     /* [HGM] translate opponent's time by time-odds factor */
17151     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17152
17153     if (time <= 0) time = 1;
17154     if (otime <= 0) otime = 1;
17155
17156     snprintf(message, MSG_SIZ, "time %ld\n", time);
17157     SendToProgram(message, cps);
17158
17159     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17160     SendToProgram(message, cps);
17161 }
17162
17163 char *
17164 EngineDefinedVariant (ChessProgramState *cps, int n)
17165 {   // return name of n-th unknown variant that engine supports
17166     static char buf[MSG_SIZ];
17167     char *p, *s = cps->variants;
17168     if(!s) return NULL;
17169     do { // parse string from variants feature
17170       VariantClass v;
17171         p = strchr(s, ',');
17172         if(p) *p = NULLCHAR;
17173       v = StringToVariant(s);
17174       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17175         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17176             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17177                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17178                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17179                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17180             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17181         }
17182         if(p) *p++ = ',';
17183         if(n < 0) return buf;
17184     } while(s = p);
17185     return NULL;
17186 }
17187
17188 int
17189 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17190 {
17191   char buf[MSG_SIZ];
17192   int len = strlen(name);
17193   int val;
17194
17195   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17196     (*p) += len + 1;
17197     sscanf(*p, "%d", &val);
17198     *loc = (val != 0);
17199     while (**p && **p != ' ')
17200       (*p)++;
17201     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17202     SendToProgram(buf, cps);
17203     return TRUE;
17204   }
17205   return FALSE;
17206 }
17207
17208 int
17209 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17210 {
17211   char buf[MSG_SIZ];
17212   int len = strlen(name);
17213   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17214     (*p) += len + 1;
17215     sscanf(*p, "%d", loc);
17216     while (**p && **p != ' ') (*p)++;
17217     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17218     SendToProgram(buf, cps);
17219     return TRUE;
17220   }
17221   return FALSE;
17222 }
17223
17224 int
17225 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17226 {
17227   char buf[MSG_SIZ];
17228   int len = strlen(name);
17229   if (strncmp((*p), name, len) == 0
17230       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17231     (*p) += len + 2;
17232     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17233     FREE(*loc); *loc = malloc(len);
17234     strncpy(*loc, *p, len);
17235     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17236     while (**p && **p != '\"') (*p)++;
17237     if (**p == '\"') (*p)++;
17238     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17239     SendToProgram(buf, cps);
17240     return TRUE;
17241   }
17242   return FALSE;
17243 }
17244
17245 int
17246 ParseOption (Option *opt, ChessProgramState *cps)
17247 // [HGM] options: process the string that defines an engine option, and determine
17248 // name, type, default value, and allowed value range
17249 {
17250         char *p, *q, buf[MSG_SIZ];
17251         int n, min = (-1)<<31, max = 1<<31, def;
17252
17253         opt->target = &opt->value;   // OK for spin/slider and checkbox
17254         if(p = strstr(opt->name, " -spin ")) {
17255             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17256             if(max < min) max = min; // enforce consistency
17257             if(def < min) def = min;
17258             if(def > max) def = max;
17259             opt->value = def;
17260             opt->min = min;
17261             opt->max = max;
17262             opt->type = Spin;
17263         } else if((p = strstr(opt->name, " -slider "))) {
17264             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17265             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17266             if(max < min) max = min; // enforce consistency
17267             if(def < min) def = min;
17268             if(def > max) def = max;
17269             opt->value = def;
17270             opt->min = min;
17271             opt->max = max;
17272             opt->type = Spin; // Slider;
17273         } else if((p = strstr(opt->name, " -string "))) {
17274             opt->textValue = p+9;
17275             opt->type = TextBox;
17276             opt->target = &opt->textValue;
17277         } else if((p = strstr(opt->name, " -file "))) {
17278             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17279             opt->target = opt->textValue = p+7;
17280             opt->type = FileName; // FileName;
17281             opt->target = &opt->textValue;
17282         } else if((p = strstr(opt->name, " -path "))) {
17283             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17284             opt->target = opt->textValue = p+7;
17285             opt->type = PathName; // PathName;
17286             opt->target = &opt->textValue;
17287         } else if(p = strstr(opt->name, " -check ")) {
17288             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17289             opt->value = (def != 0);
17290             opt->type = CheckBox;
17291         } else if(p = strstr(opt->name, " -combo ")) {
17292             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17293             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17294             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17295             opt->value = n = 0;
17296             while(q = StrStr(q, " /// ")) {
17297                 n++; *q = 0;    // count choices, and null-terminate each of them
17298                 q += 5;
17299                 if(*q == '*') { // remember default, which is marked with * prefix
17300                     q++;
17301                     opt->value = n;
17302                 }
17303                 cps->comboList[cps->comboCnt++] = q;
17304             }
17305             cps->comboList[cps->comboCnt++] = NULL;
17306             opt->max = n + 1;
17307             opt->type = ComboBox;
17308         } else if(p = strstr(opt->name, " -button")) {
17309             opt->type = Button;
17310         } else if(p = strstr(opt->name, " -save")) {
17311             opt->type = SaveButton;
17312         } else return FALSE;
17313         *p = 0; // terminate option name
17314         // now look if the command-line options define a setting for this engine option.
17315         if(cps->optionSettings && cps->optionSettings[0])
17316             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17317         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17318           snprintf(buf, MSG_SIZ, "option %s", p);
17319                 if(p = strstr(buf, ",")) *p = 0;
17320                 if(q = strchr(buf, '=')) switch(opt->type) {
17321                     case ComboBox:
17322                         for(n=0; n<opt->max; n++)
17323                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17324                         break;
17325                     case TextBox:
17326                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17327                         break;
17328                     case Spin:
17329                     case CheckBox:
17330                         opt->value = atoi(q+1);
17331                     default:
17332                         break;
17333                 }
17334                 strcat(buf, "\n");
17335                 SendToProgram(buf, cps);
17336         }
17337         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17338         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17339         return TRUE;
17340 }
17341
17342 void
17343 FeatureDone (ChessProgramState *cps, int val)
17344 {
17345   DelayedEventCallback cb = GetDelayedEvent();
17346   if ((cb == InitBackEnd3 && cps == &first) ||
17347       (cb == SettingsMenuIfReady && cps == &second) ||
17348       (cb == LoadEngine) ||
17349       (cb == TwoMachinesEventIfReady)) {
17350     CancelDelayedEvent();
17351     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17352   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17353   cps->initDone = val;
17354   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17355 }
17356
17357 /* Parse feature command from engine */
17358 void
17359 ParseFeatures (char *args, ChessProgramState *cps)
17360 {
17361   char *p = args;
17362   char *q = NULL;
17363   int val;
17364   char buf[MSG_SIZ];
17365
17366   for (;;) {
17367     while (*p == ' ') p++;
17368     if (*p == NULLCHAR) return;
17369
17370     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17371     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17372     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17373     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17374     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17375     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17376     if (BoolFeature(&p, "reuse", &val, cps)) {
17377       /* Engine can disable reuse, but can't enable it if user said no */
17378       if (!val) cps->reuse = FALSE;
17379       continue;
17380     }
17381     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17382     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17383       if (gameMode == TwoMachinesPlay) {
17384         DisplayTwoMachinesTitle();
17385       } else {
17386         DisplayTitle("");
17387       }
17388       continue;
17389     }
17390     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17391     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17392     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17393     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17394     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17395     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17396     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17397     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17398     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17399     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17400     if (IntFeature(&p, "done", &val, cps)) {
17401       FeatureDone(cps, val);
17402       continue;
17403     }
17404     /* Added by Tord: */
17405     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17406     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17407     /* End of additions by Tord */
17408
17409     /* [HGM] added features: */
17410     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17411     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17412     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17413     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17414     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17415     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17416     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17417     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17418         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17419         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17420         FREE(cps->option[cps->nrOptions].name);
17421         cps->option[cps->nrOptions].name = q; q = NULL;
17422         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17423           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17424             SendToProgram(buf, cps);
17425             continue;
17426         }
17427         if(cps->nrOptions >= MAX_OPTIONS) {
17428             cps->nrOptions--;
17429             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17430             DisplayError(buf, 0);
17431         }
17432         continue;
17433     }
17434     /* End of additions by HGM */
17435
17436     /* unknown feature: complain and skip */
17437     q = p;
17438     while (*q && *q != '=') q++;
17439     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17440     SendToProgram(buf, cps);
17441     p = q;
17442     if (*p == '=') {
17443       p++;
17444       if (*p == '\"') {
17445         p++;
17446         while (*p && *p != '\"') p++;
17447         if (*p == '\"') p++;
17448       } else {
17449         while (*p && *p != ' ') p++;
17450       }
17451     }
17452   }
17453
17454 }
17455
17456 void
17457 PeriodicUpdatesEvent (int newState)
17458 {
17459     if (newState == appData.periodicUpdates)
17460       return;
17461
17462     appData.periodicUpdates=newState;
17463
17464     /* Display type changes, so update it now */
17465 //    DisplayAnalysis();
17466
17467     /* Get the ball rolling again... */
17468     if (newState) {
17469         AnalysisPeriodicEvent(1);
17470         StartAnalysisClock();
17471     }
17472 }
17473
17474 void
17475 PonderNextMoveEvent (int newState)
17476 {
17477     if (newState == appData.ponderNextMove) return;
17478     if (gameMode == EditPosition) EditPositionDone(TRUE);
17479     if (newState) {
17480         SendToProgram("hard\n", &first);
17481         if (gameMode == TwoMachinesPlay) {
17482             SendToProgram("hard\n", &second);
17483         }
17484     } else {
17485         SendToProgram("easy\n", &first);
17486         thinkOutput[0] = NULLCHAR;
17487         if (gameMode == TwoMachinesPlay) {
17488             SendToProgram("easy\n", &second);
17489         }
17490     }
17491     appData.ponderNextMove = newState;
17492 }
17493
17494 void
17495 NewSettingEvent (int option, int *feature, char *command, int value)
17496 {
17497     char buf[MSG_SIZ];
17498
17499     if (gameMode == EditPosition) EditPositionDone(TRUE);
17500     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17501     if(feature == NULL || *feature) SendToProgram(buf, &first);
17502     if (gameMode == TwoMachinesPlay) {
17503         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17504     }
17505 }
17506
17507 void
17508 ShowThinkingEvent ()
17509 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17510 {
17511     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17512     int newState = appData.showThinking
17513         // [HGM] thinking: other features now need thinking output as well
17514         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17515
17516     if (oldState == newState) return;
17517     oldState = newState;
17518     if (gameMode == EditPosition) EditPositionDone(TRUE);
17519     if (oldState) {
17520         SendToProgram("post\n", &first);
17521         if (gameMode == TwoMachinesPlay) {
17522             SendToProgram("post\n", &second);
17523         }
17524     } else {
17525         SendToProgram("nopost\n", &first);
17526         thinkOutput[0] = NULLCHAR;
17527         if (gameMode == TwoMachinesPlay) {
17528             SendToProgram("nopost\n", &second);
17529         }
17530     }
17531 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17532 }
17533
17534 void
17535 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17536 {
17537   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17538   if (pr == NoProc) return;
17539   AskQuestion(title, question, replyPrefix, pr);
17540 }
17541
17542 void
17543 TypeInEvent (char firstChar)
17544 {
17545     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17546         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17547         gameMode == AnalyzeMode || gameMode == EditGame ||
17548         gameMode == EditPosition || gameMode == IcsExamining ||
17549         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17550         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17551                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17552                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17553         gameMode == Training) PopUpMoveDialog(firstChar);
17554 }
17555
17556 void
17557 TypeInDoneEvent (char *move)
17558 {
17559         Board board;
17560         int n, fromX, fromY, toX, toY;
17561         char promoChar;
17562         ChessMove moveType;
17563
17564         // [HGM] FENedit
17565         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17566                 EditPositionPasteFEN(move);
17567                 return;
17568         }
17569         // [HGM] movenum: allow move number to be typed in any mode
17570         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17571           ToNrEvent(2*n-1);
17572           return;
17573         }
17574         // undocumented kludge: allow command-line option to be typed in!
17575         // (potentially fatal, and does not implement the effect of the option.)
17576         // should only be used for options that are values on which future decisions will be made,
17577         // and definitely not on options that would be used during initialization.
17578         if(strstr(move, "!!! -") == move) {
17579             ParseArgsFromString(move+4);
17580             return;
17581         }
17582
17583       if (gameMode != EditGame && currentMove != forwardMostMove &&
17584         gameMode != Training) {
17585         DisplayMoveError(_("Displayed move is not current"));
17586       } else {
17587         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17588           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17589         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17590         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17591           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17592           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17593         } else {
17594           DisplayMoveError(_("Could not parse move"));
17595         }
17596       }
17597 }
17598
17599 void
17600 DisplayMove (int moveNumber)
17601 {
17602     char message[MSG_SIZ];
17603     char res[MSG_SIZ];
17604     char cpThinkOutput[MSG_SIZ];
17605
17606     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17607
17608     if (moveNumber == forwardMostMove - 1 ||
17609         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17610
17611         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17612
17613         if (strchr(cpThinkOutput, '\n')) {
17614             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17615         }
17616     } else {
17617         *cpThinkOutput = NULLCHAR;
17618     }
17619
17620     /* [AS] Hide thinking from human user */
17621     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17622         *cpThinkOutput = NULLCHAR;
17623         if( thinkOutput[0] != NULLCHAR ) {
17624             int i;
17625
17626             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17627                 cpThinkOutput[i] = '.';
17628             }
17629             cpThinkOutput[i] = NULLCHAR;
17630             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17631         }
17632     }
17633
17634     if (moveNumber == forwardMostMove - 1 &&
17635         gameInfo.resultDetails != NULL) {
17636         if (gameInfo.resultDetails[0] == NULLCHAR) {
17637           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17638         } else {
17639           snprintf(res, MSG_SIZ, " {%s} %s",
17640                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17641         }
17642     } else {
17643         res[0] = NULLCHAR;
17644     }
17645
17646     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17647         DisplayMessage(res, cpThinkOutput);
17648     } else {
17649       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17650                 WhiteOnMove(moveNumber) ? " " : ".. ",
17651                 parseList[moveNumber], res);
17652         DisplayMessage(message, cpThinkOutput);
17653     }
17654 }
17655
17656 void
17657 DisplayComment (int moveNumber, char *text)
17658 {
17659     char title[MSG_SIZ];
17660
17661     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17662       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17663     } else {
17664       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17665               WhiteOnMove(moveNumber) ? " " : ".. ",
17666               parseList[moveNumber]);
17667     }
17668     if (text != NULL && (appData.autoDisplayComment || commentUp))
17669         CommentPopUp(title, text);
17670 }
17671
17672 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17673  * might be busy thinking or pondering.  It can be omitted if your
17674  * gnuchess is configured to stop thinking immediately on any user
17675  * input.  However, that gnuchess feature depends on the FIONREAD
17676  * ioctl, which does not work properly on some flavors of Unix.
17677  */
17678 void
17679 Attention (ChessProgramState *cps)
17680 {
17681 #if ATTENTION
17682     if (!cps->useSigint) return;
17683     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17684     switch (gameMode) {
17685       case MachinePlaysWhite:
17686       case MachinePlaysBlack:
17687       case TwoMachinesPlay:
17688       case IcsPlayingWhite:
17689       case IcsPlayingBlack:
17690       case AnalyzeMode:
17691       case AnalyzeFile:
17692         /* Skip if we know it isn't thinking */
17693         if (!cps->maybeThinking) return;
17694         if (appData.debugMode)
17695           fprintf(debugFP, "Interrupting %s\n", cps->which);
17696         InterruptChildProcess(cps->pr);
17697         cps->maybeThinking = FALSE;
17698         break;
17699       default:
17700         break;
17701     }
17702 #endif /*ATTENTION*/
17703 }
17704
17705 int
17706 CheckFlags ()
17707 {
17708     if (whiteTimeRemaining <= 0) {
17709         if (!whiteFlag) {
17710             whiteFlag = TRUE;
17711             if (appData.icsActive) {
17712                 if (appData.autoCallFlag &&
17713                     gameMode == IcsPlayingBlack && !blackFlag) {
17714                   SendToICS(ics_prefix);
17715                   SendToICS("flag\n");
17716                 }
17717             } else {
17718                 if (blackFlag) {
17719                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17720                 } else {
17721                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17722                     if (appData.autoCallFlag) {
17723                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17724                         return TRUE;
17725                     }
17726                 }
17727             }
17728         }
17729     }
17730     if (blackTimeRemaining <= 0) {
17731         if (!blackFlag) {
17732             blackFlag = TRUE;
17733             if (appData.icsActive) {
17734                 if (appData.autoCallFlag &&
17735                     gameMode == IcsPlayingWhite && !whiteFlag) {
17736                   SendToICS(ics_prefix);
17737                   SendToICS("flag\n");
17738                 }
17739             } else {
17740                 if (whiteFlag) {
17741                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17742                 } else {
17743                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17744                     if (appData.autoCallFlag) {
17745                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17746                         return TRUE;
17747                     }
17748                 }
17749             }
17750         }
17751     }
17752     return FALSE;
17753 }
17754
17755 void
17756 CheckTimeControl ()
17757 {
17758     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17759         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17760
17761     /*
17762      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17763      */
17764     if ( !WhiteOnMove(forwardMostMove) ) {
17765         /* White made time control */
17766         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17767         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17768         /* [HGM] time odds: correct new time quota for time odds! */
17769                                             / WhitePlayer()->timeOdds;
17770         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17771     } else {
17772         lastBlack -= blackTimeRemaining;
17773         /* Black made time control */
17774         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17775                                             / WhitePlayer()->other->timeOdds;
17776         lastWhite = whiteTimeRemaining;
17777     }
17778 }
17779
17780 void
17781 DisplayBothClocks ()
17782 {
17783     int wom = gameMode == EditPosition ?
17784       !blackPlaysFirst : WhiteOnMove(currentMove);
17785     DisplayWhiteClock(whiteTimeRemaining, wom);
17786     DisplayBlackClock(blackTimeRemaining, !wom);
17787 }
17788
17789
17790 /* Timekeeping seems to be a portability nightmare.  I think everyone
17791    has ftime(), but I'm really not sure, so I'm including some ifdefs
17792    to use other calls if you don't.  Clocks will be less accurate if
17793    you have neither ftime nor gettimeofday.
17794 */
17795
17796 /* VS 2008 requires the #include outside of the function */
17797 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17798 #include <sys/timeb.h>
17799 #endif
17800
17801 /* Get the current time as a TimeMark */
17802 void
17803 GetTimeMark (TimeMark *tm)
17804 {
17805 #if HAVE_GETTIMEOFDAY
17806
17807     struct timeval timeVal;
17808     struct timezone timeZone;
17809
17810     gettimeofday(&timeVal, &timeZone);
17811     tm->sec = (long) timeVal.tv_sec;
17812     tm->ms = (int) (timeVal.tv_usec / 1000L);
17813
17814 #else /*!HAVE_GETTIMEOFDAY*/
17815 #if HAVE_FTIME
17816
17817 // include <sys/timeb.h> / moved to just above start of function
17818     struct timeb timeB;
17819
17820     ftime(&timeB);
17821     tm->sec = (long) timeB.time;
17822     tm->ms = (int) timeB.millitm;
17823
17824 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17825     tm->sec = (long) time(NULL);
17826     tm->ms = 0;
17827 #endif
17828 #endif
17829 }
17830
17831 /* Return the difference in milliseconds between two
17832    time marks.  We assume the difference will fit in a long!
17833 */
17834 long
17835 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17836 {
17837     return 1000L*(tm2->sec - tm1->sec) +
17838            (long) (tm2->ms - tm1->ms);
17839 }
17840
17841
17842 /*
17843  * Code to manage the game clocks.
17844  *
17845  * In tournament play, black starts the clock and then white makes a move.
17846  * We give the human user a slight advantage if he is playing white---the
17847  * clocks don't run until he makes his first move, so it takes zero time.
17848  * Also, we don't account for network lag, so we could get out of sync
17849  * with GNU Chess's clock -- but then, referees are always right.
17850  */
17851
17852 static TimeMark tickStartTM;
17853 static long intendedTickLength;
17854
17855 long
17856 NextTickLength (long timeRemaining)
17857 {
17858     long nominalTickLength, nextTickLength;
17859
17860     if (timeRemaining > 0L && timeRemaining <= 10000L)
17861       nominalTickLength = 100L;
17862     else
17863       nominalTickLength = 1000L;
17864     nextTickLength = timeRemaining % nominalTickLength;
17865     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17866
17867     return nextTickLength;
17868 }
17869
17870 /* Adjust clock one minute up or down */
17871 void
17872 AdjustClock (Boolean which, int dir)
17873 {
17874     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17875     if(which) blackTimeRemaining += 60000*dir;
17876     else      whiteTimeRemaining += 60000*dir;
17877     DisplayBothClocks();
17878     adjustedClock = TRUE;
17879 }
17880
17881 /* Stop clocks and reset to a fresh time control */
17882 void
17883 ResetClocks ()
17884 {
17885     (void) StopClockTimer();
17886     if (appData.icsActive) {
17887         whiteTimeRemaining = blackTimeRemaining = 0;
17888     } else if (searchTime) {
17889         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17890         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17891     } else { /* [HGM] correct new time quote for time odds */
17892         whiteTC = blackTC = fullTimeControlString;
17893         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17894         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17895     }
17896     if (whiteFlag || blackFlag) {
17897         DisplayTitle("");
17898         whiteFlag = blackFlag = FALSE;
17899     }
17900     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17901     DisplayBothClocks();
17902     adjustedClock = FALSE;
17903 }
17904
17905 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17906
17907 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17908
17909 /* Decrement running clock by amount of time that has passed */
17910 void
17911 DecrementClocks ()
17912 {
17913     long tRemaining;
17914     long lastTickLength, fudge;
17915     TimeMark now;
17916
17917     if (!appData.clockMode) return;
17918     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17919
17920     GetTimeMark(&now);
17921
17922     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17923
17924     /* Fudge if we woke up a little too soon */
17925     fudge = intendedTickLength - lastTickLength;
17926     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17927
17928     if (WhiteOnMove(forwardMostMove)) {
17929         if(whiteNPS >= 0) lastTickLength = 0;
17930          tRemaining = whiteTimeRemaining -= lastTickLength;
17931         if( tRemaining < 0 && !appData.icsActive) {
17932             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17933             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17934                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17935                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17936             }
17937         }
17938         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17939         DisplayWhiteClock(whiteTimeRemaining - fudge,
17940                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17941         timeSuffix = 0;
17942     } else {
17943         if(blackNPS >= 0) lastTickLength = 0;
17944          tRemaining = blackTimeRemaining -= lastTickLength;
17945         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17946             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17947             if(suddenDeath) {
17948                 blackStartMove = forwardMostMove;
17949                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17950             }
17951         }
17952         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17953         DisplayBlackClock(blackTimeRemaining - fudge,
17954                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17955         timeSuffix = 0;
17956     }
17957     if (CheckFlags()) return;
17958
17959     if(twoBoards) { // count down secondary board's clocks as well
17960         activePartnerTime -= lastTickLength;
17961         partnerUp = 1;
17962         if(activePartner == 'W')
17963             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17964         else
17965             DisplayBlackClock(activePartnerTime, TRUE);
17966         partnerUp = 0;
17967     }
17968
17969     tickStartTM = now;
17970     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17971     StartClockTimer(intendedTickLength);
17972
17973     /* if the time remaining has fallen below the alarm threshold, sound the
17974      * alarm. if the alarm has sounded and (due to a takeback or time control
17975      * with increment) the time remaining has increased to a level above the
17976      * threshold, reset the alarm so it can sound again.
17977      */
17978
17979     if (appData.icsActive && appData.icsAlarm) {
17980
17981         /* make sure we are dealing with the user's clock */
17982         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17983                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17984            )) return;
17985
17986         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17987             alarmSounded = FALSE;
17988         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17989             PlayAlarmSound();
17990             alarmSounded = TRUE;
17991         }
17992     }
17993 }
17994
17995
17996 /* A player has just moved, so stop the previously running
17997    clock and (if in clock mode) start the other one.
17998    We redisplay both clocks in case we're in ICS mode, because
17999    ICS gives us an update to both clocks after every move.
18000    Note that this routine is called *after* forwardMostMove
18001    is updated, so the last fractional tick must be subtracted
18002    from the color that is *not* on move now.
18003 */
18004 void
18005 SwitchClocks (int newMoveNr)
18006 {
18007     long lastTickLength;
18008     TimeMark now;
18009     int flagged = FALSE;
18010
18011     GetTimeMark(&now);
18012
18013     if (StopClockTimer() && appData.clockMode) {
18014         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18015         if (!WhiteOnMove(forwardMostMove)) {
18016             if(blackNPS >= 0) lastTickLength = 0;
18017             blackTimeRemaining -= lastTickLength;
18018            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18019 //         if(pvInfoList[forwardMostMove].time == -1)
18020                  pvInfoList[forwardMostMove].time =               // use GUI time
18021                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18022         } else {
18023            if(whiteNPS >= 0) lastTickLength = 0;
18024            whiteTimeRemaining -= lastTickLength;
18025            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18026 //         if(pvInfoList[forwardMostMove].time == -1)
18027                  pvInfoList[forwardMostMove].time =
18028                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18029         }
18030         flagged = CheckFlags();
18031     }
18032     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18033     CheckTimeControl();
18034
18035     if (flagged || !appData.clockMode) return;
18036
18037     switch (gameMode) {
18038       case MachinePlaysBlack:
18039       case MachinePlaysWhite:
18040       case BeginningOfGame:
18041         if (pausing) return;
18042         break;
18043
18044       case EditGame:
18045       case PlayFromGameFile:
18046       case IcsExamining:
18047         return;
18048
18049       default:
18050         break;
18051     }
18052
18053     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18054         if(WhiteOnMove(forwardMostMove))
18055              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18056         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18057     }
18058
18059     tickStartTM = now;
18060     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18061       whiteTimeRemaining : blackTimeRemaining);
18062     StartClockTimer(intendedTickLength);
18063 }
18064
18065
18066 /* Stop both clocks */
18067 void
18068 StopClocks ()
18069 {
18070     long lastTickLength;
18071     TimeMark now;
18072
18073     if (!StopClockTimer()) return;
18074     if (!appData.clockMode) return;
18075
18076     GetTimeMark(&now);
18077
18078     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18079     if (WhiteOnMove(forwardMostMove)) {
18080         if(whiteNPS >= 0) lastTickLength = 0;
18081         whiteTimeRemaining -= lastTickLength;
18082         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18083     } else {
18084         if(blackNPS >= 0) lastTickLength = 0;
18085         blackTimeRemaining -= lastTickLength;
18086         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18087     }
18088     CheckFlags();
18089 }
18090
18091 /* Start clock of player on move.  Time may have been reset, so
18092    if clock is already running, stop and restart it. */
18093 void
18094 StartClocks ()
18095 {
18096     (void) StopClockTimer(); /* in case it was running already */
18097     DisplayBothClocks();
18098     if (CheckFlags()) return;
18099
18100     if (!appData.clockMode) return;
18101     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18102
18103     GetTimeMark(&tickStartTM);
18104     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18105       whiteTimeRemaining : blackTimeRemaining);
18106
18107    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18108     whiteNPS = blackNPS = -1;
18109     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18110        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18111         whiteNPS = first.nps;
18112     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18113        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18114         blackNPS = first.nps;
18115     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18116         whiteNPS = second.nps;
18117     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18118         blackNPS = second.nps;
18119     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18120
18121     StartClockTimer(intendedTickLength);
18122 }
18123
18124 char *
18125 TimeString (long ms)
18126 {
18127     long second, minute, hour, day;
18128     char *sign = "";
18129     static char buf[40], moveTime[8];
18130
18131     if (ms > 0 && ms <= 9900) {
18132       /* convert milliseconds to tenths, rounding up */
18133       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18134
18135       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18136       return buf;
18137     }
18138
18139     /* convert milliseconds to seconds, rounding up */
18140     /* use floating point to avoid strangeness of integer division
18141        with negative dividends on many machines */
18142     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18143
18144     if (second < 0) {
18145         sign = "-";
18146         second = -second;
18147     }
18148
18149     day = second / (60 * 60 * 24);
18150     second = second % (60 * 60 * 24);
18151     hour = second / (60 * 60);
18152     second = second % (60 * 60);
18153     minute = second / 60;
18154     second = second % 60;
18155
18156     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18157     else *moveTime = NULLCHAR;
18158
18159     if (day > 0)
18160       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18161               sign, day, hour, minute, second, moveTime);
18162     else if (hour > 0)
18163       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18164     else
18165       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18166
18167     return buf;
18168 }
18169
18170
18171 /*
18172  * This is necessary because some C libraries aren't ANSI C compliant yet.
18173  */
18174 char *
18175 StrStr (char *string, char *match)
18176 {
18177     int i, length;
18178
18179     length = strlen(match);
18180
18181     for (i = strlen(string) - length; i >= 0; i--, string++)
18182       if (!strncmp(match, string, length))
18183         return string;
18184
18185     return NULL;
18186 }
18187
18188 char *
18189 StrCaseStr (char *string, char *match)
18190 {
18191     int i, j, length;
18192
18193     length = strlen(match);
18194
18195     for (i = strlen(string) - length; i >= 0; i--, string++) {
18196         for (j = 0; j < length; j++) {
18197             if (ToLower(match[j]) != ToLower(string[j]))
18198               break;
18199         }
18200         if (j == length) return string;
18201     }
18202
18203     return NULL;
18204 }
18205
18206 #ifndef _amigados
18207 int
18208 StrCaseCmp (char *s1, char *s2)
18209 {
18210     char c1, c2;
18211
18212     for (;;) {
18213         c1 = ToLower(*s1++);
18214         c2 = ToLower(*s2++);
18215         if (c1 > c2) return 1;
18216         if (c1 < c2) return -1;
18217         if (c1 == NULLCHAR) return 0;
18218     }
18219 }
18220
18221
18222 int
18223 ToLower (int c)
18224 {
18225     return isupper(c) ? tolower(c) : c;
18226 }
18227
18228
18229 int
18230 ToUpper (int c)
18231 {
18232     return islower(c) ? toupper(c) : c;
18233 }
18234 #endif /* !_amigados    */
18235
18236 char *
18237 StrSave (char *s)
18238 {
18239   char *ret;
18240
18241   if ((ret = (char *) malloc(strlen(s) + 1)))
18242     {
18243       safeStrCpy(ret, s, strlen(s)+1);
18244     }
18245   return ret;
18246 }
18247
18248 char *
18249 StrSavePtr (char *s, char **savePtr)
18250 {
18251     if (*savePtr) {
18252         free(*savePtr);
18253     }
18254     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18255       safeStrCpy(*savePtr, s, strlen(s)+1);
18256     }
18257     return(*savePtr);
18258 }
18259
18260 char *
18261 PGNDate ()
18262 {
18263     time_t clock;
18264     struct tm *tm;
18265     char buf[MSG_SIZ];
18266
18267     clock = time((time_t *)NULL);
18268     tm = localtime(&clock);
18269     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18270             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18271     return StrSave(buf);
18272 }
18273
18274
18275 char *
18276 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18277 {
18278     int i, j, fromX, fromY, toX, toY;
18279     int whiteToPlay, haveRights = nrCastlingRights;
18280     char buf[MSG_SIZ];
18281     char *p, *q;
18282     int emptycount;
18283     ChessSquare piece;
18284
18285     whiteToPlay = (gameMode == EditPosition) ?
18286       !blackPlaysFirst : (move % 2 == 0);
18287     p = buf;
18288
18289     /* Piece placement data */
18290     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18291         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18292         emptycount = 0;
18293         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18294             if (boards[move][i][j] == EmptySquare) {
18295                 emptycount++;
18296             } else { ChessSquare piece = boards[move][i][j];
18297                 if (emptycount > 0) {
18298                     if(emptycount<10) /* [HGM] can be >= 10 */
18299                         *p++ = '0' + emptycount;
18300                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18301                     emptycount = 0;
18302                 }
18303                 if(PieceToChar(piece) == '+') {
18304                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18305                     *p++ = '+';
18306                     piece = (ChessSquare)(CHUDEMOTED(piece));
18307                 }
18308                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18309                 if(*p = PieceSuffix(piece)) p++;
18310                 if(p[-1] == '~') {
18311                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18312                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18313                     *p++ = '~';
18314                 }
18315             }
18316         }
18317         if (emptycount > 0) {
18318             if(emptycount<10) /* [HGM] can be >= 10 */
18319                 *p++ = '0' + emptycount;
18320             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18321             emptycount = 0;
18322         }
18323         *p++ = '/';
18324     }
18325     *(p - 1) = ' ';
18326
18327     /* [HGM] print Crazyhouse or Shogi holdings */
18328     if( gameInfo.holdingsWidth ) {
18329         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18330         q = p;
18331         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18332             piece = boards[move][i][BOARD_WIDTH-1];
18333             if( piece != EmptySquare )
18334               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18335                   *p++ = PieceToChar(piece);
18336         }
18337         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18338             piece = boards[move][BOARD_HEIGHT-i-1][0];
18339             if( piece != EmptySquare )
18340               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18341                   *p++ = PieceToChar(piece);
18342         }
18343
18344         if( q == p ) *p++ = '-';
18345         *p++ = ']';
18346         *p++ = ' ';
18347     }
18348
18349     /* Active color */
18350     *p++ = whiteToPlay ? 'w' : 'b';
18351     *p++ = ' ';
18352
18353   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18354     haveRights = 0; q = p;
18355     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18356       piece = boards[move][0][i];
18357       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18358         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18359       }
18360     }
18361     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18362       piece = boards[move][BOARD_HEIGHT-1][i];
18363       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18364         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18365       }
18366     }
18367     if(p == q) *p++ = '-';
18368     *p++ = ' ';
18369   }
18370
18371   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18372     while(*p++ = *q++)
18373                       ;
18374     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18375   } else {
18376   if(haveRights) {
18377      int handW=0, handB=0;
18378      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18379         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18380         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18381      }
18382      q = p;
18383      if(appData.fischerCastling) {
18384         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18385            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18386                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18387         } else {
18388        /* [HGM] write directly from rights */
18389            if(boards[move][CASTLING][2] != NoRights &&
18390               boards[move][CASTLING][0] != NoRights   )
18391                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18392            if(boards[move][CASTLING][2] != NoRights &&
18393               boards[move][CASTLING][1] != NoRights   )
18394                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18395         }
18396         if(handB) {
18397            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18398                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18399         } else {
18400            if(boards[move][CASTLING][5] != NoRights &&
18401               boards[move][CASTLING][3] != NoRights   )
18402                 *p++ = boards[move][CASTLING][3] + AAA;
18403            if(boards[move][CASTLING][5] != NoRights &&
18404               boards[move][CASTLING][4] != NoRights   )
18405                 *p++ = boards[move][CASTLING][4] + AAA;
18406         }
18407      } else {
18408
18409         /* [HGM] write true castling rights */
18410         if( nrCastlingRights == 6 ) {
18411             int q, k=0;
18412             if(boards[move][CASTLING][0] != NoRights &&
18413                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18414             q = (boards[move][CASTLING][1] != NoRights &&
18415                  boards[move][CASTLING][2] != NoRights  );
18416             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18417                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18418                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18419                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18420             }
18421             if(q) *p++ = 'Q';
18422             k = 0;
18423             if(boards[move][CASTLING][3] != NoRights &&
18424                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18425             q = (boards[move][CASTLING][4] != NoRights &&
18426                  boards[move][CASTLING][5] != NoRights  );
18427             if(handB) {
18428                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18429                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18430                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18431             }
18432             if(q) *p++ = 'q';
18433         }
18434      }
18435      if (q == p) *p++ = '-'; /* No castling rights */
18436      *p++ = ' ';
18437   }
18438
18439   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18440      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18441      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18442     /* En passant target square */
18443     if (move > backwardMostMove) {
18444         fromX = moveList[move - 1][0] - AAA;
18445         fromY = moveList[move - 1][1] - ONE;
18446         toX = moveList[move - 1][2] - AAA;
18447         toY = moveList[move - 1][3] - ONE;
18448         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18449             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18450             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18451             fromX == toX) {
18452             /* 2-square pawn move just happened */
18453             *p++ = toX + AAA;
18454             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18455         } else {
18456             *p++ = '-';
18457         }
18458     } else if(move == backwardMostMove) {
18459         // [HGM] perhaps we should always do it like this, and forget the above?
18460         if((signed char)boards[move][EP_STATUS] >= 0) {
18461             *p++ = boards[move][EP_STATUS] + AAA;
18462             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18463         } else {
18464             *p++ = '-';
18465         }
18466     } else {
18467         *p++ = '-';
18468     }
18469     *p++ = ' ';
18470   }
18471   }
18472
18473     i = boards[move][CHECK_COUNT];
18474     if(i) {
18475         sprintf(p, "%d+%d ", i&255, i>>8);
18476         while(*p) p++;
18477     }
18478
18479     if(moveCounts)
18480     {   int i = 0, j=move;
18481
18482         /* [HGM] find reversible plies */
18483         if (appData.debugMode) { int k;
18484             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18485             for(k=backwardMostMove; k<=forwardMostMove; k++)
18486                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18487
18488         }
18489
18490         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18491         if( j == backwardMostMove ) i += initialRulePlies;
18492         sprintf(p, "%d ", i);
18493         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18494
18495         /* Fullmove number */
18496         sprintf(p, "%d", (move / 2) + 1);
18497     } else *--p = NULLCHAR;
18498
18499     return StrSave(buf);
18500 }
18501
18502 Boolean
18503 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18504 {
18505     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18506     char *p, c;
18507     int emptycount, virgin[BOARD_FILES];
18508     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18509
18510     p = fen;
18511
18512     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18513
18514     /* Piece placement data */
18515     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18516         j = 0;
18517         for (;;) {
18518             if (*p == '/' || *p == ' ' || *p == '[' ) {
18519                 if(j > w) w = j;
18520                 emptycount = gameInfo.boardWidth - j;
18521                 while (emptycount--)
18522                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18523                 if (*p == '/') p++;
18524                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18525                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18526                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18527                     }
18528                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18529                 }
18530                 break;
18531 #if(BOARD_FILES >= 10)*0
18532             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18533                 p++; emptycount=10;
18534                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18535                 while (emptycount--)
18536                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18537 #endif
18538             } else if (*p == '*') {
18539                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18540             } else if (isdigit(*p)) {
18541                 emptycount = *p++ - '0';
18542                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18543                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18544                 while (emptycount--)
18545                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18546             } else if (*p == '<') {
18547                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18548                 else if (i != 0 || !shuffle) return FALSE;
18549                 p++;
18550             } else if (shuffle && *p == '>') {
18551                 p++; // for now ignore closing shuffle range, and assume rank-end
18552             } else if (*p == '?') {
18553                 if (j >= gameInfo.boardWidth) return FALSE;
18554                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18555                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18556             } else if (*p == '+' || isalpha(*p)) {
18557                 char *q, *s = SUFFIXES;
18558                 if (j >= gameInfo.boardWidth) return FALSE;
18559                 if(*p=='+') {
18560                     char c = *++p;
18561                     if(q = strchr(s, p[1])) p++;
18562                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18563                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18564                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18565                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18566                 } else {
18567                     char c = *p++;
18568                     if(q = strchr(s, *p)) p++;
18569                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18570                 }
18571
18572                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18573                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18574                     piece = (ChessSquare) (PROMOTED(piece));
18575                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18576                     p++;
18577                 }
18578                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18579                 if(piece == king) wKingRank = i;
18580                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18581             } else {
18582                 return FALSE;
18583             }
18584         }
18585     }
18586     while (*p == '/' || *p == ' ') p++;
18587
18588     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18589
18590     /* [HGM] by default clear Crazyhouse holdings, if present */
18591     if(gameInfo.holdingsWidth) {
18592        for(i=0; i<BOARD_HEIGHT; i++) {
18593            board[i][0]             = EmptySquare; /* black holdings */
18594            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18595            board[i][1]             = (ChessSquare) 0; /* black counts */
18596            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18597        }
18598     }
18599
18600     /* [HGM] look for Crazyhouse holdings here */
18601     while(*p==' ') p++;
18602     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18603         int swap=0, wcnt=0, bcnt=0;
18604         if(*p == '[') p++;
18605         if(*p == '<') swap++, p++;
18606         if(*p == '-' ) p++; /* empty holdings */ else {
18607             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18608             /* if we would allow FEN reading to set board size, we would   */
18609             /* have to add holdings and shift the board read so far here   */
18610             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18611                 p++;
18612                 if((int) piece >= (int) BlackPawn ) {
18613                     i = (int)piece - (int)BlackPawn;
18614                     i = PieceToNumber((ChessSquare)i);
18615                     if( i >= gameInfo.holdingsSize ) return FALSE;
18616                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18617                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18618                     bcnt++;
18619                 } else {
18620                     i = (int)piece - (int)WhitePawn;
18621                     i = PieceToNumber((ChessSquare)i);
18622                     if( i >= gameInfo.holdingsSize ) return FALSE;
18623                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18624                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18625                     wcnt++;
18626                 }
18627             }
18628             if(subst) { // substitute back-rank question marks by holdings pieces
18629                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18630                     int k, m, n = bcnt + 1;
18631                     if(board[0][j] == ClearBoard) {
18632                         if(!wcnt) return FALSE;
18633                         n = rand() % wcnt;
18634                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18635                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18636                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18637                             break;
18638                         }
18639                     }
18640                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18641                         if(!bcnt) return FALSE;
18642                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18643                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18644                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18645                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18646                             break;
18647                         }
18648                     }
18649                 }
18650                 subst = 0;
18651             }
18652         }
18653         if(*p == ']') p++;
18654     }
18655
18656     if(subst) return FALSE; // substitution requested, but no holdings
18657
18658     while(*p == ' ') p++;
18659
18660     /* Active color */
18661     c = *p++;
18662     if(appData.colorNickNames) {
18663       if( c == appData.colorNickNames[0] ) c = 'w'; else
18664       if( c == appData.colorNickNames[1] ) c = 'b';
18665     }
18666     switch (c) {
18667       case 'w':
18668         *blackPlaysFirst = FALSE;
18669         break;
18670       case 'b':
18671         *blackPlaysFirst = TRUE;
18672         break;
18673       default:
18674         return FALSE;
18675     }
18676
18677     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18678     /* return the extra info in global variiables             */
18679
18680     while(*p==' ') p++;
18681
18682     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18683         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18684         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18685     }
18686
18687     /* set defaults in case FEN is incomplete */
18688     board[EP_STATUS] = EP_UNKNOWN;
18689     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18690     for(i=0; i<nrCastlingRights; i++ ) {
18691         board[CASTLING][i] =
18692             appData.fischerCastling ? NoRights : initialRights[i];
18693     }   /* assume possible unless obviously impossible */
18694     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18695     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18696     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18697                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18698     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18699     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18700     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18701                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18702     FENrulePlies = 0;
18703
18704     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18705       char *q = p;
18706       int w=0, b=0;
18707       while(isalpha(*p)) {
18708         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18709         if(islower(*p)) b |= 1 << (*p++ - 'a');
18710       }
18711       if(*p == '-') p++;
18712       if(p != q) {
18713         board[TOUCHED_W] = ~w;
18714         board[TOUCHED_B] = ~b;
18715         while(*p == ' ') p++;
18716       }
18717     } else
18718
18719     if(nrCastlingRights) {
18720       int fischer = 0;
18721       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18722       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18723           /* castling indicator present, so default becomes no castlings */
18724           for(i=0; i<nrCastlingRights; i++ ) {
18725                  board[CASTLING][i] = NoRights;
18726           }
18727       }
18728       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18729              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18730              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18731              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18732         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18733
18734         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18735             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18736             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18737         }
18738         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18739             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18740         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18741                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18742         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18743                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18744         switch(c) {
18745           case'K':
18746               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18747               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18748               board[CASTLING][2] = whiteKingFile;
18749               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18750               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18751               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18752               break;
18753           case'Q':
18754               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18755               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18756               board[CASTLING][2] = whiteKingFile;
18757               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18758               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18759               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18760               break;
18761           case'k':
18762               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18763               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18764               board[CASTLING][5] = blackKingFile;
18765               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18766               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18767               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18768               break;
18769           case'q':
18770               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18771               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18772               board[CASTLING][5] = blackKingFile;
18773               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18774               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18775               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18776           case '-':
18777               break;
18778           default: /* FRC castlings */
18779               if(c >= 'a') { /* black rights */
18780                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18781                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18782                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18783                   if(i == BOARD_RGHT) break;
18784                   board[CASTLING][5] = i;
18785                   c -= AAA;
18786                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18787                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18788                   if(c > i)
18789                       board[CASTLING][3] = c;
18790                   else
18791                       board[CASTLING][4] = c;
18792               } else { /* white rights */
18793                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18794                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18795                     if(board[0][i] == WhiteKing) break;
18796                   if(i == BOARD_RGHT) break;
18797                   board[CASTLING][2] = i;
18798                   c -= AAA - 'a' + 'A';
18799                   if(board[0][c] >= WhiteKing) break;
18800                   if(c > i)
18801                       board[CASTLING][0] = c;
18802                   else
18803                       board[CASTLING][1] = c;
18804               }
18805         }
18806       }
18807       for(i=0; i<nrCastlingRights; i++)
18808         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18809       if(gameInfo.variant == VariantSChess)
18810         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18811       if(fischer && shuffle) appData.fischerCastling = TRUE;
18812     if (appData.debugMode) {
18813         fprintf(debugFP, "FEN castling rights:");
18814         for(i=0; i<nrCastlingRights; i++)
18815         fprintf(debugFP, " %d", board[CASTLING][i]);
18816         fprintf(debugFP, "\n");
18817     }
18818
18819       while(*p==' ') p++;
18820     }
18821
18822     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18823
18824     /* read e.p. field in games that know e.p. capture */
18825     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18826        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18827        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18828       if(*p=='-') {
18829         p++; board[EP_STATUS] = EP_NONE;
18830       } else {
18831          char c = *p++ - AAA;
18832
18833          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18834          if(*p >= '0' && *p <='9') p++;
18835          board[EP_STATUS] = c;
18836       }
18837     }
18838
18839     while(*p == ' ') p++;
18840
18841     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18842     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18843         board[CHECK_COUNT] = i + 256*j;
18844         while(*p && *p != ' ') p++;
18845     }
18846
18847     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18848     if(c > 0) {
18849         FENrulePlies = i; /* 50-move ply counter */
18850         /* (The move number is still ignored)    */
18851         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18852     }
18853
18854     return TRUE;
18855 }
18856
18857 void
18858 EditPositionPasteFEN (char *fen)
18859 {
18860   if (fen != NULL) {
18861     Board initial_position;
18862
18863     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18864       DisplayError(_("Bad FEN position in clipboard"), 0);
18865       return ;
18866     } else {
18867       int savedBlackPlaysFirst = blackPlaysFirst;
18868       EditPositionEvent();
18869       blackPlaysFirst = savedBlackPlaysFirst;
18870       CopyBoard(boards[0], initial_position);
18871       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18872       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18873       DisplayBothClocks();
18874       DrawPosition(FALSE, boards[currentMove]);
18875     }
18876   }
18877 }
18878
18879 static char cseq[12] = "\\   ";
18880
18881 Boolean
18882 set_cont_sequence (char *new_seq)
18883 {
18884     int len;
18885     Boolean ret;
18886
18887     // handle bad attempts to set the sequence
18888         if (!new_seq)
18889                 return 0; // acceptable error - no debug
18890
18891     len = strlen(new_seq);
18892     ret = (len > 0) && (len < sizeof(cseq));
18893     if (ret)
18894       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18895     else if (appData.debugMode)
18896       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18897     return ret;
18898 }
18899
18900 /*
18901     reformat a source message so words don't cross the width boundary.  internal
18902     newlines are not removed.  returns the wrapped size (no null character unless
18903     included in source message).  If dest is NULL, only calculate the size required
18904     for the dest buffer.  lp argument indicats line position upon entry, and it's
18905     passed back upon exit.
18906 */
18907 int
18908 wrap (char *dest, char *src, int count, int width, int *lp)
18909 {
18910     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18911
18912     cseq_len = strlen(cseq);
18913     old_line = line = *lp;
18914     ansi = len = clen = 0;
18915
18916     for (i=0; i < count; i++)
18917     {
18918         if (src[i] == '\033')
18919             ansi = 1;
18920
18921         // if we hit the width, back up
18922         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18923         {
18924             // store i & len in case the word is too long
18925             old_i = i, old_len = len;
18926
18927             // find the end of the last word
18928             while (i && src[i] != ' ' && src[i] != '\n')
18929             {
18930                 i--;
18931                 len--;
18932             }
18933
18934             // word too long?  restore i & len before splitting it
18935             if ((old_i-i+clen) >= width)
18936             {
18937                 i = old_i;
18938                 len = old_len;
18939             }
18940
18941             // extra space?
18942             if (i && src[i-1] == ' ')
18943                 len--;
18944
18945             if (src[i] != ' ' && src[i] != '\n')
18946             {
18947                 i--;
18948                 if (len)
18949                     len--;
18950             }
18951
18952             // now append the newline and continuation sequence
18953             if (dest)
18954                 dest[len] = '\n';
18955             len++;
18956             if (dest)
18957                 strncpy(dest+len, cseq, cseq_len);
18958             len += cseq_len;
18959             line = cseq_len;
18960             clen = cseq_len;
18961             continue;
18962         }
18963
18964         if (dest)
18965             dest[len] = src[i];
18966         len++;
18967         if (!ansi)
18968             line++;
18969         if (src[i] == '\n')
18970             line = 0;
18971         if (src[i] == 'm')
18972             ansi = 0;
18973     }
18974     if (dest && appData.debugMode)
18975     {
18976         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18977             count, width, line, len, *lp);
18978         show_bytes(debugFP, src, count);
18979         fprintf(debugFP, "\ndest: ");
18980         show_bytes(debugFP, dest, len);
18981         fprintf(debugFP, "\n");
18982     }
18983     *lp = dest ? line : old_line;
18984
18985     return len;
18986 }
18987
18988 // [HGM] vari: routines for shelving variations
18989 Boolean modeRestore = FALSE;
18990
18991 void
18992 PushInner (int firstMove, int lastMove)
18993 {
18994         int i, j, nrMoves = lastMove - firstMove;
18995
18996         // push current tail of game on stack
18997         savedResult[storedGames] = gameInfo.result;
18998         savedDetails[storedGames] = gameInfo.resultDetails;
18999         gameInfo.resultDetails = NULL;
19000         savedFirst[storedGames] = firstMove;
19001         savedLast [storedGames] = lastMove;
19002         savedFramePtr[storedGames] = framePtr;
19003         framePtr -= nrMoves; // reserve space for the boards
19004         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19005             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19006             for(j=0; j<MOVE_LEN; j++)
19007                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19008             for(j=0; j<2*MOVE_LEN; j++)
19009                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19010             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19011             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19012             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19013             pvInfoList[firstMove+i-1].depth = 0;
19014             commentList[framePtr+i] = commentList[firstMove+i];
19015             commentList[firstMove+i] = NULL;
19016         }
19017
19018         storedGames++;
19019         forwardMostMove = firstMove; // truncate game so we can start variation
19020 }
19021
19022 void
19023 PushTail (int firstMove, int lastMove)
19024 {
19025         if(appData.icsActive) { // only in local mode
19026                 forwardMostMove = currentMove; // mimic old ICS behavior
19027                 return;
19028         }
19029         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19030
19031         PushInner(firstMove, lastMove);
19032         if(storedGames == 1) GreyRevert(FALSE);
19033         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19034 }
19035
19036 void
19037 PopInner (Boolean annotate)
19038 {
19039         int i, j, nrMoves;
19040         char buf[8000], moveBuf[20];
19041
19042         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19043         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19044         nrMoves = savedLast[storedGames] - currentMove;
19045         if(annotate) {
19046                 int cnt = 10;
19047                 if(!WhiteOnMove(currentMove))
19048                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19049                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19050                 for(i=currentMove; i<forwardMostMove; i++) {
19051                         if(WhiteOnMove(i))
19052                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19053                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19054                         strcat(buf, moveBuf);
19055                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19056                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19057                 }
19058                 strcat(buf, ")");
19059         }
19060         for(i=1; i<=nrMoves; i++) { // copy last variation back
19061             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19062             for(j=0; j<MOVE_LEN; j++)
19063                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19064             for(j=0; j<2*MOVE_LEN; j++)
19065                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19066             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19067             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19068             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19069             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19070             commentList[currentMove+i] = commentList[framePtr+i];
19071             commentList[framePtr+i] = NULL;
19072         }
19073         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19074         framePtr = savedFramePtr[storedGames];
19075         gameInfo.result = savedResult[storedGames];
19076         if(gameInfo.resultDetails != NULL) {
19077             free(gameInfo.resultDetails);
19078       }
19079         gameInfo.resultDetails = savedDetails[storedGames];
19080         forwardMostMove = currentMove + nrMoves;
19081 }
19082
19083 Boolean
19084 PopTail (Boolean annotate)
19085 {
19086         if(appData.icsActive) return FALSE; // only in local mode
19087         if(!storedGames) return FALSE; // sanity
19088         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19089
19090         PopInner(annotate);
19091         if(currentMove < forwardMostMove) ForwardEvent(); else
19092         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19093
19094         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19095         return TRUE;
19096 }
19097
19098 void
19099 CleanupTail ()
19100 {       // remove all shelved variations
19101         int i;
19102         for(i=0; i<storedGames; i++) {
19103             if(savedDetails[i])
19104                 free(savedDetails[i]);
19105             savedDetails[i] = NULL;
19106         }
19107         for(i=framePtr; i<MAX_MOVES; i++) {
19108                 if(commentList[i]) free(commentList[i]);
19109                 commentList[i] = NULL;
19110         }
19111         framePtr = MAX_MOVES-1;
19112         storedGames = 0;
19113 }
19114
19115 void
19116 LoadVariation (int index, char *text)
19117 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19118         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19119         int level = 0, move;
19120
19121         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19122         // first find outermost bracketing variation
19123         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19124             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19125                 if(*p == '{') wait = '}'; else
19126                 if(*p == '[') wait = ']'; else
19127                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19128                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19129             }
19130             if(*p == wait) wait = NULLCHAR; // closing ]} found
19131             p++;
19132         }
19133         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19134         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19135         end[1] = NULLCHAR; // clip off comment beyond variation
19136         ToNrEvent(currentMove-1);
19137         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19138         // kludge: use ParsePV() to append variation to game
19139         move = currentMove;
19140         ParsePV(start, TRUE, TRUE);
19141         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19142         ClearPremoveHighlights();
19143         CommentPopDown();
19144         ToNrEvent(currentMove+1);
19145 }
19146
19147 int transparency[2];
19148
19149 void
19150 LoadTheme ()
19151 {
19152 #define BUF_SIZ (2*MSG_SIZ)
19153     char *p, *q, buf[BUF_SIZ];
19154     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19155         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19156         ParseArgsFromString(buf);
19157         ActivateTheme(TRUE); // also redo colors
19158         return;
19159     }
19160     p = nickName;
19161     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19162     {
19163         int len;
19164         q = appData.themeNames;
19165         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19166       if(appData.useBitmaps) {
19167         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19168                 Shorten(appData.liteBackTextureFile));
19169         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19170                 Shorten(appData.darkBackTextureFile),
19171                 appData.liteBackTextureMode,
19172                 appData.darkBackTextureMode );
19173       } else {
19174         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19175       }
19176       if(!appData.useBitmaps || transparency[0]) {
19177         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19178       }
19179       if(!appData.useBitmaps || transparency[1]) {
19180         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19181       }
19182       if(appData.useBorder) {
19183         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19184                 appData.border);
19185       } else {
19186         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19187       }
19188       if(appData.useFont) {
19189         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19190                 appData.renderPiecesWithFont,
19191                 appData.fontToPieceTable,
19192                 Col2Text(9),    // appData.fontBackColorWhite
19193                 Col2Text(10) ); // appData.fontForeColorBlack
19194       } else {
19195         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19196         if(appData.pieceDirectory[0]) {
19197           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19198           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19199             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19200         }
19201         if(!appData.pieceDirectory[0] || !appData.trueColors)
19202           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19203                 Col2Text(0),   // whitePieceColor
19204                 Col2Text(1) ); // blackPieceColor
19205       }
19206       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19207                 Col2Text(4),   // highlightSquareColor
19208                 Col2Text(5) ); // premoveHighlightColor
19209         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19210         if(insert != q) insert[-1] = NULLCHAR;
19211         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19212         if(q)   free(q);
19213     }
19214     ActivateTheme(FALSE);
19215 }