Implement -showMoveTime option
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
992         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
993         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
994         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
995         appData.firstProtocolVersion = PROTOVER;
996         ParseArgsFromString(buf);
997         SwapEngines(i);
998         ReplaceEngine(cps, i);
999         FloatToFront(&appData.recentEngineList, engineLine);
1000         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1001         return;
1002     }
1003     p = engineName;
1004     while(q = strchr(p, SLASH)) p = q+1;
1005     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1006     if(engineDir[0] != NULLCHAR) {
1007         ASSIGN(appData.directory[i], engineDir); p = engineName;
1008     } else if(p != engineName) { // derive directory from engine path, when not given
1009         p[-1] = 0;
1010         ASSIGN(appData.directory[i], engineName);
1011         p[-1] = SLASH;
1012         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1013     } else { ASSIGN(appData.directory[i], "."); }
1014     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1015     if(params[0]) {
1016         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1017         snprintf(command, MSG_SIZ, "%s %s", p, params);
1018         p = command;
1019     }
1020     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1021     ASSIGN(appData.chessProgram[i], p);
1022     appData.isUCI[i] = isUCI;
1023     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1024     appData.hasOwnBookUCI[i] = hasBook;
1025     if(!nickName[0]) useNick = FALSE;
1026     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1027     if(addToList) {
1028         int len;
1029         char quote;
1030         q = firstChessProgramNames;
1031         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1032         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1033         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1034                         quote, p, quote, appData.directory[i],
1035                         useNick ? " -fn \"" : "",
1036                         useNick ? nickName : "",
1037                         useNick ? "\"" : "",
1038                         v1 ? " -firstProtocolVersion 1" : "",
1039                         hasBook ? "" : " -fNoOwnBookUCI",
1040                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1041                         storeVariant ? " -variant " : "",
1042                         storeVariant ? VariantName(gameInfo.variant) : "");
1043         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1044         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1045         if(insert != q) insert[-1] = NULLCHAR;
1046         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1047         if(q)   free(q);
1048         FloatToFront(&appData.recentEngineList, buf);
1049     }
1050     ReplaceEngine(cps, i);
1051 }
1052
1053 void
1054 InitTimeControls ()
1055 {
1056     int matched, min, sec;
1057     /*
1058      * Parse timeControl resource
1059      */
1060     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1061                           appData.movesPerSession)) {
1062         char buf[MSG_SIZ];
1063         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1064         DisplayFatalError(buf, 0, 2);
1065     }
1066
1067     /*
1068      * Parse searchTime resource
1069      */
1070     if (*appData.searchTime != NULLCHAR) {
1071         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1072         if (matched == 1) {
1073             searchTime = min * 60;
1074         } else if (matched == 2) {
1075             searchTime = min * 60 + sec;
1076         } else {
1077             char buf[MSG_SIZ];
1078             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1079             DisplayFatalError(buf, 0, 2);
1080         }
1081     }
1082 }
1083
1084 void
1085 InitBackEnd1 ()
1086 {
1087
1088     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1089     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1090
1091     GetTimeMark(&programStartTime);
1092     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1093     appData.seedBase = random() + (random()<<15);
1094     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1095
1096     ClearProgramStats();
1097     programStats.ok_to_send = 1;
1098     programStats.seen_stat = 0;
1099
1100     /*
1101      * Initialize game list
1102      */
1103     ListNew(&gameList);
1104
1105
1106     /*
1107      * Internet chess server status
1108      */
1109     if (appData.icsActive) {
1110         appData.matchMode = FALSE;
1111         appData.matchGames = 0;
1112 #if ZIPPY
1113         appData.noChessProgram = !appData.zippyPlay;
1114 #else
1115         appData.zippyPlay = FALSE;
1116         appData.zippyTalk = FALSE;
1117         appData.noChessProgram = TRUE;
1118 #endif
1119         if (*appData.icsHelper != NULLCHAR) {
1120             appData.useTelnet = TRUE;
1121             appData.telnetProgram = appData.icsHelper;
1122         }
1123     } else {
1124         appData.zippyTalk = appData.zippyPlay = FALSE;
1125     }
1126
1127     /* [AS] Initialize pv info list [HGM] and game state */
1128     {
1129         int i, j;
1130
1131         for( i=0; i<=framePtr; i++ ) {
1132             pvInfoList[i].depth = -1;
1133             boards[i][EP_STATUS] = EP_NONE;
1134             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1135         }
1136     }
1137
1138     InitTimeControls();
1139
1140     /* [AS] Adjudication threshold */
1141     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1142
1143     InitEngine(&first, 0);
1144     InitEngine(&second, 1);
1145     CommonEngineInit();
1146
1147     pairing.which = "pairing"; // pairing engine
1148     pairing.pr = NoProc;
1149     pairing.isr = NULL;
1150     pairing.program = appData.pairingEngine;
1151     pairing.host = "localhost";
1152     pairing.dir = ".";
1153
1154     if (appData.icsActive) {
1155         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1156     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1157         appData.clockMode = FALSE;
1158         first.sendTime = second.sendTime = 0;
1159     }
1160
1161 #if ZIPPY
1162     /* Override some settings from environment variables, for backward
1163        compatibility.  Unfortunately it's not feasible to have the env
1164        vars just set defaults, at least in xboard.  Ugh.
1165     */
1166     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1167       ZippyInit();
1168     }
1169 #endif
1170
1171     if (!appData.icsActive) {
1172       char buf[MSG_SIZ];
1173       int len;
1174
1175       /* Check for variants that are supported only in ICS mode,
1176          or not at all.  Some that are accepted here nevertheless
1177          have bugs; see comments below.
1178       */
1179       VariantClass variant = StringToVariant(appData.variant);
1180       switch (variant) {
1181       case VariantBughouse:     /* need four players and two boards */
1182       case VariantKriegspiel:   /* need to hide pieces and move details */
1183         /* case VariantFischeRandom: (Fabien: moved below) */
1184         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1185         if( (len >= MSG_SIZ) && appData.debugMode )
1186           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1187
1188         DisplayFatalError(buf, 0, 2);
1189         return;
1190
1191       case VariantUnknown:
1192       case VariantLoadable:
1193       case Variant29:
1194       case Variant30:
1195       case Variant31:
1196       case Variant32:
1197       case Variant33:
1198       case Variant34:
1199       case Variant35:
1200       case Variant36:
1201       default:
1202         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1203         if( (len >= MSG_SIZ) && appData.debugMode )
1204           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1205
1206         DisplayFatalError(buf, 0, 2);
1207         return;
1208
1209       case VariantNormal:     /* definitely works! */
1210         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1211           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1212           return;
1213         }
1214       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1215       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1216       case VariantGothic:     /* [HGM] should work */
1217       case VariantCapablanca: /* [HGM] should work */
1218       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1219       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1220       case VariantChu:        /* [HGM] experimental */
1221       case VariantKnightmate: /* [HGM] should work */
1222       case VariantCylinder:   /* [HGM] untested */
1223       case VariantFalcon:     /* [HGM] untested */
1224       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1225                                  offboard interposition not understood */
1226       case VariantWildCastle: /* pieces not automatically shuffled */
1227       case VariantNoCastle:   /* pieces not automatically shuffled */
1228       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1229       case VariantLosers:     /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantSuicide:    /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantGiveaway:   /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantTwoKings:   /* should work */
1236       case VariantAtomic:     /* should work except for win condition */
1237       case Variant3Check:     /* should work except for win condition */
1238       case VariantShatranj:   /* should work except for all win conditions */
1239       case VariantMakruk:     /* should work except for draw countdown */
1240       case VariantASEAN :     /* should work except for draw countdown */
1241       case VariantBerolina:   /* might work if TestLegality is off */
1242       case VariantCapaRandom: /* should work */
1243       case VariantJanus:      /* should work */
1244       case VariantSuper:      /* experimental */
1245       case VariantGreat:      /* experimental, requires legality testing to be off */
1246       case VariantSChess:     /* S-Chess, should work */
1247       case VariantGrand:      /* should work */
1248       case VariantSpartan:    /* should work */
1249       case VariantLion:       /* should work */
1250       case VariantChuChess:   /* should work */
1251         break;
1252       }
1253     }
1254
1255 }
1256
1257 int
1258 NextIntegerFromString (char ** str, long * value)
1259 {
1260     int result = -1;
1261     char * s = *str;
1262
1263     while( *s == ' ' || *s == '\t' ) {
1264         s++;
1265     }
1266
1267     *value = 0;
1268
1269     if( *s >= '0' && *s <= '9' ) {
1270         while( *s >= '0' && *s <= '9' ) {
1271             *value = *value * 10 + (*s - '0');
1272             s++;
1273         }
1274
1275         result = 0;
1276     }
1277
1278     *str = s;
1279
1280     return result;
1281 }
1282
1283 int
1284 NextTimeControlFromString (char ** str, long * value)
1285 {
1286     long temp;
1287     int result = NextIntegerFromString( str, &temp );
1288
1289     if( result == 0 ) {
1290         *value = temp * 60; /* Minutes */
1291         if( **str == ':' ) {
1292             (*str)++;
1293             result = NextIntegerFromString( str, &temp );
1294             *value += temp; /* Seconds */
1295         }
1296     }
1297
1298     return result;
1299 }
1300
1301 int
1302 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1303 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1304     int result = -1, type = 0; long temp, temp2;
1305
1306     if(**str != ':') return -1; // old params remain in force!
1307     (*str)++;
1308     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1309     if( NextIntegerFromString( str, &temp ) ) return -1;
1310     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1311
1312     if(**str != '/') {
1313         /* time only: incremental or sudden-death time control */
1314         if(**str == '+') { /* increment follows; read it */
1315             (*str)++;
1316             if(**str == '!') type = *(*str)++; // Bronstein TC
1317             if(result = NextIntegerFromString( str, &temp2)) return -1;
1318             *inc = temp2 * 1000;
1319             if(**str == '.') { // read fraction of increment
1320                 char *start = ++(*str);
1321                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1322                 temp2 *= 1000;
1323                 while(start++ < *str) temp2 /= 10;
1324                 *inc += temp2;
1325             }
1326         } else *inc = 0;
1327         *moves = 0; *tc = temp * 1000; *incType = type;
1328         return 0;
1329     }
1330
1331     (*str)++; /* classical time control */
1332     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1333
1334     if(result == 0) {
1335         *moves = temp;
1336         *tc    = temp2 * 1000;
1337         *inc   = 0;
1338         *incType = type;
1339     }
1340     return result;
1341 }
1342
1343 int
1344 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1345 {   /* [HGM] get time to add from the multi-session time-control string */
1346     int incType, moves=1; /* kludge to force reading of first session */
1347     long time, increment;
1348     char *s = tcString;
1349
1350     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1351     do {
1352         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1353         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1354         if(movenr == -1) return time;    /* last move before new session     */
1355         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1356         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1357         if(!moves) return increment;     /* current session is incremental   */
1358         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1359     } while(movenr >= -1);               /* try again for next session       */
1360
1361     return 0; // no new time quota on this move
1362 }
1363
1364 int
1365 ParseTimeControl (char *tc, float ti, int mps)
1366 {
1367   long tc1;
1368   long tc2;
1369   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1370   int min, sec=0;
1371
1372   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1373   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1374       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1375   if(ti > 0) {
1376
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1381   } else {
1382     if(mps)
1383       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1384     else
1385       snprintf(buf, MSG_SIZ, ":%s", mytc);
1386   }
1387   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1388
1389   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1390     return FALSE;
1391   }
1392
1393   if( *tc == '/' ) {
1394     /* Parse second time control */
1395     tc++;
1396
1397     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1398       return FALSE;
1399     }
1400
1401     if( tc2 == 0 ) {
1402       return FALSE;
1403     }
1404
1405     timeControl_2 = tc2 * 1000;
1406   }
1407   else {
1408     timeControl_2 = 0;
1409   }
1410
1411   if( tc1 == 0 ) {
1412     return FALSE;
1413   }
1414
1415   timeControl = tc1 * 1000;
1416
1417   if (ti >= 0) {
1418     timeIncrement = ti * 1000;  /* convert to ms */
1419     movesPerSession = 0;
1420   } else {
1421     timeIncrement = 0;
1422     movesPerSession = mps;
1423   }
1424   return TRUE;
1425 }
1426
1427 void
1428 InitBackEnd2 ()
1429 {
1430     if (appData.debugMode) {
1431 #    ifdef __GIT_VERSION
1432       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1433 #    else
1434       fprintf(debugFP, "Version: %s\n", programVersion);
1435 #    endif
1436     }
1437     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1438
1439     set_cont_sequence(appData.wrapContSeq);
1440     if (appData.matchGames > 0) {
1441         appData.matchMode = TRUE;
1442     } else if (appData.matchMode) {
1443         appData.matchGames = 1;
1444     }
1445     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1446         appData.matchGames = appData.sameColorGames;
1447     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1448         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1449         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1450     }
1451     Reset(TRUE, FALSE);
1452     if (appData.noChessProgram || first.protocolVersion == 1) {
1453       InitBackEnd3();
1454     } else {
1455       /* kludge: allow timeout for initial "feature" commands */
1456       FreezeUI();
1457       DisplayMessage("", _("Starting chess program"));
1458       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1459     }
1460 }
1461
1462 int
1463 CalculateIndex (int index, int gameNr)
1464 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1465     int res;
1466     if(index > 0) return index; // fixed nmber
1467     if(index == 0) return 1;
1468     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1469     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1470     return res;
1471 }
1472
1473 int
1474 LoadGameOrPosition (int gameNr)
1475 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1476     if (*appData.loadGameFile != NULLCHAR) {
1477         if (!LoadGameFromFile(appData.loadGameFile,
1478                 CalculateIndex(appData.loadGameIndex, gameNr),
1479                               appData.loadGameFile, FALSE)) {
1480             DisplayFatalError(_("Bad game file"), 0, 1);
1481             return 0;
1482         }
1483     } else if (*appData.loadPositionFile != NULLCHAR) {
1484         if (!LoadPositionFromFile(appData.loadPositionFile,
1485                 CalculateIndex(appData.loadPositionIndex, gameNr),
1486                                   appData.loadPositionFile)) {
1487             DisplayFatalError(_("Bad position file"), 0, 1);
1488             return 0;
1489         }
1490     }
1491     return 1;
1492 }
1493
1494 void
1495 ReserveGame (int gameNr, char resChar)
1496 {
1497     FILE *tf = fopen(appData.tourneyFile, "r+");
1498     char *p, *q, c, buf[MSG_SIZ];
1499     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1500     safeStrCpy(buf, lastMsg, MSG_SIZ);
1501     DisplayMessage(_("Pick new game"), "");
1502     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1503     ParseArgsFromFile(tf);
1504     p = q = appData.results;
1505     if(appData.debugMode) {
1506       char *r = appData.participants;
1507       fprintf(debugFP, "results = '%s'\n", p);
1508       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1509       fprintf(debugFP, "\n");
1510     }
1511     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1512     nextGame = q - p;
1513     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1514     safeStrCpy(q, p, strlen(p) + 2);
1515     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1516     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1517     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1518         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1519         q[nextGame] = '*';
1520     }
1521     fseek(tf, -(strlen(p)+4), SEEK_END);
1522     c = fgetc(tf);
1523     if(c != '"') // depending on DOS or Unix line endings we can be one off
1524          fseek(tf, -(strlen(p)+2), SEEK_END);
1525     else fseek(tf, -(strlen(p)+3), SEEK_END);
1526     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1527     DisplayMessage(buf, "");
1528     free(p); appData.results = q;
1529     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1530        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1531       int round = appData.defaultMatchGames * appData.tourneyType;
1532       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1533          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1534         UnloadEngine(&first);  // next game belongs to other pairing;
1535         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1536     }
1537     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1538 }
1539
1540 void
1541 MatchEvent (int mode)
1542 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1543         int dummy;
1544         if(matchMode) { // already in match mode: switch it off
1545             abortMatch = TRUE;
1546             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1547             return;
1548         }
1549 //      if(gameMode != BeginningOfGame) {
1550 //          DisplayError(_("You can only start a match from the initial position."), 0);
1551 //          return;
1552 //      }
1553         abortMatch = FALSE;
1554         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1555         /* Set up machine vs. machine match */
1556         nextGame = 0;
1557         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1558         if(appData.tourneyFile[0]) {
1559             ReserveGame(-1, 0);
1560             if(nextGame > appData.matchGames) {
1561                 char buf[MSG_SIZ];
1562                 if(strchr(appData.results, '*') == NULL) {
1563                     FILE *f;
1564                     appData.tourneyCycles++;
1565                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1566                         fclose(f);
1567                         NextTourneyGame(-1, &dummy);
1568                         ReserveGame(-1, 0);
1569                         if(nextGame <= appData.matchGames) {
1570                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1571                             matchMode = mode;
1572                             ScheduleDelayedEvent(NextMatchGame, 10000);
1573                             return;
1574                         }
1575                     }
1576                 }
1577                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1578                 DisplayError(buf, 0);
1579                 appData.tourneyFile[0] = 0;
1580                 return;
1581             }
1582         } else
1583         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1584             DisplayFatalError(_("Can't have a match with no chess programs"),
1585                               0, 2);
1586             return;
1587         }
1588         matchMode = mode;
1589         matchGame = roundNr = 1;
1590         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1591         NextMatchGame();
1592 }
1593
1594 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1604        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1605         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1606        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1607        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1608         char c, *q = first.variants, *p = strchr(q, ',');
1609         if(p) *p = NULLCHAR;
1610         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1611             int w, h, s;
1612             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1613                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1614             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1615             Reset(TRUE, FALSE);         // and re-initialize
1616         }
1617         if(p) *p = ',';
1618     }
1619
1620     InitChessProgram(&first, startedFromSetupPosition);
1621
1622     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1623         free(programVersion);
1624         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1625         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1626         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1627     }
1628
1629     if (appData.icsActive) {
1630 #ifdef WIN32
1631         /* [DM] Make a console window if needed [HGM] merged ifs */
1632         ConsoleCreate();
1633 #endif
1634         err = establish();
1635         if (err != 0)
1636           {
1637             if (*appData.icsCommPort != NULLCHAR)
1638               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1639                              appData.icsCommPort);
1640             else
1641               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1642                         appData.icsHost, appData.icsPort);
1643
1644             if( (len >= MSG_SIZ) && appData.debugMode )
1645               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1646
1647             DisplayFatalError(buf, err, 1);
1648             return;
1649         }
1650         SetICSMode();
1651         telnetISR =
1652           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1653         fromUserISR =
1654           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1655         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1656             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1657     } else if (appData.noChessProgram) {
1658         SetNCPMode();
1659     } else {
1660         SetGNUMode();
1661     }
1662
1663     if (*appData.cmailGameName != NULLCHAR) {
1664         SetCmailMode();
1665         OpenLoopback(&cmailPR);
1666         cmailISR =
1667           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1668     }
1669
1670     ThawUI();
1671     DisplayMessage("", "");
1672     if (StrCaseCmp(appData.initialMode, "") == 0) {
1673       initialMode = BeginningOfGame;
1674       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1675         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1676         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1677         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1678         ModeHighlight();
1679       }
1680     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1681       initialMode = TwoMachinesPlay;
1682     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1683       initialMode = AnalyzeFile;
1684     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1685       initialMode = AnalyzeMode;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1687       initialMode = MachinePlaysWhite;
1688     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1689       initialMode = MachinePlaysBlack;
1690     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1691       initialMode = EditGame;
1692     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1693       initialMode = EditPosition;
1694     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1695       initialMode = Training;
1696     } else {
1697       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1698       if( (len >= MSG_SIZ) && appData.debugMode )
1699         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1700
1701       DisplayFatalError(buf, 0, 2);
1702       return;
1703     }
1704
1705     if (appData.matchMode) {
1706         if(appData.tourneyFile[0]) { // start tourney from command line
1707             FILE *f;
1708             if(f = fopen(appData.tourneyFile, "r")) {
1709                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1710                 fclose(f);
1711                 appData.clockMode = TRUE;
1712                 SetGNUMode();
1713             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1714         }
1715         MatchEvent(TRUE);
1716     } else if (*appData.cmailGameName != NULLCHAR) {
1717         /* Set up cmail mode */
1718         ReloadCmailMsgEvent(TRUE);
1719     } else {
1720         /* Set up other modes */
1721         if (initialMode == AnalyzeFile) {
1722           if (*appData.loadGameFile == NULLCHAR) {
1723             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1724             return;
1725           }
1726         }
1727         if (*appData.loadGameFile != NULLCHAR) {
1728             (void) LoadGameFromFile(appData.loadGameFile,
1729                                     appData.loadGameIndex,
1730                                     appData.loadGameFile, TRUE);
1731         } else if (*appData.loadPositionFile != NULLCHAR) {
1732             (void) LoadPositionFromFile(appData.loadPositionFile,
1733                                         appData.loadPositionIndex,
1734                                         appData.loadPositionFile);
1735             /* [HGM] try to make self-starting even after FEN load */
1736             /* to allow automatic setup of fairy variants with wtm */
1737             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1738                 gameMode = BeginningOfGame;
1739                 setboardSpoiledMachineBlack = 1;
1740             }
1741             /* [HGM] loadPos: make that every new game uses the setup */
1742             /* from file as long as we do not switch variant          */
1743             if(!blackPlaysFirst) {
1744                 startedFromPositionFile = TRUE;
1745                 CopyBoard(filePosition, boards[0]);
1746                 CopyBoard(initialPosition, boards[0]);
1747             }
1748         } else if(*appData.fen != NULLCHAR) {
1749             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1750                 startedFromPositionFile = TRUE;
1751                 Reset(TRUE, TRUE);
1752             }
1753         }
1754         if (initialMode == AnalyzeMode) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1761             return;
1762           }
1763           AnalyzeModeEvent();
1764         } else if (initialMode == AnalyzeFile) {
1765           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1766           ShowThinkingEvent();
1767           AnalyzeFileEvent();
1768           AnalysisPeriodicEvent(1);
1769         } else if (initialMode == MachinePlaysWhite) {
1770           if (appData.noChessProgram) {
1771             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1772                               0, 2);
1773             return;
1774           }
1775           if (appData.icsActive) {
1776             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1777                               0, 2);
1778             return;
1779           }
1780           MachineWhiteEvent();
1781         } else if (initialMode == MachinePlaysBlack) {
1782           if (appData.noChessProgram) {
1783             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1784                               0, 2);
1785             return;
1786           }
1787           if (appData.icsActive) {
1788             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1789                               0, 2);
1790             return;
1791           }
1792           MachineBlackEvent();
1793         } else if (initialMode == TwoMachinesPlay) {
1794           if (appData.noChessProgram) {
1795             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1796                               0, 2);
1797             return;
1798           }
1799           if (appData.icsActive) {
1800             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1801                               0, 2);
1802             return;
1803           }
1804           TwoMachinesEvent();
1805         } else if (initialMode == EditGame) {
1806           EditGameEvent();
1807         } else if (initialMode == EditPosition) {
1808           EditPositionEvent();
1809         } else if (initialMode == Training) {
1810           if (*appData.loadGameFile == NULLCHAR) {
1811             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1812             return;
1813           }
1814           TrainingEvent();
1815         }
1816     }
1817 }
1818
1819 void
1820 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1821 {
1822     DisplayBook(current+1);
1823
1824     MoveHistorySet( movelist, first, last, current, pvInfoList );
1825
1826     EvalGraphSet( first, last, current, pvInfoList );
1827
1828     MakeEngineOutputTitle();
1829 }
1830
1831 /*
1832  * Establish will establish a contact to a remote host.port.
1833  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1834  *  used to talk to the host.
1835  * Returns 0 if okay, error code if not.
1836  */
1837 int
1838 establish ()
1839 {
1840     char buf[MSG_SIZ];
1841
1842     if (*appData.icsCommPort != NULLCHAR) {
1843         /* Talk to the host through a serial comm port */
1844         return OpenCommPort(appData.icsCommPort, &icsPR);
1845
1846     } else if (*appData.gateway != NULLCHAR) {
1847         if (*appData.remoteShell == NULLCHAR) {
1848             /* Use the rcmd protocol to run telnet program on a gateway host */
1849             snprintf(buf, sizeof(buf), "%s %s %s",
1850                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1851             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1852
1853         } else {
1854             /* Use the rsh program to run telnet program on a gateway host */
1855             if (*appData.remoteUser == NULLCHAR) {
1856                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1857                         appData.gateway, appData.telnetProgram,
1858                         appData.icsHost, appData.icsPort);
1859             } else {
1860                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1861                         appData.remoteShell, appData.gateway,
1862                         appData.remoteUser, appData.telnetProgram,
1863                         appData.icsHost, appData.icsPort);
1864             }
1865             return StartChildProcess(buf, "", &icsPR);
1866
1867         }
1868     } else if (appData.useTelnet) {
1869         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1870
1871     } else {
1872         /* TCP socket interface differs somewhat between
1873            Unix and NT; handle details in the front end.
1874            */
1875         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1876     }
1877 }
1878
1879 void
1880 EscapeExpand (char *p, char *q)
1881 {       // [HGM] initstring: routine to shape up string arguments
1882         while(*p++ = *q++) if(p[-1] == '\\')
1883             switch(*q++) {
1884                 case 'n': p[-1] = '\n'; break;
1885                 case 'r': p[-1] = '\r'; break;
1886                 case 't': p[-1] = '\t'; break;
1887                 case '\\': p[-1] = '\\'; break;
1888                 case 0: *p = 0; return;
1889                 default: p[-1] = q[-1]; break;
1890             }
1891 }
1892
1893 void
1894 show_bytes (FILE *fp, char *buf, int count)
1895 {
1896     while (count--) {
1897         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1898             fprintf(fp, "\\%03o", *buf & 0xff);
1899         } else {
1900             putc(*buf, fp);
1901         }
1902         buf++;
1903     }
1904     fflush(fp);
1905 }
1906
1907 /* Returns an errno value */
1908 int
1909 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1910 {
1911     char buf[8192], *p, *q, *buflim;
1912     int left, newcount, outcount;
1913
1914     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1915         *appData.gateway != NULLCHAR) {
1916         if (appData.debugMode) {
1917             fprintf(debugFP, ">ICS: ");
1918             show_bytes(debugFP, message, count);
1919             fprintf(debugFP, "\n");
1920         }
1921         return OutputToProcess(pr, message, count, outError);
1922     }
1923
1924     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1925     p = message;
1926     q = buf;
1927     left = count;
1928     newcount = 0;
1929     while (left) {
1930         if (q >= buflim) {
1931             if (appData.debugMode) {
1932                 fprintf(debugFP, ">ICS: ");
1933                 show_bytes(debugFP, buf, newcount);
1934                 fprintf(debugFP, "\n");
1935             }
1936             outcount = OutputToProcess(pr, buf, newcount, outError);
1937             if (outcount < newcount) return -1; /* to be sure */
1938             q = buf;
1939             newcount = 0;
1940         }
1941         if (*p == '\n') {
1942             *q++ = '\r';
1943             newcount++;
1944         } else if (((unsigned char) *p) == TN_IAC) {
1945             *q++ = (char) TN_IAC;
1946             newcount ++;
1947         }
1948         *q++ = *p++;
1949         newcount++;
1950         left--;
1951     }
1952     if (appData.debugMode) {
1953         fprintf(debugFP, ">ICS: ");
1954         show_bytes(debugFP, buf, newcount);
1955         fprintf(debugFP, "\n");
1956     }
1957     outcount = OutputToProcess(pr, buf, newcount, outError);
1958     if (outcount < newcount) return -1; /* to be sure */
1959     return count;
1960 }
1961
1962 void
1963 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1964 {
1965     int outError, outCount;
1966     static int gotEof = 0;
1967     static FILE *ini;
1968
1969     /* Pass data read from player on to ICS */
1970     if (count > 0) {
1971         gotEof = 0;
1972         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1973         if (outCount < count) {
1974             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975         }
1976         if(have_sent_ICS_logon == 2) {
1977           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1978             fprintf(ini, "%s", message);
1979             have_sent_ICS_logon = 3;
1980           } else
1981             have_sent_ICS_logon = 1;
1982         } else if(have_sent_ICS_logon == 3) {
1983             fprintf(ini, "%s", message);
1984             fclose(ini);
1985           have_sent_ICS_logon = 1;
1986         }
1987     } else if (count < 0) {
1988         RemoveInputSource(isr);
1989         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1990     } else if (gotEof++ > 0) {
1991         RemoveInputSource(isr);
1992         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1993     }
1994 }
1995
1996 void
1997 KeepAlive ()
1998 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1999     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2000     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2001     SendToICS("date\n");
2002     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2003 }
2004
2005 /* added routine for printf style output to ics */
2006 void
2007 ics_printf (char *format, ...)
2008 {
2009     char buffer[MSG_SIZ];
2010     va_list args;
2011
2012     va_start(args, format);
2013     vsnprintf(buffer, sizeof(buffer), format, args);
2014     buffer[sizeof(buffer)-1] = '\0';
2015     SendToICS(buffer);
2016     va_end(args);
2017 }
2018
2019 void
2020 SendToICS (char *s)
2021 {
2022     int count, outCount, outError;
2023
2024     if (icsPR == NoProc) return;
2025
2026     count = strlen(s);
2027     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2028     if (outCount < count) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 /* This is used for sending logon scripts to the ICS. Sending
2034    without a delay causes problems when using timestamp on ICC
2035    (at least on my machine). */
2036 void
2037 SendToICSDelayed (char *s, long msdelay)
2038 {
2039     int count, outCount, outError;
2040
2041     if (icsPR == NoProc) return;
2042
2043     count = strlen(s);
2044     if (appData.debugMode) {
2045         fprintf(debugFP, ">ICS: ");
2046         show_bytes(debugFP, s, count);
2047         fprintf(debugFP, "\n");
2048     }
2049     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2050                                       msdelay);
2051     if (outCount < count) {
2052         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2053     }
2054 }
2055
2056
2057 /* Remove all highlighting escape sequences in s
2058    Also deletes any suffix starting with '('
2059    */
2060 char *
2061 StripHighlightAndTitle (char *s)
2062 {
2063     static char retbuf[MSG_SIZ];
2064     char *p = retbuf;
2065
2066     while (*s != NULLCHAR) {
2067         while (*s == '\033') {
2068             while (*s != NULLCHAR && !isalpha(*s)) s++;
2069             if (*s != NULLCHAR) s++;
2070         }
2071         while (*s != NULLCHAR && *s != '\033') {
2072             if (*s == '(' || *s == '[') {
2073                 *p = NULLCHAR;
2074                 return retbuf;
2075             }
2076             *p++ = *s++;
2077         }
2078     }
2079     *p = NULLCHAR;
2080     return retbuf;
2081 }
2082
2083 /* Remove all highlighting escape sequences in s */
2084 char *
2085 StripHighlight (char *s)
2086 {
2087     static char retbuf[MSG_SIZ];
2088     char *p = retbuf;
2089
2090     while (*s != NULLCHAR) {
2091         while (*s == '\033') {
2092             while (*s != NULLCHAR && !isalpha(*s)) s++;
2093             if (*s != NULLCHAR) s++;
2094         }
2095         while (*s != NULLCHAR && *s != '\033') {
2096             *p++ = *s++;
2097         }
2098     }
2099     *p = NULLCHAR;
2100     return retbuf;
2101 }
2102
2103 char engineVariant[MSG_SIZ];
2104 char *variantNames[] = VARIANT_NAMES;
2105 char *
2106 VariantName (VariantClass v)
2107 {
2108     if(v == VariantUnknown || *engineVariant) return engineVariant;
2109     return variantNames[v];
2110 }
2111
2112
2113 /* Identify a variant from the strings the chess servers use or the
2114    PGN Variant tag names we use. */
2115 VariantClass
2116 StringToVariant (char *e)
2117 {
2118     char *p;
2119     int wnum = -1;
2120     VariantClass v = VariantNormal;
2121     int i, found = FALSE;
2122     char buf[MSG_SIZ], c;
2123     int len;
2124
2125     if (!e) return v;
2126
2127     /* [HGM] skip over optional board-size prefixes */
2128     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2129         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2130         while( *e++ != '_');
2131     }
2132
2133     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2134         v = VariantNormal;
2135         found = TRUE;
2136     } else
2137     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2138       if (p = StrCaseStr(e, variantNames[i])) {
2139         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2140         v = (VariantClass) i;
2141         found = TRUE;
2142         break;
2143       }
2144     }
2145
2146     if (!found) {
2147       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2148           || StrCaseStr(e, "wild/fr")
2149           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2150         v = VariantFischeRandom;
2151       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2152                  (i = 1, p = StrCaseStr(e, "w"))) {
2153         p += i;
2154         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2155         if (isdigit(*p)) {
2156           wnum = atoi(p);
2157         } else {
2158           wnum = -1;
2159         }
2160         switch (wnum) {
2161         case 0: /* FICS only, actually */
2162         case 1:
2163           /* Castling legal even if K starts on d-file */
2164           v = VariantWildCastle;
2165           break;
2166         case 2:
2167         case 3:
2168         case 4:
2169           /* Castling illegal even if K & R happen to start in
2170              normal positions. */
2171           v = VariantNoCastle;
2172           break;
2173         case 5:
2174         case 7:
2175         case 8:
2176         case 10:
2177         case 11:
2178         case 12:
2179         case 13:
2180         case 14:
2181         case 15:
2182         case 18:
2183         case 19:
2184           /* Castling legal iff K & R start in normal positions */
2185           v = VariantNormal;
2186           break;
2187         case 6:
2188         case 20:
2189         case 21:
2190           /* Special wilds for position setup; unclear what to do here */
2191           v = VariantLoadable;
2192           break;
2193         case 9:
2194           /* Bizarre ICC game */
2195           v = VariantTwoKings;
2196           break;
2197         case 16:
2198           v = VariantKriegspiel;
2199           break;
2200         case 17:
2201           v = VariantLosers;
2202           break;
2203         case 22:
2204           v = VariantFischeRandom;
2205           break;
2206         case 23:
2207           v = VariantCrazyhouse;
2208           break;
2209         case 24:
2210           v = VariantBughouse;
2211           break;
2212         case 25:
2213           v = Variant3Check;
2214           break;
2215         case 26:
2216           /* Not quite the same as FICS suicide! */
2217           v = VariantGiveaway;
2218           break;
2219         case 27:
2220           v = VariantAtomic;
2221           break;
2222         case 28:
2223           v = VariantShatranj;
2224           break;
2225
2226         /* Temporary names for future ICC types.  The name *will* change in
2227            the next xboard/WinBoard release after ICC defines it. */
2228         case 29:
2229           v = Variant29;
2230           break;
2231         case 30:
2232           v = Variant30;
2233           break;
2234         case 31:
2235           v = Variant31;
2236           break;
2237         case 32:
2238           v = Variant32;
2239           break;
2240         case 33:
2241           v = Variant33;
2242           break;
2243         case 34:
2244           v = Variant34;
2245           break;
2246         case 35:
2247           v = Variant35;
2248           break;
2249         case 36:
2250           v = Variant36;
2251           break;
2252         case 37:
2253           v = VariantShogi;
2254           break;
2255         case 38:
2256           v = VariantXiangqi;
2257           break;
2258         case 39:
2259           v = VariantCourier;
2260           break;
2261         case 40:
2262           v = VariantGothic;
2263           break;
2264         case 41:
2265           v = VariantCapablanca;
2266           break;
2267         case 42:
2268           v = VariantKnightmate;
2269           break;
2270         case 43:
2271           v = VariantFairy;
2272           break;
2273         case 44:
2274           v = VariantCylinder;
2275           break;
2276         case 45:
2277           v = VariantFalcon;
2278           break;
2279         case 46:
2280           v = VariantCapaRandom;
2281           break;
2282         case 47:
2283           v = VariantBerolina;
2284           break;
2285         case 48:
2286           v = VariantJanus;
2287           break;
2288         case 49:
2289           v = VariantSuper;
2290           break;
2291         case 50:
2292           v = VariantGreat;
2293           break;
2294         case -1:
2295           /* Found "wild" or "w" in the string but no number;
2296              must assume it's normal chess. */
2297           v = VariantNormal;
2298           break;
2299         default:
2300           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2301           if( (len >= MSG_SIZ) && appData.debugMode )
2302             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2303
2304           DisplayError(buf, 0);
2305           v = VariantUnknown;
2306           break;
2307         }
2308       }
2309     }
2310     if (appData.debugMode) {
2311       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2312               e, wnum, VariantName(v));
2313     }
2314     return v;
2315 }
2316
2317 static int leftover_start = 0, leftover_len = 0;
2318 char star_match[STAR_MATCH_N][MSG_SIZ];
2319
2320 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2321    advance *index beyond it, and set leftover_start to the new value of
2322    *index; else return FALSE.  If pattern contains the character '*', it
2323    matches any sequence of characters not containing '\r', '\n', or the
2324    character following the '*' (if any), and the matched sequence(s) are
2325    copied into star_match.
2326    */
2327 int
2328 looking_at ( char *buf, int *index, char *pattern)
2329 {
2330     char *bufp = &buf[*index], *patternp = pattern;
2331     int star_count = 0;
2332     char *matchp = star_match[0];
2333
2334     for (;;) {
2335         if (*patternp == NULLCHAR) {
2336             *index = leftover_start = bufp - buf;
2337             *matchp = NULLCHAR;
2338             return TRUE;
2339         }
2340         if (*bufp == NULLCHAR) return FALSE;
2341         if (*patternp == '*') {
2342             if (*bufp == *(patternp + 1)) {
2343                 *matchp = NULLCHAR;
2344                 matchp = star_match[++star_count];
2345                 patternp += 2;
2346                 bufp++;
2347                 continue;
2348             } else if (*bufp == '\n' || *bufp == '\r') {
2349                 patternp++;
2350                 if (*patternp == NULLCHAR)
2351                   continue;
2352                 else
2353                   return FALSE;
2354             } else {
2355                 *matchp++ = *bufp++;
2356                 continue;
2357             }
2358         }
2359         if (*patternp != *bufp) return FALSE;
2360         patternp++;
2361         bufp++;
2362     }
2363 }
2364
2365 void
2366 SendToPlayer (char *data, int length)
2367 {
2368     int error, outCount;
2369     outCount = OutputToProcess(NoProc, data, length, &error);
2370     if (outCount < length) {
2371         DisplayFatalError(_("Error writing to display"), error, 1);
2372     }
2373 }
2374
2375 void
2376 PackHolding (char packed[], char *holding)
2377 {
2378     char *p = holding;
2379     char *q = packed;
2380     int runlength = 0;
2381     int curr = 9999;
2382     do {
2383         if (*p == curr) {
2384             runlength++;
2385         } else {
2386             switch (runlength) {
2387               case 0:
2388                 break;
2389               case 1:
2390                 *q++ = curr;
2391                 break;
2392               case 2:
2393                 *q++ = curr;
2394                 *q++ = curr;
2395                 break;
2396               default:
2397                 sprintf(q, "%d", runlength);
2398                 while (*q) q++;
2399                 *q++ = curr;
2400                 break;
2401             }
2402             runlength = 1;
2403             curr = *p;
2404         }
2405     } while (*p++);
2406     *q = NULLCHAR;
2407 }
2408
2409 /* Telnet protocol requests from the front end */
2410 void
2411 TelnetRequest (unsigned char ddww, unsigned char option)
2412 {
2413     unsigned char msg[3];
2414     int outCount, outError;
2415
2416     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2417
2418     if (appData.debugMode) {
2419         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2420         switch (ddww) {
2421           case TN_DO:
2422             ddwwStr = "DO";
2423             break;
2424           case TN_DONT:
2425             ddwwStr = "DONT";
2426             break;
2427           case TN_WILL:
2428             ddwwStr = "WILL";
2429             break;
2430           case TN_WONT:
2431             ddwwStr = "WONT";
2432             break;
2433           default:
2434             ddwwStr = buf1;
2435             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2436             break;
2437         }
2438         switch (option) {
2439           case TN_ECHO:
2440             optionStr = "ECHO";
2441             break;
2442           default:
2443             optionStr = buf2;
2444             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2445             break;
2446         }
2447         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2448     }
2449     msg[0] = TN_IAC;
2450     msg[1] = ddww;
2451     msg[2] = option;
2452     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2453     if (outCount < 3) {
2454         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2455     }
2456 }
2457
2458 void
2459 DoEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DO, TN_ECHO);
2463 }
2464
2465 void
2466 DontEcho ()
2467 {
2468     if (!appData.icsActive) return;
2469     TelnetRequest(TN_DONT, TN_ECHO);
2470 }
2471
2472 void
2473 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2474 {
2475     /* put the holdings sent to us by the server on the board holdings area */
2476     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2477     char p;
2478     ChessSquare piece;
2479
2480     if(gameInfo.holdingsWidth < 2)  return;
2481     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2482         return; // prevent overwriting by pre-board holdings
2483
2484     if( (int)lowestPiece >= BlackPawn ) {
2485         holdingsColumn = 0;
2486         countsColumn = 1;
2487         holdingsStartRow = BOARD_HEIGHT-1;
2488         direction = -1;
2489     } else {
2490         holdingsColumn = BOARD_WIDTH-1;
2491         countsColumn = BOARD_WIDTH-2;
2492         holdingsStartRow = 0;
2493         direction = 1;
2494     }
2495
2496     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2497         board[i][holdingsColumn] = EmptySquare;
2498         board[i][countsColumn]   = (ChessSquare) 0;
2499     }
2500     while( (p=*holdings++) != NULLCHAR ) {
2501         piece = CharToPiece( ToUpper(p) );
2502         if(piece == EmptySquare) continue;
2503         /*j = (int) piece - (int) WhitePawn;*/
2504         j = PieceToNumber(piece);
2505         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2506         if(j < 0) continue;               /* should not happen */
2507         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2508         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2509         board[holdingsStartRow+j*direction][countsColumn]++;
2510     }
2511 }
2512
2513
2514 void
2515 VariantSwitch (Board board, VariantClass newVariant)
2516 {
2517    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2518    static Board oldBoard;
2519
2520    startedFromPositionFile = FALSE;
2521    if(gameInfo.variant == newVariant) return;
2522
2523    /* [HGM] This routine is called each time an assignment is made to
2524     * gameInfo.variant during a game, to make sure the board sizes
2525     * are set to match the new variant. If that means adding or deleting
2526     * holdings, we shift the playing board accordingly
2527     * This kludge is needed because in ICS observe mode, we get boards
2528     * of an ongoing game without knowing the variant, and learn about the
2529     * latter only later. This can be because of the move list we requested,
2530     * in which case the game history is refilled from the beginning anyway,
2531     * but also when receiving holdings of a crazyhouse game. In the latter
2532     * case we want to add those holdings to the already received position.
2533     */
2534
2535
2536    if (appData.debugMode) {
2537      fprintf(debugFP, "Switch board from %s to %s\n",
2538              VariantName(gameInfo.variant), VariantName(newVariant));
2539      setbuf(debugFP, NULL);
2540    }
2541    shuffleOpenings = 0;       /* [HGM] shuffle */
2542    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2543    switch(newVariant)
2544      {
2545      case VariantShogi:
2546        newWidth = 9;  newHeight = 9;
2547        gameInfo.holdingsSize = 7;
2548      case VariantBughouse:
2549      case VariantCrazyhouse:
2550        newHoldingsWidth = 2; break;
2551      case VariantGreat:
2552        newWidth = 10;
2553      case VariantSuper:
2554        newHoldingsWidth = 2;
2555        gameInfo.holdingsSize = 8;
2556        break;
2557      case VariantGothic:
2558      case VariantCapablanca:
2559      case VariantCapaRandom:
2560        newWidth = 10;
2561      default:
2562        newHoldingsWidth = gameInfo.holdingsSize = 0;
2563      };
2564
2565    if(newWidth  != gameInfo.boardWidth  ||
2566       newHeight != gameInfo.boardHeight ||
2567       newHoldingsWidth != gameInfo.holdingsWidth ) {
2568
2569      /* shift position to new playing area, if needed */
2570      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2571        for(i=0; i<BOARD_HEIGHT; i++)
2572          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2573            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574              board[i][j];
2575        for(i=0; i<newHeight; i++) {
2576          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2577          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2578        }
2579      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2580        for(i=0; i<BOARD_HEIGHT; i++)
2581          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2582            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583              board[i][j];
2584      }
2585      board[HOLDINGS_SET] = 0;
2586      gameInfo.boardWidth  = newWidth;
2587      gameInfo.boardHeight = newHeight;
2588      gameInfo.holdingsWidth = newHoldingsWidth;
2589      gameInfo.variant = newVariant;
2590      InitDrawingSizes(-2, 0);
2591    } else gameInfo.variant = newVariant;
2592    CopyBoard(oldBoard, board);   // remember correctly formatted board
2593      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2594    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2595 }
2596
2597 static int loggedOn = FALSE;
2598
2599 /*-- Game start info cache: --*/
2600 int gs_gamenum;
2601 char gs_kind[MSG_SIZ];
2602 static char player1Name[128] = "";
2603 static char player2Name[128] = "";
2604 static char cont_seq[] = "\n\\   ";
2605 static int player1Rating = -1;
2606 static int player2Rating = -1;
2607 /*----------------------------*/
2608
2609 ColorClass curColor = ColorNormal;
2610 int suppressKibitz = 0;
2611
2612 // [HGM] seekgraph
2613 Boolean soughtPending = FALSE;
2614 Boolean seekGraphUp;
2615 #define MAX_SEEK_ADS 200
2616 #define SQUARE 0x80
2617 char *seekAdList[MAX_SEEK_ADS];
2618 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2619 float tcList[MAX_SEEK_ADS];
2620 char colorList[MAX_SEEK_ADS];
2621 int nrOfSeekAds = 0;
2622 int minRating = 1010, maxRating = 2800;
2623 int hMargin = 10, vMargin = 20, h, w;
2624 extern int squareSize, lineGap;
2625
2626 void
2627 PlotSeekAd (int i)
2628 {
2629         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2630         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2631         if(r < minRating+100 && r >=0 ) r = minRating+100;
2632         if(r > maxRating) r = maxRating;
2633         if(tc < 1.f) tc = 1.f;
2634         if(tc > 95.f) tc = 95.f;
2635         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2636         y = ((double)r - minRating)/(maxRating - minRating)
2637             * (h-vMargin-squareSize/8-1) + vMargin;
2638         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2639         if(strstr(seekAdList[i], " u ")) color = 1;
2640         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2641            !strstr(seekAdList[i], "bullet") &&
2642            !strstr(seekAdList[i], "blitz") &&
2643            !strstr(seekAdList[i], "standard") ) color = 2;
2644         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2645         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2646 }
2647
2648 void
2649 PlotSingleSeekAd (int i)
2650 {
2651         PlotSeekAd(i);
2652 }
2653
2654 void
2655 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2656 {
2657         char buf[MSG_SIZ], *ext = "";
2658         VariantClass v = StringToVariant(type);
2659         if(strstr(type, "wild")) {
2660             ext = type + 4; // append wild number
2661             if(v == VariantFischeRandom) type = "chess960"; else
2662             if(v == VariantLoadable) type = "setup"; else
2663             type = VariantName(v);
2664         }
2665         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2666         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2667             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2668             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2669             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2670             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2671             seekNrList[nrOfSeekAds] = nr;
2672             zList[nrOfSeekAds] = 0;
2673             seekAdList[nrOfSeekAds++] = StrSave(buf);
2674             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2675         }
2676 }
2677
2678 void
2679 EraseSeekDot (int i)
2680 {
2681     int x = xList[i], y = yList[i], d=squareSize/4, k;
2682     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2683     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2684     // now replot every dot that overlapped
2685     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2686         int xx = xList[k], yy = yList[k];
2687         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2688             DrawSeekDot(xx, yy, colorList[k]);
2689     }
2690 }
2691
2692 void
2693 RemoveSeekAd (int nr)
2694 {
2695         int i;
2696         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2697             EraseSeekDot(i);
2698             if(seekAdList[i]) free(seekAdList[i]);
2699             seekAdList[i] = seekAdList[--nrOfSeekAds];
2700             seekNrList[i] = seekNrList[nrOfSeekAds];
2701             ratingList[i] = ratingList[nrOfSeekAds];
2702             colorList[i]  = colorList[nrOfSeekAds];
2703             tcList[i] = tcList[nrOfSeekAds];
2704             xList[i]  = xList[nrOfSeekAds];
2705             yList[i]  = yList[nrOfSeekAds];
2706             zList[i]  = zList[nrOfSeekAds];
2707             seekAdList[nrOfSeekAds] = NULL;
2708             break;
2709         }
2710 }
2711
2712 Boolean
2713 MatchSoughtLine (char *line)
2714 {
2715     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2716     int nr, base, inc, u=0; char dummy;
2717
2718     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2719        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2720        (u=1) &&
2721        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2723         // match: compact and save the line
2724         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2725         return TRUE;
2726     }
2727     return FALSE;
2728 }
2729
2730 int
2731 DrawSeekGraph ()
2732 {
2733     int i;
2734     if(!seekGraphUp) return FALSE;
2735     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2736     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2737
2738     DrawSeekBackground(0, 0, w, h);
2739     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2740     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2741     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2742         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2743         yy = h-1-yy;
2744         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2745         if(i%500 == 0) {
2746             char buf[MSG_SIZ];
2747             snprintf(buf, MSG_SIZ, "%d", i);
2748             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2749         }
2750     }
2751     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2752     for(i=1; i<100; i+=(i<10?1:5)) {
2753         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2754         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2755         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2756             char buf[MSG_SIZ];
2757             snprintf(buf, MSG_SIZ, "%d", i);
2758             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2759         }
2760     }
2761     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2762     return TRUE;
2763 }
2764
2765 int
2766 SeekGraphClick (ClickType click, int x, int y, int moving)
2767 {
2768     static int lastDown = 0, displayed = 0, lastSecond;
2769     if(y < 0) return FALSE;
2770     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2771         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2772         if(!seekGraphUp) return FALSE;
2773         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2774         DrawPosition(TRUE, NULL);
2775         return TRUE;
2776     }
2777     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2778         if(click == Release || moving) return FALSE;
2779         nrOfSeekAds = 0;
2780         soughtPending = TRUE;
2781         SendToICS(ics_prefix);
2782         SendToICS("sought\n"); // should this be "sought all"?
2783     } else { // issue challenge based on clicked ad
2784         int dist = 10000; int i, closest = 0, second = 0;
2785         for(i=0; i<nrOfSeekAds; i++) {
2786             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2787             if(d < dist) { dist = d; closest = i; }
2788             second += (d - zList[i] < 120); // count in-range ads
2789             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2790         }
2791         if(dist < 120) {
2792             char buf[MSG_SIZ];
2793             second = (second > 1);
2794             if(displayed != closest || second != lastSecond) {
2795                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2796                 lastSecond = second; displayed = closest;
2797             }
2798             if(click == Press) {
2799                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2800                 lastDown = closest;
2801                 return TRUE;
2802             } // on press 'hit', only show info
2803             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2804             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2805             SendToICS(ics_prefix);
2806             SendToICS(buf);
2807             return TRUE; // let incoming board of started game pop down the graph
2808         } else if(click == Release) { // release 'miss' is ignored
2809             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2810             if(moving == 2) { // right up-click
2811                 nrOfSeekAds = 0; // refresh graph
2812                 soughtPending = TRUE;
2813                 SendToICS(ics_prefix);
2814                 SendToICS("sought\n"); // should this be "sought all"?
2815             }
2816             return TRUE;
2817         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2818         // press miss or release hit 'pop down' seek graph
2819         seekGraphUp = FALSE;
2820         DrawPosition(TRUE, NULL);
2821     }
2822     return TRUE;
2823 }
2824
2825 void
2826 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2827 {
2828 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2829 #define STARTED_NONE 0
2830 #define STARTED_MOVES 1
2831 #define STARTED_BOARD 2
2832 #define STARTED_OBSERVE 3
2833 #define STARTED_HOLDINGS 4
2834 #define STARTED_CHATTER 5
2835 #define STARTED_COMMENT 6
2836 #define STARTED_MOVES_NOHIDE 7
2837
2838     static int started = STARTED_NONE;
2839     static char parse[20000];
2840     static int parse_pos = 0;
2841     static char buf[BUF_SIZE + 1];
2842     static int firstTime = TRUE, intfSet = FALSE;
2843     static ColorClass prevColor = ColorNormal;
2844     static int savingComment = FALSE;
2845     static int cmatch = 0; // continuation sequence match
2846     char *bp;
2847     char str[MSG_SIZ];
2848     int i, oldi;
2849     int buf_len;
2850     int next_out;
2851     int tkind;
2852     int backup;    /* [DM] For zippy color lines */
2853     char *p;
2854     char talker[MSG_SIZ]; // [HGM] chat
2855     int channel, collective=0;
2856
2857     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2858
2859     if (appData.debugMode) {
2860       if (!error) {
2861         fprintf(debugFP, "<ICS: ");
2862         show_bytes(debugFP, data, count);
2863         fprintf(debugFP, "\n");
2864       }
2865     }
2866
2867     if (appData.debugMode) { int f = forwardMostMove;
2868         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2869                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2870                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2871     }
2872     if (count > 0) {
2873         /* If last read ended with a partial line that we couldn't parse,
2874            prepend it to the new read and try again. */
2875         if (leftover_len > 0) {
2876             for (i=0; i<leftover_len; i++)
2877               buf[i] = buf[leftover_start + i];
2878         }
2879
2880     /* copy new characters into the buffer */
2881     bp = buf + leftover_len;
2882     buf_len=leftover_len;
2883     for (i=0; i<count; i++)
2884     {
2885         // ignore these
2886         if (data[i] == '\r')
2887             continue;
2888
2889         // join lines split by ICS?
2890         if (!appData.noJoin)
2891         {
2892             /*
2893                 Joining just consists of finding matches against the
2894                 continuation sequence, and discarding that sequence
2895                 if found instead of copying it.  So, until a match
2896                 fails, there's nothing to do since it might be the
2897                 complete sequence, and thus, something we don't want
2898                 copied.
2899             */
2900             if (data[i] == cont_seq[cmatch])
2901             {
2902                 cmatch++;
2903                 if (cmatch == strlen(cont_seq))
2904                 {
2905                     cmatch = 0; // complete match.  just reset the counter
2906
2907                     /*
2908                         it's possible for the ICS to not include the space
2909                         at the end of the last word, making our [correct]
2910                         join operation fuse two separate words.  the server
2911                         does this when the space occurs at the width setting.
2912                     */
2913                     if (!buf_len || buf[buf_len-1] != ' ')
2914                     {
2915                         *bp++ = ' ';
2916                         buf_len++;
2917                     }
2918                 }
2919                 continue;
2920             }
2921             else if (cmatch)
2922             {
2923                 /*
2924                     match failed, so we have to copy what matched before
2925                     falling through and copying this character.  In reality,
2926                     this will only ever be just the newline character, but
2927                     it doesn't hurt to be precise.
2928                 */
2929                 strncpy(bp, cont_seq, cmatch);
2930                 bp += cmatch;
2931                 buf_len += cmatch;
2932                 cmatch = 0;
2933             }
2934         }
2935
2936         // copy this char
2937         *bp++ = data[i];
2938         buf_len++;
2939     }
2940
2941         buf[buf_len] = NULLCHAR;
2942 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2943         next_out = 0;
2944         leftover_start = 0;
2945
2946         i = 0;
2947         while (i < buf_len) {
2948             /* Deal with part of the TELNET option negotiation
2949                protocol.  We refuse to do anything beyond the
2950                defaults, except that we allow the WILL ECHO option,
2951                which ICS uses to turn off password echoing when we are
2952                directly connected to it.  We reject this option
2953                if localLineEditing mode is on (always on in xboard)
2954                and we are talking to port 23, which might be a real
2955                telnet server that will try to keep WILL ECHO on permanently.
2956              */
2957             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2958                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2959                 unsigned char option;
2960                 oldi = i;
2961                 switch ((unsigned char) buf[++i]) {
2962                   case TN_WILL:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<WILL ");
2965                     switch (option = (unsigned char) buf[++i]) {
2966                       case TN_ECHO:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "ECHO ");
2969                         /* Reply only if this is a change, according
2970                            to the protocol rules. */
2971                         if (remoteEchoOption) break;
2972                         if (appData.localLineEditing &&
2973                             atoi(appData.icsPort) == TN_PORT) {
2974                             TelnetRequest(TN_DONT, TN_ECHO);
2975                         } else {
2976                             EchoOff();
2977                             TelnetRequest(TN_DO, TN_ECHO);
2978                             remoteEchoOption = TRUE;
2979                         }
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", option);
2984                         /* Whatever this is, we don't want it. */
2985                         TelnetRequest(TN_DONT, option);
2986                         break;
2987                     }
2988                     break;
2989                   case TN_WONT:
2990                     if (appData.debugMode)
2991                       fprintf(debugFP, "\n<WONT ");
2992                     switch (option = (unsigned char) buf[++i]) {
2993                       case TN_ECHO:
2994                         if (appData.debugMode)
2995                           fprintf(debugFP, "ECHO ");
2996                         /* Reply only if this is a change, according
2997                            to the protocol rules. */
2998                         if (!remoteEchoOption) break;
2999                         EchoOn();
3000                         TelnetRequest(TN_DONT, TN_ECHO);
3001                         remoteEchoOption = FALSE;
3002                         break;
3003                       default:
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", (unsigned char) option);
3006                         /* Whatever this is, it must already be turned
3007                            off, because we never agree to turn on
3008                            anything non-default, so according to the
3009                            protocol rules, we don't reply. */
3010                         break;
3011                     }
3012                     break;
3013                   case TN_DO:
3014                     if (appData.debugMode)
3015                       fprintf(debugFP, "\n<DO ");
3016                     switch (option = (unsigned char) buf[++i]) {
3017                       default:
3018                         /* Whatever this is, we refuse to do it. */
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         TelnetRequest(TN_WONT, option);
3022                         break;
3023                     }
3024                     break;
3025                   case TN_DONT:
3026                     if (appData.debugMode)
3027                       fprintf(debugFP, "\n<DONT ");
3028                     switch (option = (unsigned char) buf[++i]) {
3029                       default:
3030                         if (appData.debugMode)
3031                           fprintf(debugFP, "%d ", option);
3032                         /* Whatever this is, we are already not doing
3033                            it, because we never agree to do anything
3034                            non-default, so according to the protocol
3035                            rules, we don't reply. */
3036                         break;
3037                     }
3038                     break;
3039                   case TN_IAC:
3040                     if (appData.debugMode)
3041                       fprintf(debugFP, "\n<IAC ");
3042                     /* Doubled IAC; pass it through */
3043                     i--;
3044                     break;
3045                   default:
3046                     if (appData.debugMode)
3047                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3048                     /* Drop all other telnet commands on the floor */
3049                     break;
3050                 }
3051                 if (oldi > next_out)
3052                   SendToPlayer(&buf[next_out], oldi - next_out);
3053                 if (++i > next_out)
3054                   next_out = i;
3055                 continue;
3056             }
3057
3058             /* OK, this at least will *usually* work */
3059             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3060                 loggedOn = TRUE;
3061             }
3062
3063             if (loggedOn && !intfSet) {
3064                 if (ics_type == ICS_ICC) {
3065                   snprintf(str, MSG_SIZ,
3066                           "/set-quietly interface %s\n/set-quietly style 12\n",
3067                           programVersion);
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3070                 } else if (ics_type == ICS_CHESSNET) {
3071                   snprintf(str, MSG_SIZ, "/style 12\n");
3072                 } else {
3073                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3074                   strcat(str, programVersion);
3075                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3076                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3078 #ifdef WIN32
3079                   strcat(str, "$iset nohighlight 1\n");
3080 #endif
3081                   strcat(str, "$iset lock 1\n$style 12\n");
3082                 }
3083                 SendToICS(str);
3084                 NotifyFrontendLogin();
3085                 intfSet = TRUE;
3086             }
3087
3088             if (started == STARTED_COMMENT) {
3089                 /* Accumulate characters in comment */
3090                 parse[parse_pos++] = buf[i];
3091                 if (buf[i] == '\n') {
3092                     parse[parse_pos] = NULLCHAR;
3093                     if(chattingPartner>=0) {
3094                         char mess[MSG_SIZ];
3095                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3096                         OutputChatMessage(chattingPartner, mess);
3097                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3098                             int p;
3099                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3100                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3101                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3102                                 OutputChatMessage(p, mess);
3103                                 break;
3104                             }
3105                         }
3106                         chattingPartner = -1;
3107                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3108                         collective = 0;
3109                     } else
3110                     if(!suppressKibitz) // [HGM] kibitz
3111                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3112                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3113                         int nrDigit = 0, nrAlph = 0, j;
3114                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3115                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3116                         parse[parse_pos] = NULLCHAR;
3117                         // try to be smart: if it does not look like search info, it should go to
3118                         // ICS interaction window after all, not to engine-output window.
3119                         for(j=0; j<parse_pos; j++) { // count letters and digits
3120                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3121                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3122                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3123                         }
3124                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3125                             int depth=0; float score;
3126                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3127                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3128                                 pvInfoList[forwardMostMove-1].depth = depth;
3129                                 pvInfoList[forwardMostMove-1].score = 100*score;
3130                             }
3131                             OutputKibitz(suppressKibitz, parse);
3132                         } else {
3133                             char tmp[MSG_SIZ];
3134                             if(gameMode == IcsObserving) // restore original ICS messages
3135                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3136                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3137                             else
3138                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3140                             SendToPlayer(tmp, strlen(tmp));
3141                         }
3142                         next_out = i+1; // [HGM] suppress printing in ICS window
3143                     }
3144                     started = STARTED_NONE;
3145                 } else {
3146                     /* Don't match patterns against characters in comment */
3147                     i++;
3148                     continue;
3149                 }
3150             }
3151             if (started == STARTED_CHATTER) {
3152                 if (buf[i] != '\n') {
3153                     /* Don't match patterns against characters in chatter */
3154                     i++;
3155                     continue;
3156                 }
3157                 started = STARTED_NONE;
3158                 if(suppressKibitz) next_out = i+1;
3159             }
3160
3161             /* Kludge to deal with rcmd protocol */
3162             if (firstTime && looking_at(buf, &i, "\001*")) {
3163                 DisplayFatalError(&buf[1], 0, 1);
3164                 continue;
3165             } else {
3166                 firstTime = FALSE;
3167             }
3168
3169             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3170                 ics_type = ICS_ICC;
3171                 ics_prefix = "/";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3177                 ics_type = ICS_FICS;
3178                 ics_prefix = "$";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3184                 ics_type = ICS_CHESSNET;
3185                 ics_prefix = "/";
3186                 if (appData.debugMode)
3187                   fprintf(debugFP, "ics_type %d\n", ics_type);
3188                 continue;
3189             }
3190
3191             if (!loggedOn &&
3192                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3193                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3194                  looking_at(buf, &i, "will be \"*\""))) {
3195               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3196               continue;
3197             }
3198
3199             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3200               char buf[MSG_SIZ];
3201               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3202               DisplayIcsInteractionTitle(buf);
3203               have_set_title = TRUE;
3204             }
3205
3206             /* skip finger notes */
3207             if (started == STARTED_NONE &&
3208                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3209                  (buf[i] == '1' && buf[i+1] == '0')) &&
3210                 buf[i+2] == ':' && buf[i+3] == ' ') {
3211               started = STARTED_CHATTER;
3212               i += 3;
3213               continue;
3214             }
3215
3216             oldi = i;
3217             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3218             if(appData.seekGraph) {
3219                 if(soughtPending && MatchSoughtLine(buf+i)) {
3220                     i = strstr(buf+i, "rated") - buf;
3221                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                     next_out = leftover_start = i;
3223                     started = STARTED_CHATTER;
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3228                         && looking_at(buf, &i, "* ads displayed")) {
3229                     soughtPending = FALSE;
3230                     seekGraphUp = TRUE;
3231                     DrawSeekGraph();
3232                     continue;
3233                 }
3234                 if(appData.autoRefresh) {
3235                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3236                         int s = (ics_type == ICS_ICC); // ICC format differs
3237                         if(seekGraphUp)
3238                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3239                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3240                         looking_at(buf, &i, "*% "); // eat prompt
3241                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i; // suppress
3244                         continue;
3245                     }
3246                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3247                         char *p = star_match[0];
3248                         while(*p) {
3249                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3250                             while(*p && *p++ != ' '); // next
3251                         }
3252                         looking_at(buf, &i, "*% "); // eat prompt
3253                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254                         next_out = i;
3255                         continue;
3256                     }
3257                 }
3258             }
3259
3260             /* skip formula vars */
3261             if (started == STARTED_NONE &&
3262                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3263               started = STARTED_CHATTER;
3264               i += 3;
3265               continue;
3266             }
3267
3268             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3269             if (appData.autoKibitz && started == STARTED_NONE &&
3270                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3271                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3272                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3273                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3274                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3275                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3276                         suppressKibitz = TRUE;
3277                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278                         next_out = i;
3279                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3280                                 && (gameMode == IcsPlayingWhite)) ||
3281                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3282                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3283                             started = STARTED_CHATTER; // own kibitz we simply discard
3284                         else {
3285                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3286                             parse_pos = 0; parse[0] = NULLCHAR;
3287                             savingComment = TRUE;
3288                             suppressKibitz = gameMode != IcsObserving ? 2 :
3289                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3290                         }
3291                         continue;
3292                 } else
3293                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3294                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3295                          && atoi(star_match[0])) {
3296                     // suppress the acknowledgements of our own autoKibitz
3297                     char *p;
3298                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3300                     SendToPlayer(star_match[0], strlen(star_match[0]));
3301                     if(looking_at(buf, &i, "*% ")) // eat prompt
3302                         suppressKibitz = FALSE;
3303                     next_out = i;
3304                     continue;
3305                 }
3306             } // [HGM] kibitz: end of patch
3307
3308             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3309
3310             // [HGM] chat: intercept tells by users for which we have an open chat window
3311             channel = -1;
3312             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3313                                            looking_at(buf, &i, "* whispers:") ||
3314                                            looking_at(buf, &i, "* kibitzes:") ||
3315                                            looking_at(buf, &i, "* shouts:") ||
3316                                            looking_at(buf, &i, "* c-shouts:") ||
3317                                            looking_at(buf, &i, "--> * ") ||
3318                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3322                 int p;
3323                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3324                 chattingPartner = -1; collective = 0;
3325
3326                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3327                 for(p=0; p<MAX_CHAT; p++) {
3328                     collective = 1;
3329                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3330                     talker[0] = '['; strcat(talker, "] ");
3331                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3332                     chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("kibitzes", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3344                 for(p=0; p<MAX_CHAT; p++) {
3345                     collective = 1;
3346                     if(!strcmp("whispers", chatPartner[p])) {
3347                         talker[0] = '['; strcat(talker, "] ");
3348                         chattingPartner = p; break;
3349                     }
3350                 } else
3351                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3352                   if(buf[i-8] == '-' && buf[i-3] == 't')
3353                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3354                     collective = 1;
3355                     if(!strcmp("c-shouts", chatPartner[p])) {
3356                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3357                         chattingPartner = p; break;
3358                     }
3359                   }
3360                   if(chattingPartner < 0)
3361                   for(p=0; p<MAX_CHAT; p++) {
3362                     collective = 1;
3363                     if(!strcmp("shouts", chatPartner[p])) {
3364                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3365                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3366                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3367                         chattingPartner = p; break;
3368                     }
3369                   }
3370                 }
3371                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3372                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3373                     talker[0] = 0;
3374                     Colorize(ColorTell, FALSE);
3375                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3376                     collective |= 2;
3377                     chattingPartner = p; break;
3378                 }
3379                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3380                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3381                     started = STARTED_COMMENT;
3382                     parse_pos = 0; parse[0] = NULLCHAR;
3383                     savingComment = 3 + chattingPartner; // counts as TRUE
3384                     if(collective == 3) i = oldi; else {
3385                         suppressKibitz = TRUE;
3386                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3387                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3388                         continue;
3389                     }
3390                 }
3391             } // [HGM] chat: end of patch
3392
3393           backup = i;
3394             if (appData.zippyTalk || appData.zippyPlay) {
3395                 /* [DM] Backup address for color zippy lines */
3396 #if ZIPPY
3397                if (loggedOn == TRUE)
3398                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3399                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3400                        ;
3401 #endif
3402             } // [DM] 'else { ' deleted
3403                 if (
3404                     /* Regular tells and says */
3405                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3406                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3407                     looking_at(buf, &i, "* says: ") ||
3408                     /* Don't color "message" or "messages" output */
3409                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3410                     looking_at(buf, &i, "*. * at *:*: ") ||
3411                     looking_at(buf, &i, "--* (*:*): ") ||
3412                     /* Message notifications (same color as tells) */
3413                     looking_at(buf, &i, "* has left a message ") ||
3414                     looking_at(buf, &i, "* just sent you a message:\n") ||
3415                     /* Whispers and kibitzes */
3416                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3417                     looking_at(buf, &i, "* kibitzes: ") ||
3418                     /* Channel tells */
3419                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3420
3421                   if (tkind == 1 && strchr(star_match[0], ':')) {
3422                       /* Avoid "tells you:" spoofs in channels */
3423                      tkind = 3;
3424                   }
3425                   if (star_match[0][0] == NULLCHAR ||
3426                       strchr(star_match[0], ' ') ||
3427                       (tkind == 3 && strchr(star_match[1], ' '))) {
3428                     /* Reject bogus matches */
3429                     i = oldi;
3430                   } else {
3431                     if (appData.colorize) {
3432                       if (oldi > next_out) {
3433                         SendToPlayer(&buf[next_out], oldi - next_out);
3434                         next_out = oldi;
3435                       }
3436                       switch (tkind) {
3437                       case 1:
3438                         Colorize(ColorTell, FALSE);
3439                         curColor = ColorTell;
3440                         break;
3441                       case 2:
3442                         Colorize(ColorKibitz, FALSE);
3443                         curColor = ColorKibitz;
3444                         break;
3445                       case 3:
3446                         p = strrchr(star_match[1], '(');
3447                         if (p == NULL) {
3448                           p = star_match[1];
3449                         } else {
3450                           p++;
3451                         }
3452                         if (atoi(p) == 1) {
3453                           Colorize(ColorChannel1, FALSE);
3454                           curColor = ColorChannel1;
3455                         } else {
3456                           Colorize(ColorChannel, FALSE);
3457                           curColor = ColorChannel;
3458                         }
3459                         break;
3460                       case 5:
3461                         curColor = ColorNormal;
3462                         break;
3463                       }
3464                     }
3465                     if (started == STARTED_NONE && appData.autoComment &&
3466                         (gameMode == IcsObserving ||
3467                          gameMode == IcsPlayingWhite ||
3468                          gameMode == IcsPlayingBlack)) {
3469                       parse_pos = i - oldi;
3470                       memcpy(parse, &buf[oldi], parse_pos);
3471                       parse[parse_pos] = NULLCHAR;
3472                       started = STARTED_COMMENT;
3473                       savingComment = TRUE;
3474                     } else if(collective != 3) {
3475                       started = STARTED_CHATTER;
3476                       savingComment = FALSE;
3477                     }
3478                     loggedOn = TRUE;
3479                     continue;
3480                   }
3481                 }
3482
3483                 if (looking_at(buf, &i, "* s-shouts: ") ||
3484                     looking_at(buf, &i, "* c-shouts: ")) {
3485                     if (appData.colorize) {
3486                         if (oldi > next_out) {
3487                             SendToPlayer(&buf[next_out], oldi - next_out);
3488                             next_out = oldi;
3489                         }
3490                         Colorize(ColorSShout, FALSE);
3491                         curColor = ColorSShout;
3492                     }
3493                     loggedOn = TRUE;
3494                     started = STARTED_CHATTER;
3495                     continue;
3496                 }
3497
3498                 if (looking_at(buf, &i, "--->")) {
3499                     loggedOn = TRUE;
3500                     continue;
3501                 }
3502
3503                 if (looking_at(buf, &i, "* shouts: ") ||
3504                     looking_at(buf, &i, "--> ")) {
3505                     if (appData.colorize) {
3506                         if (oldi > next_out) {
3507                             SendToPlayer(&buf[next_out], oldi - next_out);
3508                             next_out = oldi;
3509                         }
3510                         Colorize(ColorShout, FALSE);
3511                         curColor = ColorShout;
3512                     }
3513                     loggedOn = TRUE;
3514                     started = STARTED_CHATTER;
3515                     continue;
3516                 }
3517
3518                 if (looking_at( buf, &i, "Challenge:")) {
3519                     if (appData.colorize) {
3520                         if (oldi > next_out) {
3521                             SendToPlayer(&buf[next_out], oldi - next_out);
3522                             next_out = oldi;
3523                         }
3524                         Colorize(ColorChallenge, FALSE);
3525                         curColor = ColorChallenge;
3526                     }
3527                     loggedOn = TRUE;
3528                     continue;
3529                 }
3530
3531                 if (looking_at(buf, &i, "* offers you") ||
3532                     looking_at(buf, &i, "* offers to be") ||
3533                     looking_at(buf, &i, "* would like to") ||
3534                     looking_at(buf, &i, "* requests to") ||
3535                     looking_at(buf, &i, "Your opponent offers") ||
3536                     looking_at(buf, &i, "Your opponent requests")) {
3537
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorRequest, FALSE);
3544                         curColor = ColorRequest;
3545                     }
3546                     continue;
3547                 }
3548
3549                 if (looking_at(buf, &i, "* (*) seeking")) {
3550                     if (appData.colorize) {
3551                         if (oldi > next_out) {
3552                             SendToPlayer(&buf[next_out], oldi - next_out);
3553                             next_out = oldi;
3554                         }
3555                         Colorize(ColorSeek, FALSE);
3556                         curColor = ColorSeek;
3557                     }
3558                     continue;
3559             }
3560
3561           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3562
3563             if (looking_at(buf, &i, "\\   ")) {
3564                 if (prevColor != ColorNormal) {
3565                     if (oldi > next_out) {
3566                         SendToPlayer(&buf[next_out], oldi - next_out);
3567                         next_out = oldi;
3568                     }
3569                     Colorize(prevColor, TRUE);
3570                     curColor = prevColor;
3571                 }
3572                 if (savingComment) {
3573                     parse_pos = i - oldi;
3574                     memcpy(parse, &buf[oldi], parse_pos);
3575                     parse[parse_pos] = NULLCHAR;
3576                     started = STARTED_COMMENT;
3577                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3578                         chattingPartner = savingComment - 3; // kludge to remember the box
3579                 } else {
3580                     started = STARTED_CHATTER;
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i, "Black Strength :") ||
3586                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3587                 looking_at(buf, &i, "<10>") ||
3588                 looking_at(buf, &i, "#@#")) {
3589                 /* Wrong board style */
3590                 loggedOn = TRUE;
3591                 SendToICS(ics_prefix);
3592                 SendToICS("set style 12\n");
3593                 SendToICS(ics_prefix);
3594                 SendToICS("refresh\n");
3595                 continue;
3596             }
3597
3598             if (looking_at(buf, &i, "login:")) {
3599               if (!have_sent_ICS_logon) {
3600                 if(ICSInitScript())
3601                   have_sent_ICS_logon = 1;
3602                 else // no init script was found
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3604               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3605                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3606               }
3607                 continue;
3608             }
3609
3610             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3611                 (looking_at(buf, &i, "\n<12> ") ||
3612                  looking_at(buf, &i, "<12> "))) {
3613                 loggedOn = TRUE;
3614                 if (oldi > next_out) {
3615                     SendToPlayer(&buf[next_out], oldi - next_out);
3616                 }
3617                 next_out = i;
3618                 started = STARTED_BOARD;
3619                 parse_pos = 0;
3620                 continue;
3621             }
3622
3623             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3624                 looking_at(buf, &i, "<b1> ")) {
3625                 if (oldi > next_out) {
3626                     SendToPlayer(&buf[next_out], oldi - next_out);
3627                 }
3628                 next_out = i;
3629                 started = STARTED_HOLDINGS;
3630                 parse_pos = 0;
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3635                 loggedOn = TRUE;
3636                 /* Header for a move list -- first line */
3637
3638                 switch (ics_getting_history) {
3639                   case H_FALSE:
3640                     switch (gameMode) {
3641                       case IcsIdle:
3642                       case BeginningOfGame:
3643                         /* User typed "moves" or "oldmoves" while we
3644                            were idle.  Pretend we asked for these
3645                            moves and soak them up so user can step
3646                            through them and/or save them.
3647                            */
3648                         Reset(FALSE, TRUE);
3649                         gameMode = IcsObserving;
3650                         ModeHighlight();
3651                         ics_gamenum = -1;
3652                         ics_getting_history = H_GOT_UNREQ_HEADER;
3653                         break;
3654                       case EditGame: /*?*/
3655                       case EditPosition: /*?*/
3656                         /* Should above feature work in these modes too? */
3657                         /* For now it doesn't */
3658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3659                         break;
3660                       default:
3661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3662                         break;
3663                     }
3664                     break;
3665                   case H_REQUESTED:
3666                     /* Is this the right one? */
3667                     if (gameInfo.white && gameInfo.black &&
3668                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3669                         strcmp(gameInfo.black, star_match[2]) == 0) {
3670                         /* All is well */
3671                         ics_getting_history = H_GOT_REQ_HEADER;
3672                     }
3673                     break;
3674                   case H_GOT_REQ_HEADER:
3675                   case H_GOT_UNREQ_HEADER:
3676                   case H_GOT_UNWANTED_HEADER:
3677                   case H_GETTING_MOVES:
3678                     /* Should not happen */
3679                     DisplayError(_("Error gathering move list: two headers"), 0);
3680                     ics_getting_history = H_FALSE;
3681                     break;
3682                 }
3683
3684                 /* Save player ratings into gameInfo if needed */
3685                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3686                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3687                     (gameInfo.whiteRating == -1 ||
3688                      gameInfo.blackRating == -1)) {
3689
3690                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3691                     gameInfo.blackRating = string_to_rating(star_match[3]);
3692                     if (appData.debugMode)
3693                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3694                               gameInfo.whiteRating, gameInfo.blackRating);
3695                 }
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i,
3700               "* * match, initial time: * minute*, increment: * second")) {
3701                 /* Header for a move list -- second line */
3702                 /* Initial board will follow if this is a wild game */
3703                 if (gameInfo.event != NULL) free(gameInfo.event);
3704                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3705                 gameInfo.event = StrSave(str);
3706                 /* [HGM] we switched variant. Translate boards if needed. */
3707                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3708                 continue;
3709             }
3710
3711             if (looking_at(buf, &i, "Move  ")) {
3712                 /* Beginning of a move list */
3713                 switch (ics_getting_history) {
3714                   case H_FALSE:
3715                     /* Normally should not happen */
3716                     /* Maybe user hit reset while we were parsing */
3717                     break;
3718                   case H_REQUESTED:
3719                     /* Happens if we are ignoring a move list that is not
3720                      * the one we just requested.  Common if the user
3721                      * tries to observe two games without turning off
3722                      * getMoveList */
3723                     break;
3724                   case H_GETTING_MOVES:
3725                     /* Should not happen */
3726                     DisplayError(_("Error gathering move list: nested"), 0);
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                   case H_GOT_REQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES;
3732                     parse_pos = 0;
3733                     if (oldi > next_out) {
3734                         SendToPlayer(&buf[next_out], oldi - next_out);
3735                     }
3736                     break;
3737                   case H_GOT_UNREQ_HEADER:
3738                     ics_getting_history = H_GETTING_MOVES;
3739                     started = STARTED_MOVES_NOHIDE;
3740                     parse_pos = 0;
3741                     break;
3742                   case H_GOT_UNWANTED_HEADER:
3743                     ics_getting_history = H_FALSE;
3744                     break;
3745                 }
3746                 continue;
3747             }
3748
3749             if (looking_at(buf, &i, "% ") ||
3750                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3751                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3752                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3753                     soughtPending = FALSE;
3754                     seekGraphUp = TRUE;
3755                     DrawSeekGraph();
3756                 }
3757                 if(suppressKibitz) next_out = i;
3758                 savingComment = FALSE;
3759                 suppressKibitz = 0;
3760                 switch (started) {
3761                   case STARTED_MOVES:
3762                   case STARTED_MOVES_NOHIDE:
3763                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3764                     parse[parse_pos + i - oldi] = NULLCHAR;
3765                     ParseGameHistory(parse);
3766 #if ZIPPY
3767                     if (appData.zippyPlay && first.initDone) {
3768                         FeedMovesToProgram(&first, forwardMostMove);
3769                         if (gameMode == IcsPlayingWhite) {
3770                             if (WhiteOnMove(forwardMostMove)) {
3771                                 if (first.sendTime) {
3772                                   if (first.useColors) {
3773                                     SendToProgram("black\n", &first);
3774                                   }
3775                                   SendTimeRemaining(&first, TRUE);
3776                                 }
3777                                 if (first.useColors) {
3778                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3779                                 }
3780                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3781                                 first.maybeThinking = TRUE;
3782                             } else {
3783                                 if (first.usePlayother) {
3784                                   if (first.sendTime) {
3785                                     SendTimeRemaining(&first, TRUE);
3786                                   }
3787                                   SendToProgram("playother\n", &first);
3788                                   firstMove = FALSE;
3789                                 } else {
3790                                   firstMove = TRUE;
3791                                 }
3792                             }
3793                         } else if (gameMode == IcsPlayingBlack) {
3794                             if (!WhiteOnMove(forwardMostMove)) {
3795                                 if (first.sendTime) {
3796                                   if (first.useColors) {
3797                                     SendToProgram("white\n", &first);
3798                                   }
3799                                   SendTimeRemaining(&first, FALSE);
3800                                 }
3801                                 if (first.useColors) {
3802                                   SendToProgram("black\n", &first);
3803                                 }
3804                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3805                                 first.maybeThinking = TRUE;
3806                             } else {
3807                                 if (first.usePlayother) {
3808                                   if (first.sendTime) {
3809                                     SendTimeRemaining(&first, FALSE);
3810                                   }
3811                                   SendToProgram("playother\n", &first);
3812                                   firstMove = FALSE;
3813                                 } else {
3814                                   firstMove = TRUE;
3815                                 }
3816                             }
3817                         }
3818                     }
3819 #endif
3820                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3821                         /* Moves came from oldmoves or moves command
3822                            while we weren't doing anything else.
3823                            */
3824                         currentMove = forwardMostMove;
3825                         ClearHighlights();/*!!could figure this out*/
3826                         flipView = appData.flipView;
3827                         DrawPosition(TRUE, boards[currentMove]);
3828                         DisplayBothClocks();
3829                         snprintf(str, MSG_SIZ, "%s %s %s",
3830                                 gameInfo.white, _("vs."),  gameInfo.black);
3831                         DisplayTitle(str);
3832                         gameMode = IcsIdle;
3833                     } else {
3834                         /* Moves were history of an active game */
3835                         if (gameInfo.resultDetails != NULL) {
3836                             free(gameInfo.resultDetails);
3837                             gameInfo.resultDetails = NULL;
3838                         }
3839                     }
3840                     HistorySet(parseList, backwardMostMove,
3841                                forwardMostMove, currentMove-1);
3842                     DisplayMove(currentMove - 1);
3843                     if (started == STARTED_MOVES) next_out = i;
3844                     started = STARTED_NONE;
3845                     ics_getting_history = H_FALSE;
3846                     break;
3847
3848                   case STARTED_OBSERVE:
3849                     started = STARTED_NONE;
3850                     SendToICS(ics_prefix);
3851                     SendToICS("refresh\n");
3852                     break;
3853
3854                   default:
3855                     break;
3856                 }
3857                 if(bookHit) { // [HGM] book: simulate book reply
3858                     static char bookMove[MSG_SIZ]; // a bit generous?
3859
3860                     programStats.nodes = programStats.depth = programStats.time =
3861                     programStats.score = programStats.got_only_move = 0;
3862                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3863
3864                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3865                     strcat(bookMove, bookHit);
3866                     HandleMachineMove(bookMove, &first);
3867                 }
3868                 continue;
3869             }
3870
3871             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3872                  started == STARTED_HOLDINGS ||
3873                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3874                 /* Accumulate characters in move list or board */
3875                 parse[parse_pos++] = buf[i];
3876             }
3877
3878             /* Start of game messages.  Mostly we detect start of game
3879                when the first board image arrives.  On some versions
3880                of the ICS, though, we need to do a "refresh" after starting
3881                to observe in order to get the current board right away. */
3882             if (looking_at(buf, &i, "Adding game * to observation list")) {
3883                 started = STARTED_OBSERVE;
3884                 continue;
3885             }
3886
3887             /* Handle auto-observe */
3888             if (appData.autoObserve &&
3889                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3890                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3891                 char *player;
3892                 /* Choose the player that was highlighted, if any. */
3893                 if (star_match[0][0] == '\033' ||
3894                     star_match[1][0] != '\033') {
3895                     player = star_match[0];
3896                 } else {
3897                     player = star_match[2];
3898                 }
3899                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3900                         ics_prefix, StripHighlightAndTitle(player));
3901                 SendToICS(str);
3902
3903                 /* Save ratings from notify string */
3904                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3905                 player1Rating = string_to_rating(star_match[1]);
3906                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3907                 player2Rating = string_to_rating(star_match[3]);
3908
3909                 if (appData.debugMode)
3910                   fprintf(debugFP,
3911                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3912                           player1Name, player1Rating,
3913                           player2Name, player2Rating);
3914
3915                 continue;
3916             }
3917
3918             /* Deal with automatic examine mode after a game,
3919                and with IcsObserving -> IcsExamining transition */
3920             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3921                 looking_at(buf, &i, "has made you an examiner of game *")) {
3922
3923                 int gamenum = atoi(star_match[0]);
3924                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3925                     gamenum == ics_gamenum) {
3926                     /* We were already playing or observing this game;
3927                        no need to refetch history */
3928                     gameMode = IcsExamining;
3929                     if (pausing) {
3930                         pauseExamForwardMostMove = forwardMostMove;
3931                     } else if (currentMove < forwardMostMove) {
3932                         ForwardInner(forwardMostMove);
3933                     }
3934                 } else {
3935                     /* I don't think this case really can happen */
3936                     SendToICS(ics_prefix);
3937                     SendToICS("refresh\n");
3938                 }
3939                 continue;
3940             }
3941
3942             /* Error messages */
3943 //          if (ics_user_moved) {
3944             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3945                 if (looking_at(buf, &i, "Illegal move") ||
3946                     looking_at(buf, &i, "Not a legal move") ||
3947                     looking_at(buf, &i, "Your king is in check") ||
3948                     looking_at(buf, &i, "It isn't your turn") ||
3949                     looking_at(buf, &i, "It is not your move")) {
3950                     /* Illegal move */
3951                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3952                         currentMove = forwardMostMove-1;
3953                         DisplayMove(currentMove - 1); /* before DMError */
3954                         DrawPosition(FALSE, boards[currentMove]);
3955                         SwitchClocks(forwardMostMove-1); // [HGM] race
3956                         DisplayBothClocks();
3957                     }
3958                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3959                     ics_user_moved = 0;
3960                     continue;
3961                 }
3962             }
3963
3964             if (looking_at(buf, &i, "still have time") ||
3965                 looking_at(buf, &i, "not out of time") ||
3966                 looking_at(buf, &i, "either player is out of time") ||
3967                 looking_at(buf, &i, "has timeseal; checking")) {
3968                 /* We must have called his flag a little too soon */
3969                 whiteFlag = blackFlag = FALSE;
3970                 continue;
3971             }
3972
3973             if (looking_at(buf, &i, "added * seconds to") ||
3974                 looking_at(buf, &i, "seconds were added to")) {
3975                 /* Update the clocks */
3976                 SendToICS(ics_prefix);
3977                 SendToICS("refresh\n");
3978                 continue;
3979             }
3980
3981             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3982                 ics_clock_paused = TRUE;
3983                 StopClocks();
3984                 continue;
3985             }
3986
3987             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3988                 ics_clock_paused = FALSE;
3989                 StartClocks();
3990                 continue;
3991             }
3992
3993             /* Grab player ratings from the Creating: message.
3994                Note we have to check for the special case when
3995                the ICS inserts things like [white] or [black]. */
3996             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3997                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3998                 /* star_matches:
3999                    0    player 1 name (not necessarily white)
4000                    1    player 1 rating
4001                    2    empty, white, or black (IGNORED)
4002                    3    player 2 name (not necessarily black)
4003                    4    player 2 rating
4004
4005                    The names/ratings are sorted out when the game
4006                    actually starts (below).
4007                 */
4008                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4009                 player1Rating = string_to_rating(star_match[1]);
4010                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4011                 player2Rating = string_to_rating(star_match[4]);
4012
4013                 if (appData.debugMode)
4014                   fprintf(debugFP,
4015                           "Ratings from 'Creating:' %s %d, %s %d\n",
4016                           player1Name, player1Rating,
4017                           player2Name, player2Rating);
4018
4019                 continue;
4020             }
4021
4022             /* Improved generic start/end-of-game messages */
4023             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4024                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4025                 /* If tkind == 0: */
4026                 /* star_match[0] is the game number */
4027                 /*           [1] is the white player's name */
4028                 /*           [2] is the black player's name */
4029                 /* For end-of-game: */
4030                 /*           [3] is the reason for the game end */
4031                 /*           [4] is a PGN end game-token, preceded by " " */
4032                 /* For start-of-game: */
4033                 /*           [3] begins with "Creating" or "Continuing" */
4034                 /*           [4] is " *" or empty (don't care). */
4035                 int gamenum = atoi(star_match[0]);
4036                 char *whitename, *blackname, *why, *endtoken;
4037                 ChessMove endtype = EndOfFile;
4038
4039                 if (tkind == 0) {
4040                   whitename = star_match[1];
4041                   blackname = star_match[2];
4042                   why = star_match[3];
4043                   endtoken = star_match[4];
4044                 } else {
4045                   whitename = star_match[1];
4046                   blackname = star_match[3];
4047                   why = star_match[5];
4048                   endtoken = star_match[6];
4049                 }
4050
4051                 /* Game start messages */
4052                 if (strncmp(why, "Creating ", 9) == 0 ||
4053                     strncmp(why, "Continuing ", 11) == 0) {
4054                     gs_gamenum = gamenum;
4055                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4056                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4057                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4058 #if ZIPPY
4059                     if (appData.zippyPlay) {
4060                         ZippyGameStart(whitename, blackname);
4061                     }
4062 #endif /*ZIPPY*/
4063                     partnerBoardValid = FALSE; // [HGM] bughouse
4064                     continue;
4065                 }
4066
4067                 /* Game end messages */
4068                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4069                     ics_gamenum != gamenum) {
4070                     continue;
4071                 }
4072                 while (endtoken[0] == ' ') endtoken++;
4073                 switch (endtoken[0]) {
4074                   case '*':
4075                   default:
4076                     endtype = GameUnfinished;
4077                     break;
4078                   case '0':
4079                     endtype = BlackWins;
4080                     break;
4081                   case '1':
4082                     if (endtoken[1] == '/')
4083                       endtype = GameIsDrawn;
4084                     else
4085                       endtype = WhiteWins;
4086                     break;
4087                 }
4088                 GameEnds(endtype, why, GE_ICS);
4089 #if ZIPPY
4090                 if (appData.zippyPlay && first.initDone) {
4091                     ZippyGameEnd(endtype, why);
4092                     if (first.pr == NoProc) {
4093                       /* Start the next process early so that we'll
4094                          be ready for the next challenge */
4095                       StartChessProgram(&first);
4096                     }
4097                     /* Send "new" early, in case this command takes
4098                        a long time to finish, so that we'll be ready
4099                        for the next challenge. */
4100                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4101                     Reset(TRUE, TRUE);
4102                 }
4103 #endif /*ZIPPY*/
4104                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4105                 continue;
4106             }
4107
4108             if (looking_at(buf, &i, "Removing game * from observation") ||
4109                 looking_at(buf, &i, "no longer observing game *") ||
4110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4111                 if (gameMode == IcsObserving &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       /* icsEngineAnalyze */
4115                       if (appData.icsEngineAnalyze) {
4116                             ExitAnalyzeMode();
4117                             ModeHighlight();
4118                       }
4119                       StopClocks();
4120                       gameMode = IcsIdle;
4121                       ics_gamenum = -1;
4122                       ics_user_moved = FALSE;
4123                   }
4124                 continue;
4125             }
4126
4127             if (looking_at(buf, &i, "no longer examining game *")) {
4128                 if (gameMode == IcsExamining &&
4129                     atoi(star_match[0]) == ics_gamenum)
4130                   {
4131                       gameMode = IcsIdle;
4132                       ics_gamenum = -1;
4133                       ics_user_moved = FALSE;
4134                   }
4135                 continue;
4136             }
4137
4138             /* Advance leftover_start past any newlines we find,
4139                so only partial lines can get reparsed */
4140             if (looking_at(buf, &i, "\n")) {
4141                 prevColor = curColor;
4142                 if (curColor != ColorNormal) {
4143                     if (oldi > next_out) {
4144                         SendToPlayer(&buf[next_out], oldi - next_out);
4145                         next_out = oldi;
4146                     }
4147                     Colorize(ColorNormal, FALSE);
4148                     curColor = ColorNormal;
4149                 }
4150                 if (started == STARTED_BOARD) {
4151                     started = STARTED_NONE;
4152                     parse[parse_pos] = NULLCHAR;
4153                     ParseBoard12(parse);
4154                     ics_user_moved = 0;
4155
4156                     /* Send premove here */
4157                     if (appData.premove) {
4158                       char str[MSG_SIZ];
4159                       if (currentMove == 0 &&
4160                           gameMode == IcsPlayingWhite &&
4161                           appData.premoveWhite) {
4162                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4163                         if (appData.debugMode)
4164                           fprintf(debugFP, "Sending premove:\n");
4165                         SendToICS(str);
4166                       } else if (currentMove == 1 &&
4167                                  gameMode == IcsPlayingBlack &&
4168                                  appData.premoveBlack) {
4169                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4170                         if (appData.debugMode)
4171                           fprintf(debugFP, "Sending premove:\n");
4172                         SendToICS(str);
4173                       } else if (gotPremove) {
4174                         int oldFMM = forwardMostMove;
4175                         gotPremove = 0;
4176                         ClearPremoveHighlights();
4177                         if (appData.debugMode)
4178                           fprintf(debugFP, "Sending premove:\n");
4179                           UserMoveEvent(premoveFromX, premoveFromY,
4180                                         premoveToX, premoveToY,
4181                                         premovePromoChar);
4182                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4183                           if(moveList[oldFMM-1][1] != '@')
4184                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4185                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                           else // (drop)
4187                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4188                         }
4189                       }
4190                     }
4191
4192                     /* Usually suppress following prompt */
4193                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4194                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4195                         if (looking_at(buf, &i, "*% ")) {
4196                             savingComment = FALSE;
4197                             suppressKibitz = 0;
4198                         }
4199                     }
4200                     next_out = i;
4201                 } else if (started == STARTED_HOLDINGS) {
4202                     int gamenum;
4203                     char new_piece[MSG_SIZ];
4204                     started = STARTED_NONE;
4205                     parse[parse_pos] = NULLCHAR;
4206                     if (appData.debugMode)
4207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4208                                                         parse, currentMove);
4209                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4210                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4211                         if (gameInfo.variant == VariantNormal) {
4212                           /* [HGM] We seem to switch variant during a game!
4213                            * Presumably no holdings were displayed, so we have
4214                            * to move the position two files to the right to
4215                            * create room for them!
4216                            */
4217                           VariantClass newVariant;
4218                           switch(gameInfo.boardWidth) { // base guess on board width
4219                                 case 9:  newVariant = VariantShogi; break;
4220                                 case 10: newVariant = VariantGreat; break;
4221                                 default: newVariant = VariantCrazyhouse; break;
4222                           }
4223                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224                           /* Get a move list just to see the header, which
4225                              will tell us whether this is really bug or zh */
4226                           if (ics_getting_history == H_FALSE) {
4227                             ics_getting_history = H_REQUESTED;
4228                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229                             SendToICS(str);
4230                           }
4231                         }
4232                         new_piece[0] = NULLCHAR;
4233                         sscanf(parse, "game %d white [%s black [%s <- %s",
4234                                &gamenum, white_holding, black_holding,
4235                                new_piece);
4236                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4237                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4238                         /* [HGM] copy holdings to board holdings area */
4239                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4240                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4241                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4242 #if ZIPPY
4243                         if (appData.zippyPlay && first.initDone) {
4244                             ZippyHoldings(white_holding, black_holding,
4245                                           new_piece);
4246                         }
4247 #endif /*ZIPPY*/
4248                         if (tinyLayout || smallLayout) {
4249                             char wh[16], bh[16];
4250                             PackHolding(wh, white_holding);
4251                             PackHolding(bh, black_holding);
4252                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4253                                     gameInfo.white, gameInfo.black);
4254                         } else {
4255                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4256                                     gameInfo.white, white_holding, _("vs."),
4257                                     gameInfo.black, black_holding);
4258                         }
4259                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4260                         DrawPosition(FALSE, boards[currentMove]);
4261                         DisplayTitle(str);
4262                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4263                         sscanf(parse, "game %d white [%s black [%s <- %s",
4264                                &gamenum, white_holding, black_holding,
4265                                new_piece);
4266                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4267                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4268                         /* [HGM] copy holdings to partner-board holdings area */
4269                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4270                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4271                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4272                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4274                       }
4275                     }
4276                     /* Suppress following prompt */
4277                     if (looking_at(buf, &i, "*% ")) {
4278                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4279                         savingComment = FALSE;
4280                         suppressKibitz = 0;
4281                     }
4282                     next_out = i;
4283                 }
4284                 continue;
4285             }
4286
4287             i++;                /* skip unparsed character and loop back */
4288         }
4289
4290         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4291 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4292 //          SendToPlayer(&buf[next_out], i - next_out);
4293             started != STARTED_HOLDINGS && leftover_start > next_out) {
4294             SendToPlayer(&buf[next_out], leftover_start - next_out);
4295             next_out = i;
4296         }
4297
4298         leftover_len = buf_len - leftover_start;
4299         /* if buffer ends with something we couldn't parse,
4300            reparse it after appending the next read */
4301
4302     } else if (count == 0) {
4303         RemoveInputSource(isr);
4304         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4305     } else {
4306         DisplayFatalError(_("Error reading from ICS"), error, 1);
4307     }
4308 }
4309
4310
4311 /* Board style 12 looks like this:
4312
4313    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4314
4315  * The "<12> " is stripped before it gets to this routine.  The two
4316  * trailing 0's (flip state and clock ticking) are later addition, and
4317  * some chess servers may not have them, or may have only the first.
4318  * Additional trailing fields may be added in the future.
4319  */
4320
4321 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4322
4323 #define RELATION_OBSERVING_PLAYED    0
4324 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4325 #define RELATION_PLAYING_MYMOVE      1
4326 #define RELATION_PLAYING_NOTMYMOVE  -1
4327 #define RELATION_EXAMINING           2
4328 #define RELATION_ISOLATED_BOARD     -3
4329 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4330
4331 void
4332 ParseBoard12 (char *string)
4333 {
4334 #if ZIPPY
4335     int i, takeback;
4336     char *bookHit = NULL; // [HGM] book
4337 #endif
4338     GameMode newGameMode;
4339     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4340     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4341     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4342     char to_play, board_chars[200];
4343     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4344     char black[32], white[32];
4345     Board board;
4346     int prevMove = currentMove;
4347     int ticking = 2;
4348     ChessMove moveType;
4349     int fromX, fromY, toX, toY;
4350     char promoChar;
4351     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4352     Boolean weird = FALSE, reqFlag = FALSE;
4353
4354     fromX = fromY = toX = toY = -1;
4355
4356     newGame = FALSE;
4357
4358     if (appData.debugMode)
4359       fprintf(debugFP, "Parsing board: %s\n", string);
4360
4361     move_str[0] = NULLCHAR;
4362     elapsed_time[0] = NULLCHAR;
4363     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4364         int  i = 0, j;
4365         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4366             if(string[i] == ' ') { ranks++; files = 0; }
4367             else files++;
4368             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4369             i++;
4370         }
4371         for(j = 0; j <i; j++) board_chars[j] = string[j];
4372         board_chars[i] = '\0';
4373         string += i + 1;
4374     }
4375     n = sscanf(string, PATTERN, &to_play, &double_push,
4376                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4377                &gamenum, white, black, &relation, &basetime, &increment,
4378                &white_stren, &black_stren, &white_time, &black_time,
4379                &moveNum, str, elapsed_time, move_str, &ics_flip,
4380                &ticking);
4381
4382     if (n < 21) {
4383         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4384         DisplayError(str, 0);
4385         return;
4386     }
4387
4388     /* Convert the move number to internal form */
4389     moveNum = (moveNum - 1) * 2;
4390     if (to_play == 'B') moveNum++;
4391     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4392       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4393                         0, 1);
4394       return;
4395     }
4396
4397     switch (relation) {
4398       case RELATION_OBSERVING_PLAYED:
4399       case RELATION_OBSERVING_STATIC:
4400         if (gamenum == -1) {
4401             /* Old ICC buglet */
4402             relation = RELATION_OBSERVING_STATIC;
4403         }
4404         newGameMode = IcsObserving;
4405         break;
4406       case RELATION_PLAYING_MYMOVE:
4407       case RELATION_PLAYING_NOTMYMOVE:
4408         newGameMode =
4409           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4410             IcsPlayingWhite : IcsPlayingBlack;
4411         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4412         break;
4413       case RELATION_EXAMINING:
4414         newGameMode = IcsExamining;
4415         break;
4416       case RELATION_ISOLATED_BOARD:
4417       default:
4418         /* Just display this board.  If user was doing something else,
4419            we will forget about it until the next board comes. */
4420         newGameMode = IcsIdle;
4421         break;
4422       case RELATION_STARTING_POSITION:
4423         newGameMode = gameMode;
4424         break;
4425     }
4426
4427     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4428         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4429          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4430       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4431       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4432       static int lastBgGame = -1;
4433       char *toSqr;
4434       for (k = 0; k < ranks; k++) {
4435         for (j = 0; j < files; j++)
4436           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437         if(gameInfo.holdingsWidth > 1) {
4438              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440         }
4441       }
4442       CopyBoard(partnerBoard, board);
4443       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4444         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4445         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4446       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4447       if(toSqr = strchr(str, '-')) {
4448         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4449         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4451       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4452       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4453       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4454       if(twoBoards) {
4455           DisplayWhiteClock(white_time*fac, to_play == 'W');
4456           DisplayBlackClock(black_time*fac, to_play != 'W');
4457           activePartner = to_play;
4458           if(gamenum != lastBgGame) {
4459               char buf[MSG_SIZ];
4460               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4461               DisplayTitle(buf);
4462           }
4463           lastBgGame = gamenum;
4464           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4465                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4466       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4467                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4468       if(!twoBoards) DisplayMessage(partnerStatus, "");
4469         partnerBoardValid = TRUE;
4470       return;
4471     }
4472
4473     if(appData.dualBoard && appData.bgObserve) {
4474         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4475             SendToICS(ics_prefix), SendToICS("pobserve\n");
4476         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4477             char buf[MSG_SIZ];
4478             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4479             SendToICS(buf);
4480         }
4481     }
4482
4483     /* Modify behavior for initial board display on move listing
4484        of wild games.
4485        */
4486     switch (ics_getting_history) {
4487       case H_FALSE:
4488       case H_REQUESTED:
4489         break;
4490       case H_GOT_REQ_HEADER:
4491       case H_GOT_UNREQ_HEADER:
4492         /* This is the initial position of the current game */
4493         gamenum = ics_gamenum;
4494         moveNum = 0;            /* old ICS bug workaround */
4495         if (to_play == 'B') {
4496           startedFromSetupPosition = TRUE;
4497           blackPlaysFirst = TRUE;
4498           moveNum = 1;
4499           if (forwardMostMove == 0) forwardMostMove = 1;
4500           if (backwardMostMove == 0) backwardMostMove = 1;
4501           if (currentMove == 0) currentMove = 1;
4502         }
4503         newGameMode = gameMode;
4504         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4505         break;
4506       case H_GOT_UNWANTED_HEADER:
4507         /* This is an initial board that we don't want */
4508         return;
4509       case H_GETTING_MOVES:
4510         /* Should not happen */
4511         DisplayError(_("Error gathering move list: extra board"), 0);
4512         ics_getting_history = H_FALSE;
4513         return;
4514     }
4515
4516    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4517                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4518                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4519      /* [HGM] We seem to have switched variant unexpectedly
4520       * Try to guess new variant from board size
4521       */
4522           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4523           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4524           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4525           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4526           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4527           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4528           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4529           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4530           /* Get a move list just to see the header, which
4531              will tell us whether this is really bug or zh */
4532           if (ics_getting_history == H_FALSE) {
4533             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4534             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4535             SendToICS(str);
4536           }
4537     }
4538
4539     /* Take action if this is the first board of a new game, or of a
4540        different game than is currently being displayed.  */
4541     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4542         relation == RELATION_ISOLATED_BOARD) {
4543
4544         /* Forget the old game and get the history (if any) of the new one */
4545         if (gameMode != BeginningOfGame) {
4546           Reset(TRUE, TRUE);
4547         }
4548         newGame = TRUE;
4549         if (appData.autoRaiseBoard) BoardToTop();
4550         prevMove = -3;
4551         if (gamenum == -1) {
4552             newGameMode = IcsIdle;
4553         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4554                    appData.getMoveList && !reqFlag) {
4555             /* Need to get game history */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560
4561         /* Initially flip the board to have black on the bottom if playing
4562            black or if the ICS flip flag is set, but let the user change
4563            it with the Flip View button. */
4564         flipView = appData.autoFlipView ?
4565           (newGameMode == IcsPlayingBlack) || ics_flip :
4566           appData.flipView;
4567
4568         /* Done with values from previous mode; copy in new ones */
4569         gameMode = newGameMode;
4570         ModeHighlight();
4571         ics_gamenum = gamenum;
4572         if (gamenum == gs_gamenum) {
4573             int klen = strlen(gs_kind);
4574             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4575             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4576             gameInfo.event = StrSave(str);
4577         } else {
4578             gameInfo.event = StrSave("ICS game");
4579         }
4580         gameInfo.site = StrSave(appData.icsHost);
4581         gameInfo.date = PGNDate();
4582         gameInfo.round = StrSave("-");
4583         gameInfo.white = StrSave(white);
4584         gameInfo.black = StrSave(black);
4585         timeControl = basetime * 60 * 1000;
4586         timeControl_2 = 0;
4587         timeIncrement = increment * 1000;
4588         movesPerSession = 0;
4589         gameInfo.timeControl = TimeControlTagValue();
4590         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4591   if (appData.debugMode) {
4592     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4593     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4594     setbuf(debugFP, NULL);
4595   }
4596
4597         gameInfo.outOfBook = NULL;
4598
4599         /* Do we have the ratings? */
4600         if (strcmp(player1Name, white) == 0 &&
4601             strcmp(player2Name, black) == 0) {
4602             if (appData.debugMode)
4603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4604                       player1Rating, player2Rating);
4605             gameInfo.whiteRating = player1Rating;
4606             gameInfo.blackRating = player2Rating;
4607         } else if (strcmp(player2Name, white) == 0 &&
4608                    strcmp(player1Name, black) == 0) {
4609             if (appData.debugMode)
4610               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4611                       player2Rating, player1Rating);
4612             gameInfo.whiteRating = player2Rating;
4613             gameInfo.blackRating = player1Rating;
4614         }
4615         player1Name[0] = player2Name[0] = NULLCHAR;
4616
4617         /* Silence shouts if requested */
4618         if (appData.quietPlay &&
4619             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4620             SendToICS(ics_prefix);
4621             SendToICS("set shout 0\n");
4622         }
4623     }
4624
4625     /* Deal with midgame name changes */
4626     if (!newGame) {
4627         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4628             if (gameInfo.white) free(gameInfo.white);
4629             gameInfo.white = StrSave(white);
4630         }
4631         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4632             if (gameInfo.black) free(gameInfo.black);
4633             gameInfo.black = StrSave(black);
4634         }
4635     }
4636
4637     /* Throw away game result if anything actually changes in examine mode */
4638     if (gameMode == IcsExamining && !newGame) {
4639         gameInfo.result = GameUnfinished;
4640         if (gameInfo.resultDetails != NULL) {
4641             free(gameInfo.resultDetails);
4642             gameInfo.resultDetails = NULL;
4643         }
4644     }
4645
4646     /* In pausing && IcsExamining mode, we ignore boards coming
4647        in if they are in a different variation than we are. */
4648     if (pauseExamInvalid) return;
4649     if (pausing && gameMode == IcsExamining) {
4650         if (moveNum <= pauseExamForwardMostMove) {
4651             pauseExamInvalid = TRUE;
4652             forwardMostMove = pauseExamForwardMostMove;
4653             return;
4654         }
4655     }
4656
4657   if (appData.debugMode) {
4658     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4659   }
4660     /* Parse the board */
4661     for (k = 0; k < ranks; k++) {
4662       for (j = 0; j < files; j++)
4663         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4664       if(gameInfo.holdingsWidth > 1) {
4665            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4666            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4667       }
4668     }
4669     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4670       board[5][BOARD_RGHT+1] = WhiteAngel;
4671       board[6][BOARD_RGHT+1] = WhiteMarshall;
4672       board[1][0] = BlackMarshall;
4673       board[2][0] = BlackAngel;
4674       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4675     }
4676     CopyBoard(boards[moveNum], board);
4677     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4678     if (moveNum == 0) {
4679         startedFromSetupPosition =
4680           !CompareBoards(board, initialPosition);
4681         if(startedFromSetupPosition)
4682             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4683     }
4684
4685     /* [HGM] Set castling rights. Take the outermost Rooks,
4686        to make it also work for FRC opening positions. Note that board12
4687        is really defective for later FRC positions, as it has no way to
4688        indicate which Rook can castle if they are on the same side of King.
4689        For the initial position we grant rights to the outermost Rooks,
4690        and remember thos rights, and we then copy them on positions
4691        later in an FRC game. This means WB might not recognize castlings with
4692        Rooks that have moved back to their original position as illegal,
4693        but in ICS mode that is not its job anyway.
4694     */
4695     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4696     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4697
4698         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4699             if(board[0][i] == WhiteRook) j = i;
4700         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4702             if(board[0][i] == WhiteRook) j = i;
4703         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710
4711         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4712         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4715         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4716             if(board[BOARD_HEIGHT-1][k] == bKing)
4717                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4718         if(gameInfo.variant == VariantTwoKings) {
4719             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4720             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4721             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4722         }
4723     } else { int r;
4724         r = boards[moveNum][CASTLING][0] = initialRights[0];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4726         r = boards[moveNum][CASTLING][1] = initialRights[1];
4727         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4728         r = boards[moveNum][CASTLING][3] = initialRights[3];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4730         r = boards[moveNum][CASTLING][4] = initialRights[4];
4731         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4732         /* wildcastle kludge: always assume King has rights */
4733         r = boards[moveNum][CASTLING][2] = initialRights[2];
4734         r = boards[moveNum][CASTLING][5] = initialRights[5];
4735     }
4736     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4737     boards[moveNum][EP_STATUS] = EP_NONE;
4738     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4739     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4740     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4741
4742
4743     if (ics_getting_history == H_GOT_REQ_HEADER ||
4744         ics_getting_history == H_GOT_UNREQ_HEADER) {
4745         /* This was an initial position from a move list, not
4746            the current position */
4747         return;
4748     }
4749
4750     /* Update currentMove and known move number limits */
4751     newMove = newGame || moveNum > forwardMostMove;
4752
4753     if (newGame) {
4754         forwardMostMove = backwardMostMove = currentMove = moveNum;
4755         if (gameMode == IcsExamining && moveNum == 0) {
4756           /* Workaround for ICS limitation: we are not told the wild
4757              type when starting to examine a game.  But if we ask for
4758              the move list, the move list header will tell us */
4759             ics_getting_history = H_REQUESTED;
4760             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4761             SendToICS(str);
4762         }
4763     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4764                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4765 #if ZIPPY
4766         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4767         /* [HGM] applied this also to an engine that is silently watching        */
4768         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4769             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4770             gameInfo.variant == currentlyInitializedVariant) {
4771           takeback = forwardMostMove - moveNum;
4772           for (i = 0; i < takeback; i++) {
4773             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4774             SendToProgram("undo\n", &first);
4775           }
4776         }
4777 #endif
4778
4779         forwardMostMove = moveNum;
4780         if (!pausing || currentMove > forwardMostMove)
4781           currentMove = forwardMostMove;
4782     } else {
4783         /* New part of history that is not contiguous with old part */
4784         if (pausing && gameMode == IcsExamining) {
4785             pauseExamInvalid = TRUE;
4786             forwardMostMove = pauseExamForwardMostMove;
4787             return;
4788         }
4789         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4790 #if ZIPPY
4791             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4792                 // [HGM] when we will receive the move list we now request, it will be
4793                 // fed to the engine from the first move on. So if the engine is not
4794                 // in the initial position now, bring it there.
4795                 InitChessProgram(&first, 0);
4796             }
4797 #endif
4798             ics_getting_history = H_REQUESTED;
4799             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4800             SendToICS(str);
4801         }
4802         forwardMostMove = backwardMostMove = currentMove = moveNum;
4803     }
4804
4805     /* Update the clocks */
4806     if (strchr(elapsed_time, '.')) {
4807       /* Time is in ms */
4808       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4809       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4810     } else {
4811       /* Time is in seconds */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4814     }
4815
4816
4817 #if ZIPPY
4818     if (appData.zippyPlay && newGame &&
4819         gameMode != IcsObserving && gameMode != IcsIdle &&
4820         gameMode != IcsExamining)
4821       ZippyFirstBoard(moveNum, basetime, increment);
4822 #endif
4823
4824     /* Put the move on the move list, first converting
4825        to canonical algebraic form. */
4826     if (moveNum > 0) {
4827   if (appData.debugMode) {
4828     int f = forwardMostMove;
4829     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4830             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4831             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4832     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4833     fprintf(debugFP, "moveNum = %d\n", moveNum);
4834     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4835     setbuf(debugFP, NULL);
4836   }
4837         if (moveNum <= backwardMostMove) {
4838             /* We don't know what the board looked like before
4839                this move.  Punt. */
4840           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4841             strcat(parseList[moveNum - 1], " ");
4842             strcat(parseList[moveNum - 1], elapsed_time);
4843             moveList[moveNum - 1][0] = NULLCHAR;
4844         } else if (strcmp(move_str, "none") == 0) {
4845             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4846             /* Again, we don't know what the board looked like;
4847                this is really the start of the game. */
4848             parseList[moveNum - 1][0] = NULLCHAR;
4849             moveList[moveNum - 1][0] = NULLCHAR;
4850             backwardMostMove = moveNum;
4851             startedFromSetupPosition = TRUE;
4852             fromX = fromY = toX = toY = -1;
4853         } else {
4854           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4855           //                 So we parse the long-algebraic move string in stead of the SAN move
4856           int valid; char buf[MSG_SIZ], *prom;
4857
4858           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4859                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4860           // str looks something like "Q/a1-a2"; kill the slash
4861           if(str[1] == '/')
4862             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4863           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4864           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4865                 strcat(buf, prom); // long move lacks promo specification!
4866           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4867                 if(appData.debugMode)
4868                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4869                 safeStrCpy(move_str, buf, MSG_SIZ);
4870           }
4871           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar)
4873                || ParseOneMove(buf, moveNum - 1, &moveType,
4874                                 &fromX, &fromY, &toX, &toY, &promoChar);
4875           // end of long SAN patch
4876           if (valid) {
4877             (void) CoordsToAlgebraic(boards[moveNum - 1],
4878                                      PosFlags(moveNum - 1),
4879                                      fromY, fromX, toY, toX, promoChar,
4880                                      parseList[moveNum-1]);
4881             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4882               case MT_NONE:
4883               case MT_STALEMATE:
4884               default:
4885                 break;
4886               case MT_CHECK:
4887                 if(!IS_SHOGI(gameInfo.variant))
4888                     strcat(parseList[moveNum - 1], "+");
4889                 break;
4890               case MT_CHECKMATE:
4891               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4892                 strcat(parseList[moveNum - 1], "#");
4893                 break;
4894             }
4895             strcat(parseList[moveNum - 1], " ");
4896             strcat(parseList[moveNum - 1], elapsed_time);
4897             /* currentMoveString is set as a side-effect of ParseOneMove */
4898             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4899             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4900             strcat(moveList[moveNum - 1], "\n");
4901
4902             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4903                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4904               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4905                 ChessSquare old, new = boards[moveNum][k][j];
4906                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4907                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4908                   if(old == new) continue;
4909                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4910                   else if(new == WhiteWazir || new == BlackWazir) {
4911                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4912                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4913                       else boards[moveNum][k][j] = old; // preserve type of Gold
4914                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4915                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4916               }
4917           } else {
4918             /* Move from ICS was illegal!?  Punt. */
4919             if (appData.debugMode) {
4920               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4921               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4922             }
4923             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4924             strcat(parseList[moveNum - 1], " ");
4925             strcat(parseList[moveNum - 1], elapsed_time);
4926             moveList[moveNum - 1][0] = NULLCHAR;
4927             fromX = fromY = toX = toY = -1;
4928           }
4929         }
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4932     setbuf(debugFP, NULL);
4933   }
4934
4935 #if ZIPPY
4936         /* Send move to chess program (BEFORE animating it). */
4937         if (appData.zippyPlay && !newGame && newMove &&
4938            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4939
4940             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4941                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4942                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4943                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4944                             move_str);
4945                     DisplayError(str, 0);
4946                 } else {
4947                     if (first.sendTime) {
4948                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4949                     }
4950                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4951                     if (firstMove && !bookHit) {
4952                         firstMove = FALSE;
4953                         if (first.useColors) {
4954                           SendToProgram(gameMode == IcsPlayingWhite ?
4955                                         "white\ngo\n" :
4956                                         "black\ngo\n", &first);
4957                         } else {
4958                           SendToProgram("go\n", &first);
4959                         }
4960                         first.maybeThinking = TRUE;
4961                     }
4962                 }
4963             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4964               if (moveList[moveNum - 1][0] == NULLCHAR) {
4965                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4966                 DisplayError(str, 0);
4967               } else {
4968                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4969                 SendMoveToProgram(moveNum - 1, &first);
4970               }
4971             }
4972         }
4973 #endif
4974     }
4975
4976     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4977         /* If move comes from a remote source, animate it.  If it
4978            isn't remote, it will have already been animated. */
4979         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4980             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4981         }
4982         if (!pausing && appData.highlightLastMove) {
4983             SetHighlights(fromX, fromY, toX, toY);
4984         }
4985     }
4986
4987     /* Start the clocks */
4988     whiteFlag = blackFlag = FALSE;
4989     appData.clockMode = !(basetime == 0 && increment == 0);
4990     if (ticking == 0) {
4991       ics_clock_paused = TRUE;
4992       StopClocks();
4993     } else if (ticking == 1) {
4994       ics_clock_paused = FALSE;
4995     }
4996     if (gameMode == IcsIdle ||
4997         relation == RELATION_OBSERVING_STATIC ||
4998         relation == RELATION_EXAMINING ||
4999         ics_clock_paused)
5000       DisplayBothClocks();
5001     else
5002       StartClocks();
5003
5004     /* Display opponents and material strengths */
5005     if (gameInfo.variant != VariantBughouse &&
5006         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5007         if (tinyLayout || smallLayout) {
5008             if(gameInfo.variant == VariantNormal)
5009               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5011                     basetime, increment);
5012             else
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment, (int) gameInfo.variant);
5016         } else {
5017             if(gameInfo.variant == VariantNormal)
5018               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5019                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5020                     basetime, increment);
5021             else
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment, VariantName(gameInfo.variant));
5025         }
5026         DisplayTitle(str);
5027   if (appData.debugMode) {
5028     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5029   }
5030     }
5031
5032
5033     /* Display the board */
5034     if (!pausing && !appData.noGUI) {
5035
5036       if (appData.premove)
5037           if (!gotPremove ||
5038              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5039              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5040               ClearPremoveHighlights();
5041
5042       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5043         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5044       DrawPosition(j, boards[currentMove]);
5045
5046       DisplayMove(moveNum - 1);
5047       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5048             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5049               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5050         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5051       }
5052     }
5053
5054     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5055 #if ZIPPY
5056     if(bookHit) { // [HGM] book: simulate book reply
5057         static char bookMove[MSG_SIZ]; // a bit generous?
5058
5059         programStats.nodes = programStats.depth = programStats.time =
5060         programStats.score = programStats.got_only_move = 0;
5061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5062
5063         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5064         strcat(bookMove, bookHit);
5065         HandleMachineMove(bookMove, &first);
5066     }
5067 #endif
5068 }
5069
5070 void
5071 GetMoveListEvent ()
5072 {
5073     char buf[MSG_SIZ];
5074     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5075         ics_getting_history = H_REQUESTED;
5076         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5077         SendToICS(buf);
5078     }
5079 }
5080
5081 void
5082 SendToBoth (char *msg)
5083 {   // to make it easy to keep two engines in step in dual analysis
5084     SendToProgram(msg, &first);
5085     if(second.analyzing) SendToProgram(msg, &second);
5086 }
5087
5088 void
5089 AnalysisPeriodicEvent (int force)
5090 {
5091     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5092          && !force) || !appData.periodicUpdates)
5093       return;
5094
5095     /* Send . command to Crafty to collect stats */
5096     SendToBoth(".\n");
5097
5098     /* Don't send another until we get a response (this makes
5099        us stop sending to old Crafty's which don't understand
5100        the "." command (sending illegal cmds resets node count & time,
5101        which looks bad)) */
5102     programStats.ok_to_send = 0;
5103 }
5104
5105 void
5106 ics_update_width (int new_width)
5107 {
5108         ics_printf("set width %d\n", new_width);
5109 }
5110
5111 void
5112 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5117         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5118             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5119             SendToProgram(buf, cps);
5120             return;
5121         }
5122         // null move in variant where engine does not understand it (for analysis purposes)
5123         SendBoard(cps, moveNum + 1); // send position after move in stead.
5124         return;
5125     }
5126     if (cps->useUsermove) {
5127       SendToProgram("usermove ", cps);
5128     }
5129     if (cps->useSAN) {
5130       char *space;
5131       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5132         int len = space - parseList[moveNum];
5133         memcpy(buf, parseList[moveNum], len);
5134         buf[len++] = '\n';
5135         buf[len] = NULLCHAR;
5136       } else {
5137         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5138       }
5139       SendToProgram(buf, cps);
5140     } else {
5141       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5142         AlphaRank(moveList[moveNum], 4);
5143         SendToProgram(moveList[moveNum], cps);
5144         AlphaRank(moveList[moveNum], 4); // and back
5145       } else
5146       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5147        * the engine. It would be nice to have a better way to identify castle
5148        * moves here. */
5149       if(appData.fischerCastling && cps->useOOCastle) {
5150         int fromX = moveList[moveNum][0] - AAA;
5151         int fromY = moveList[moveNum][1] - ONE;
5152         int toX = moveList[moveNum][2] - AAA;
5153         int toY = moveList[moveNum][3] - ONE;
5154         if((boards[moveNum][fromY][fromX] == WhiteKing
5155             && boards[moveNum][toY][toX] == WhiteRook)
5156            || (boards[moveNum][fromY][fromX] == BlackKing
5157                && boards[moveNum][toY][toX] == BlackRook)) {
5158           if(toX > fromX) SendToProgram("O-O\n", cps);
5159           else SendToProgram("O-O-O\n", cps);
5160         }
5161         else SendToProgram(moveList[moveNum], cps);
5162       } else
5163       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5164         char *m = moveList[moveNum];
5165         static char c[2];
5166         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5167         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5168           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5169                                                m[2], m[3] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5172         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5173           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5174           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5175                                                m[7], m[8] - '0',
5176                                                m[7], m[8] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[5], m[6] - '0',
5179                                                m[2], m[3] - '0', c);
5180         } else
5181           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5182                                                m[5], m[6] - '0',
5183                                                m[5], m[6] - '0',
5184                                                m[2], m[3] - '0', c);
5185           SendToProgram(buf, cps);
5186       } else
5187       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5188         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5189           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5190           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5191                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192         } else
5193           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5194                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5195         SendToProgram(buf, cps);
5196       }
5197       else SendToProgram(moveList[moveNum], cps);
5198       /* End of additions by Tord */
5199     }
5200
5201     /* [HGM] setting up the opening has brought engine in force mode! */
5202     /*       Send 'go' if we are in a mode where machine should play. */
5203     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5204         (gameMode == TwoMachinesPlay   ||
5205 #if ZIPPY
5206          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5207 #endif
5208          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5209         SendToProgram("go\n", cps);
5210   if (appData.debugMode) {
5211     fprintf(debugFP, "(extra)\n");
5212   }
5213     }
5214     setboardSpoiledMachineBlack = 0;
5215 }
5216
5217 void
5218 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5219 {
5220     char user_move[MSG_SIZ];
5221     char suffix[4];
5222
5223     if(gameInfo.variant == VariantSChess && promoChar) {
5224         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5225         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5226     } else suffix[0] = NULLCHAR;
5227
5228     switch (moveType) {
5229       default:
5230         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5231                 (int)moveType, fromX, fromY, toX, toY);
5232         DisplayError(user_move + strlen("say "), 0);
5233         break;
5234       case WhiteKingSideCastle:
5235       case BlackKingSideCastle:
5236       case WhiteQueenSideCastleWild:
5237       case BlackQueenSideCastleWild:
5238       /* PUSH Fabien */
5239       case WhiteHSideCastleFR:
5240       case BlackHSideCastleFR:
5241       /* POP Fabien */
5242         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5243         break;
5244       case WhiteQueenSideCastle:
5245       case BlackQueenSideCastle:
5246       case WhiteKingSideCastleWild:
5247       case BlackKingSideCastleWild:
5248       /* PUSH Fabien */
5249       case WhiteASideCastleFR:
5250       case BlackASideCastleFR:
5251       /* POP Fabien */
5252         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5253         break;
5254       case WhiteNonPromotion:
5255       case BlackNonPromotion:
5256         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5257         break;
5258       case WhitePromotion:
5259       case BlackPromotion:
5260         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5261            gameInfo.variant == VariantMakruk)
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 PieceToChar(WhiteFerz));
5265         else if(gameInfo.variant == VariantGreat)
5266           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteMan));
5269         else
5270           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 promoChar);
5273         break;
5274       case WhiteDrop:
5275       case BlackDrop:
5276       drop:
5277         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5278                  ToUpper(PieceToChar((ChessSquare) fromX)),
5279                  AAA + toX, ONE + toY);
5280         break;
5281       case IllegalMove:  /* could be a variant we don't quite understand */
5282         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5283       case NormalMove:
5284       case WhiteCapturesEnPassant:
5285       case BlackCapturesEnPassant:
5286         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5287                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5288         break;
5289     }
5290     SendToICS(user_move);
5291     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5292         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5293 }
5294
5295 void
5296 UploadGameEvent ()
5297 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5298     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5299     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5300     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5301       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5302       return;
5303     }
5304     if(gameMode != IcsExamining) { // is this ever not the case?
5305         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5306
5307         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5308           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5309         } else { // on FICS we must first go to general examine mode
5310           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5311         }
5312         if(gameInfo.variant != VariantNormal) {
5313             // try figure out wild number, as xboard names are not always valid on ICS
5314             for(i=1; i<=36; i++) {
5315               snprintf(buf, MSG_SIZ, "wild/%d", i);
5316                 if(StringToVariant(buf) == gameInfo.variant) break;
5317             }
5318             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5319             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5320             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5321         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5322         SendToICS(ics_prefix);
5323         SendToICS(buf);
5324         if(startedFromSetupPosition || backwardMostMove != 0) {
5325           fen = PositionToFEN(backwardMostMove, NULL, 1);
5326           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5327             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5328             SendToICS(buf);
5329           } else { // FICS: everything has to set by separate bsetup commands
5330             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5331             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5332             SendToICS(buf);
5333             if(!WhiteOnMove(backwardMostMove)) {
5334                 SendToICS("bsetup tomove black\n");
5335             }
5336             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5337             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5338             SendToICS(buf);
5339             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5340             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5341             SendToICS(buf);
5342             i = boards[backwardMostMove][EP_STATUS];
5343             if(i >= 0) { // set e.p.
5344               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5345                 SendToICS(buf);
5346             }
5347             bsetup++;
5348           }
5349         }
5350       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5351             SendToICS("bsetup done\n"); // switch to normal examining.
5352     }
5353     for(i = backwardMostMove; i<last; i++) {
5354         char buf[20];
5355         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5356         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5357             int len = strlen(moveList[i]);
5358             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5359             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5360         }
5361         SendToICS(buf);
5362     }
5363     SendToICS(ics_prefix);
5364     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5365 }
5366
5367 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5368 int legNr = 1;
5369
5370 void
5371 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5372 {
5373     if (rf == DROP_RANK) {
5374       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5375       sprintf(move, "%c@%c%c\n",
5376                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5377     } else {
5378         if (promoChar == 'x' || promoChar == NULLCHAR) {
5379           sprintf(move, "%c%c%c%c\n",
5380                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5381           if(killX >= 0 && killY >= 0) {
5382             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5383             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5384           }
5385         } else {
5386             sprintf(move, "%c%c%c%c%c\n",
5387                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5388           if(killX >= 0 && killY >= 0) {
5389             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5390             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5391           }
5392         }
5393     }
5394 }
5395
5396 void
5397 ProcessICSInitScript (FILE *f)
5398 {
5399     char buf[MSG_SIZ];
5400
5401     while (fgets(buf, MSG_SIZ, f)) {
5402         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5403     }
5404
5405     fclose(f);
5406 }
5407
5408
5409 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5410 int dragging;
5411 static ClickType lastClickType;
5412
5413 int
5414 PieceInString (char *s, ChessSquare piece)
5415 {
5416   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5417   while((p = strchr(s, ID))) {
5418     if(!suffix || p[1] == suffix) return TRUE;
5419     s = p;
5420   }
5421   return FALSE;
5422 }
5423
5424 int
5425 Partner (ChessSquare *p)
5426 { // change piece into promotion partner if one shogi-promotes to the other
5427   ChessSquare partner = promoPartner[*p];
5428   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5429   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5430   *p = partner;
5431   return 1;
5432 }
5433
5434 void
5435 Sweep (int step)
5436 {
5437     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5438     static int toggleFlag;
5439     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5440     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5441     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5442     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5443     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5444     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5445     do {
5446         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5447         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5448         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5449         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5450         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5451         if(!step) step = -1;
5452     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5453             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5454             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5455             promoSweep == pawn ||
5456             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5457             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5458     if(toX >= 0) {
5459         int victim = boards[currentMove][toY][toX];
5460         boards[currentMove][toY][toX] = promoSweep;
5461         DrawPosition(FALSE, boards[currentMove]);
5462         boards[currentMove][toY][toX] = victim;
5463     } else
5464     ChangeDragPiece(promoSweep);
5465 }
5466
5467 int
5468 PromoScroll (int x, int y)
5469 {
5470   int step = 0;
5471
5472   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5473   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5474   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5475   if(!step) return FALSE;
5476   lastX = x; lastY = y;
5477   if((promoSweep < BlackPawn) == flipView) step = -step;
5478   if(step > 0) selectFlag = 1;
5479   if(!selectFlag) Sweep(step);
5480   return FALSE;
5481 }
5482
5483 void
5484 NextPiece (int step)
5485 {
5486     ChessSquare piece = boards[currentMove][toY][toX];
5487     do {
5488         pieceSweep -= step;
5489         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5490         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5491         if(!step) step = -1;
5492     } while(PieceToChar(pieceSweep) == '.');
5493     boards[currentMove][toY][toX] = pieceSweep;
5494     DrawPosition(FALSE, boards[currentMove]);
5495     boards[currentMove][toY][toX] = piece;
5496 }
5497 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5498 void
5499 AlphaRank (char *move, int n)
5500 {
5501 //    char *p = move, c; int x, y;
5502
5503     if (appData.debugMode) {
5504         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5505     }
5506
5507     if(move[1]=='*' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         move[1] = '@';
5511         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5512         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5513     } else
5514     if(move[0]>='0' && move[0]<='9' &&
5515        move[1]>='a' && move[1]<='x' &&
5516        move[2]>='0' && move[2]<='9' &&
5517        move[3]>='a' && move[3]<='x'    ) {
5518         /* input move, Shogi -> normal */
5519         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5520         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5521         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5523     } else
5524     if(move[1]=='@' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527         move[1] = '*';
5528         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5529         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5530     } else
5531     if(
5532        move[0]>='a' && move[0]<='x' &&
5533        move[3]>='0' && move[3]<='9' &&
5534        move[2]>='a' && move[2]<='x'    ) {
5535          /* output move, normal -> Shogi */
5536         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5537         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5538         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5540         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5541     }
5542     if (appData.debugMode) {
5543         fprintf(debugFP, "   out = '%s'\n", move);
5544     }
5545 }
5546
5547 char yy_textstr[8000];
5548
5549 /* Parser for moves from gnuchess, ICS, or user typein box */
5550 Boolean
5551 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5552 {
5553     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5554
5555     switch (*moveType) {
5556       case WhitePromotion:
5557       case BlackPromotion:
5558       case WhiteNonPromotion:
5559       case BlackNonPromotion:
5560       case NormalMove:
5561       case FirstLeg:
5562       case WhiteCapturesEnPassant:
5563       case BlackCapturesEnPassant:
5564       case WhiteKingSideCastle:
5565       case WhiteQueenSideCastle:
5566       case BlackKingSideCastle:
5567       case BlackQueenSideCastle:
5568       case WhiteKingSideCastleWild:
5569       case WhiteQueenSideCastleWild:
5570       case BlackKingSideCastleWild:
5571       case BlackQueenSideCastleWild:
5572       /* Code added by Tord: */
5573       case WhiteHSideCastleFR:
5574       case WhiteASideCastleFR:
5575       case BlackHSideCastleFR:
5576       case BlackASideCastleFR:
5577       /* End of code added by Tord */
5578       case IllegalMove:         /* bug or odd chess variant */
5579         if(currentMoveString[1] == '@') { // illegal drop
5580           *fromX = WhiteOnMove(moveNum) ?
5581             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5582             (int) CharToPiece(ToLower(currentMoveString[0]));
5583           goto drop;
5584         }
5585         *fromX = currentMoveString[0] - AAA;
5586         *fromY = currentMoveString[1] - ONE;
5587         *toX = currentMoveString[2] - AAA;
5588         *toY = currentMoveString[3] - ONE;
5589         *promoChar = currentMoveString[4];
5590         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5591         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5592             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5593     if (appData.debugMode) {
5594         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5595     }
5596             *fromX = *fromY = *toX = *toY = 0;
5597             return FALSE;
5598         }
5599         if (appData.testLegality) {
5600           return (*moveType != IllegalMove);
5601         } else {
5602           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5603                          // [HGM] lion: if this is a double move we are less critical
5604                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5605         }
5606
5607       case WhiteDrop:
5608       case BlackDrop:
5609         *fromX = *moveType == WhiteDrop ?
5610           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611           (int) CharToPiece(ToLower(currentMoveString[0]));
5612       drop:
5613         *fromY = DROP_RANK;
5614         *toX = currentMoveString[2] - AAA;
5615         *toY = currentMoveString[3] - ONE;
5616         *promoChar = NULLCHAR;
5617         return TRUE;
5618
5619       case AmbiguousMove:
5620       case ImpossibleMove:
5621       case EndOfFile:
5622       case ElapsedTime:
5623       case Comment:
5624       case PGNTag:
5625       case NAG:
5626       case WhiteWins:
5627       case BlackWins:
5628       case GameIsDrawn:
5629       default:
5630     if (appData.debugMode) {
5631         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5632     }
5633         /* bug? */
5634         *fromX = *fromY = *toX = *toY = 0;
5635         *promoChar = NULLCHAR;
5636         return FALSE;
5637     }
5638 }
5639
5640 Boolean pushed = FALSE;
5641 char *lastParseAttempt;
5642
5643 void
5644 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5645 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5646   int fromX, fromY, toX, toY; char promoChar;
5647   ChessMove moveType;
5648   Boolean valid;
5649   int nr = 0;
5650
5651   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5652   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5653     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5654     pushed = TRUE;
5655   }
5656   endPV = forwardMostMove;
5657   do {
5658     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5659     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5660     lastParseAttempt = pv;
5661     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5662     if(!valid && nr == 0 &&
5663        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5664         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5665         // Hande case where played move is different from leading PV move
5666         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5667         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5668         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5669         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5670           endPV += 2; // if position different, keep this
5671           moveList[endPV-1][0] = fromX + AAA;
5672           moveList[endPV-1][1] = fromY + ONE;
5673           moveList[endPV-1][2] = toX + AAA;
5674           moveList[endPV-1][3] = toY + ONE;
5675           parseList[endPV-1][0] = NULLCHAR;
5676           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5677         }
5678       }
5679     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5680     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5681     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5682     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5683         valid++; // allow comments in PV
5684         continue;
5685     }
5686     nr++;
5687     if(endPV+1 > framePtr) break; // no space, truncate
5688     if(!valid) break;
5689     endPV++;
5690     CopyBoard(boards[endPV], boards[endPV-1]);
5691     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5692     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5693     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5694     CoordsToAlgebraic(boards[endPV - 1],
5695                              PosFlags(endPV - 1),
5696                              fromY, fromX, toY, toX, promoChar,
5697                              parseList[endPV - 1]);
5698   } while(valid);
5699   if(atEnd == 2) return; // used hidden, for PV conversion
5700   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5701   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5702   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5703                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5704   DrawPosition(TRUE, boards[currentMove]);
5705 }
5706
5707 int
5708 MultiPV (ChessProgramState *cps, int kind)
5709 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5710         int i;
5711         for(i=0; i<cps->nrOptions; i++) {
5712             char *s = cps->option[i].name;
5713             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5714             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5715                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5716         }
5717         return -1;
5718 }
5719
5720 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5721 static int multi, pv_margin;
5722 static ChessProgramState *activeCps;
5723
5724 Boolean
5725 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5726 {
5727         int startPV, lineStart, origIndex = index;
5728         char *p, buf2[MSG_SIZ];
5729         ChessProgramState *cps = (pane ? &second : &first);
5730
5731         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5732         lastX = x; lastY = y;
5733         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5734         lineStart = startPV = index;
5735         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5736         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5737         index = startPV;
5738         do{ while(buf[index] && buf[index] != '\n') index++;
5739         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5740         buf[index] = 0;
5741         if(lineStart == 0 && gameMode == AnalyzeMode) {
5742             int n = 0;
5743             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5744             if(n == 0) { // click not on "fewer" or "more"
5745                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5746                     pv_margin = cps->option[multi].value;
5747                     activeCps = cps; // non-null signals margin adjustment
5748                 }
5749             } else if((multi = MultiPV(cps, 1)) >= 0) {
5750                 n += cps->option[multi].value; if(n < 1) n = 1;
5751                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5752                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5753                 cps->option[multi].value = n;
5754                 *start = *end = 0;
5755                 return FALSE;
5756             }
5757         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5758                 ExcludeClick(origIndex - lineStart);
5759                 return FALSE;
5760         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5761                 Collapse(origIndex - lineStart);
5762                 return FALSE;
5763         }
5764         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5765         *start = startPV; *end = index-1;
5766         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5767         return TRUE;
5768 }
5769
5770 char *
5771 PvToSAN (char *pv)
5772 {
5773         static char buf[10*MSG_SIZ];
5774         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5775         *buf = NULLCHAR;
5776         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5777         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5778         for(i = forwardMostMove; i<endPV; i++){
5779             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5780             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5781             k += strlen(buf+k);
5782         }
5783         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5784         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5785         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5786         endPV = savedEnd;
5787         return buf;
5788 }
5789
5790 Boolean
5791 LoadPV (int x, int y)
5792 { // called on right mouse click to load PV
5793   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5794   lastX = x; lastY = y;
5795   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5796   extendGame = FALSE;
5797   return TRUE;
5798 }
5799
5800 void
5801 UnLoadPV ()
5802 {
5803   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5804   if(activeCps) {
5805     if(pv_margin != activeCps->option[multi].value) {
5806       char buf[MSG_SIZ];
5807       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5808       SendToProgram(buf, activeCps);
5809       activeCps->option[multi].value = pv_margin;
5810     }
5811     activeCps = NULL;
5812     return;
5813   }
5814   if(endPV < 0) return;
5815   if(appData.autoCopyPV) CopyFENToClipboard();
5816   endPV = -1;
5817   if(extendGame && currentMove > forwardMostMove) {
5818         Boolean saveAnimate = appData.animate;
5819         if(pushed) {
5820             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5821                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5822             } else storedGames--; // abandon shelved tail of original game
5823         }
5824         pushed = FALSE;
5825         forwardMostMove = currentMove;
5826         currentMove = oldFMM;
5827         appData.animate = FALSE;
5828         ToNrEvent(forwardMostMove);
5829         appData.animate = saveAnimate;
5830   }
5831   currentMove = forwardMostMove;
5832   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5833   ClearPremoveHighlights();
5834   DrawPosition(TRUE, boards[currentMove]);
5835 }
5836
5837 void
5838 MovePV (int x, int y, int h)
5839 { // step through PV based on mouse coordinates (called on mouse move)
5840   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5841
5842   if(activeCps) { // adjusting engine's multi-pv margin
5843     if(x > lastX) pv_margin++; else
5844     if(x < lastX) pv_margin -= (pv_margin > 0);
5845     if(x != lastX) {
5846       char buf[MSG_SIZ];
5847       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5848       DisplayMessage(buf, "");
5849     }
5850     lastX = x;
5851     return;
5852   }
5853   // we must somehow check if right button is still down (might be released off board!)
5854   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5855   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5856   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5857   if(!step) return;
5858   lastX = x; lastY = y;
5859
5860   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5861   if(endPV < 0) return;
5862   if(y < margin) step = 1; else
5863   if(y > h - margin) step = -1;
5864   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5865   currentMove += step;
5866   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5867   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5868                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5869   DrawPosition(FALSE, boards[currentMove]);
5870 }
5871
5872
5873 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5874 // All positions will have equal probability, but the current method will not provide a unique
5875 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5876 #define DARK 1
5877 #define LITE 2
5878 #define ANY 3
5879
5880 int squaresLeft[4];
5881 int piecesLeft[(int)BlackPawn];
5882 int seed, nrOfShuffles;
5883
5884 void
5885 GetPositionNumber ()
5886 {       // sets global variable seed
5887         int i;
5888
5889         seed = appData.defaultFrcPosition;
5890         if(seed < 0) { // randomize based on time for negative FRC position numbers
5891                 for(i=0; i<50; i++) seed += random();
5892                 seed = random() ^ random() >> 8 ^ random() << 8;
5893                 if(seed<0) seed = -seed;
5894         }
5895 }
5896
5897 int
5898 put (Board board, int pieceType, int rank, int n, int shade)
5899 // put the piece on the (n-1)-th empty squares of the given shade
5900 {
5901         int i;
5902
5903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5904                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5905                         board[rank][i] = (ChessSquare) pieceType;
5906                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5907                         squaresLeft[ANY]--;
5908                         piecesLeft[pieceType]--;
5909                         return i;
5910                 }
5911         }
5912         return -1;
5913 }
5914
5915
5916 void
5917 AddOnePiece (Board board, int pieceType, int rank, int shade)
5918 // calculate where the next piece goes, (any empty square), and put it there
5919 {
5920         int i;
5921
5922         i = seed % squaresLeft[shade];
5923         nrOfShuffles *= squaresLeft[shade];
5924         seed /= squaresLeft[shade];
5925         put(board, pieceType, rank, i, shade);
5926 }
5927
5928 void
5929 AddTwoPieces (Board board, int pieceType, int rank)
5930 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5931 {
5932         int i, n=squaresLeft[ANY], j=n-1, k;
5933
5934         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5935         i = seed % k;  // pick one
5936         nrOfShuffles *= k;
5937         seed /= k;
5938         while(i >= j) i -= j--;
5939         j = n - 1 - j; i += j;
5940         put(board, pieceType, rank, j, ANY);
5941         put(board, pieceType, rank, i, ANY);
5942 }
5943
5944 void
5945 SetUpShuffle (Board board, int number)
5946 {
5947         int i, p, first=1;
5948
5949         GetPositionNumber(); nrOfShuffles = 1;
5950
5951         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5952         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5953         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5954
5955         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5956
5957         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5958             p = (int) board[0][i];
5959             if(p < (int) BlackPawn) piecesLeft[p] ++;
5960             board[0][i] = EmptySquare;
5961         }
5962
5963         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5964             // shuffles restricted to allow normal castling put KRR first
5965             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5966                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5968                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5970                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5971             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5972                 put(board, WhiteRook, 0, 0, ANY);
5973             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5974         }
5975
5976         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5977             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5978             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5979                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5980                 while(piecesLeft[p] >= 2) {
5981                     AddOnePiece(board, p, 0, LITE);
5982                     AddOnePiece(board, p, 0, DARK);
5983                 }
5984                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5985             }
5986
5987         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5988             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5989             // but we leave King and Rooks for last, to possibly obey FRC restriction
5990             if(p == (int)WhiteRook) continue;
5991             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5992             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5993         }
5994
5995         // now everything is placed, except perhaps King (Unicorn) and Rooks
5996
5997         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5998             // Last King gets castling rights
5999             while(piecesLeft[(int)WhiteUnicorn]) {
6000                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6001                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6002             }
6003
6004             while(piecesLeft[(int)WhiteKing]) {
6005                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6006                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6007             }
6008
6009
6010         } else {
6011             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6012             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6013         }
6014
6015         // Only Rooks can be left; simply place them all
6016         while(piecesLeft[(int)WhiteRook]) {
6017                 i = put(board, WhiteRook, 0, 0, ANY);
6018                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6019                         if(first) {
6020                                 first=0;
6021                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6022                         }
6023                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6024                 }
6025         }
6026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6027             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6028         }
6029
6030         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6031 }
6032
6033 int
6034 ptclen (const char *s, char *escapes)
6035 {
6036     int n = 0;
6037     if(!*escapes) return strlen(s);
6038     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6039     return n;
6040 }
6041
6042 int
6043 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6044 /* [HGM] moved here from winboard.c because of its general usefulness */
6045 /*       Basically a safe strcpy that uses the last character as King */
6046 {
6047     int result = FALSE; int NrPieces;
6048     unsigned char partner[EmptySquare];
6049
6050     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6051                     && NrPieces >= 12 && !(NrPieces&1)) {
6052         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6053
6054         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6055         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6056             char *p, c=0;
6057             if(map[j] == '/') offs = WhitePBishop - i, j++;
6058             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6059             table[i+offs] = map[j++];
6060             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6061             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6062             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6063         }
6064         table[(int) WhiteKing]  = map[j++];
6065         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6066             char *p, c=0;
6067             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6068             i = WHITE_TO_BLACK ii;
6069             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6070             table[i+offs] = map[j++];
6071             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6072             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6073             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6074         }
6075         table[(int) BlackKing]  = map[j++];
6076
6077
6078         if(*escapes) { // set up promotion pairing
6079             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6080             // pieceToChar entirely filled, so we can look up specified partners
6081             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6082                 int c = table[i];
6083                 if(c == '^' || c == '-') { // has specified partner
6084                     int p;
6085                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6086                     if(c == '^') table[i] = '+';
6087                     if(p < EmptySquare) {
6088                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6089                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6090                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6091                     }
6092                 } else if(c == '*') {
6093                     table[i] = partner[i];
6094                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6095                 }
6096             }
6097         }
6098
6099         result = TRUE;
6100     }
6101
6102     return result;
6103 }
6104
6105 int
6106 SetCharTable (unsigned char *table, const char * map)
6107 {
6108     return SetCharTableEsc(table, map, "");
6109 }
6110
6111 void
6112 Prelude (Board board)
6113 {       // [HGM] superchess: random selection of exo-pieces
6114         int i, j, k; ChessSquare p;
6115         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6116
6117         GetPositionNumber(); // use FRC position number
6118
6119         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6120             SetCharTable(pieceToChar, appData.pieceToCharTable);
6121             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6122                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6123         }
6124
6125         j = seed%4;                 seed /= 4;
6126         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%3 + (seed%3 >= j); seed /= 3;
6130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%3;                 seed /= 3;
6134         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6136         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6137         j = seed%2 + (seed%2 >= j); seed /= 2;
6138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6140         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6141         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6142         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6143         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6144         put(board, exoPieces[0],    0, 0, ANY);
6145         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6146 }
6147
6148 void
6149 InitPosition (int redraw)
6150 {
6151     ChessSquare (* pieces)[BOARD_FILES];
6152     int i, j, pawnRow=1, pieceRows=1, overrule,
6153     oldx = gameInfo.boardWidth,
6154     oldy = gameInfo.boardHeight,
6155     oldh = gameInfo.holdingsWidth;
6156     static int oldv;
6157
6158     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6159
6160     /* [AS] Initialize pv info list [HGM] and game status */
6161     {
6162         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6163             pvInfoList[i].depth = 0;
6164             boards[i][EP_STATUS] = EP_NONE;
6165             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6166         }
6167
6168         initialRulePlies = 0; /* 50-move counter start */
6169
6170         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6171         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6172     }
6173
6174
6175     /* [HGM] logic here is completely changed. In stead of full positions */
6176     /* the initialized data only consist of the two backranks. The switch */
6177     /* selects which one we will use, which is than copied to the Board   */
6178     /* initialPosition, which for the rest is initialized by Pawns and    */
6179     /* empty squares. This initial position is then copied to boards[0],  */
6180     /* possibly after shuffling, so that it remains available.            */
6181
6182     gameInfo.holdingsWidth = 0; /* default board sizes */
6183     gameInfo.boardWidth    = 8;
6184     gameInfo.boardHeight   = 8;
6185     gameInfo.holdingsSize  = 0;
6186     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6187     for(i=0; i<BOARD_FILES-6; i++)
6188       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6189     initialPosition[EP_STATUS] = EP_NONE;
6190     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6191     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6192     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6193          SetCharTable(pieceNickName, appData.pieceNickNames);
6194     else SetCharTable(pieceNickName, "............");
6195     pieces = FIDEArray;
6196
6197     switch (gameInfo.variant) {
6198     case VariantFischeRandom:
6199       shuffleOpenings = TRUE;
6200       appData.fischerCastling = TRUE;
6201     default:
6202       break;
6203     case VariantShatranj:
6204       pieces = ShatranjArray;
6205       nrCastlingRights = 0;
6206       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6207       break;
6208     case VariantMakruk:
6209       pieces = makrukArray;
6210       nrCastlingRights = 0;
6211       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6212       break;
6213     case VariantASEAN:
6214       pieces = aseanArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6217       break;
6218     case VariantTwoKings:
6219       pieces = twoKingsArray;
6220       break;
6221     case VariantGrand:
6222       pieces = GrandArray;
6223       nrCastlingRights = 0;
6224       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225       gameInfo.boardWidth = 10;
6226       gameInfo.boardHeight = 10;
6227       gameInfo.holdingsSize = 7;
6228       break;
6229     case VariantCapaRandom:
6230       shuffleOpenings = TRUE;
6231       appData.fischerCastling = TRUE;
6232     case VariantCapablanca:
6233       pieces = CapablancaArray;
6234       gameInfo.boardWidth = 10;
6235       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6236       break;
6237     case VariantGothic:
6238       pieces = GothicArray;
6239       gameInfo.boardWidth = 10;
6240       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6241       break;
6242     case VariantSChess:
6243       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6244       gameInfo.holdingsSize = 7;
6245       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6246       break;
6247     case VariantJanus:
6248       pieces = JanusArray;
6249       gameInfo.boardWidth = 10;
6250       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6251       nrCastlingRights = 6;
6252         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6253         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6254         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6255         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6256         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6257         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6258       break;
6259     case VariantFalcon:
6260       pieces = FalconArray;
6261       gameInfo.boardWidth = 10;
6262       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6263       break;
6264     case VariantXiangqi:
6265       pieces = XiangqiArray;
6266       gameInfo.boardWidth  = 9;
6267       gameInfo.boardHeight = 10;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6270       break;
6271     case VariantShogi:
6272       pieces = ShogiArray;
6273       gameInfo.boardWidth  = 9;
6274       gameInfo.boardHeight = 9;
6275       gameInfo.holdingsSize = 7;
6276       nrCastlingRights = 0;
6277       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6278       break;
6279     case VariantChu:
6280       pieces = ChuArray; pieceRows = 3;
6281       gameInfo.boardWidth  = 12;
6282       gameInfo.boardHeight = 12;
6283       nrCastlingRights = 0;
6284 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6285   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6286       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6287                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6288       break;
6289     case VariantCourier:
6290       pieces = CourierArray;
6291       gameInfo.boardWidth  = 12;
6292       nrCastlingRights = 0;
6293       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6294       break;
6295     case VariantKnightmate:
6296       pieces = KnightmateArray;
6297       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6298       break;
6299     case VariantSpartan:
6300       pieces = SpartanArray;
6301       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6302       break;
6303     case VariantLion:
6304       pieces = lionArray;
6305       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6306       break;
6307     case VariantChuChess:
6308       pieces = ChuChessArray;
6309       gameInfo.boardWidth = 10;
6310       gameInfo.boardHeight = 10;
6311       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6312       break;
6313     case VariantFairy:
6314       pieces = fairyArray;
6315       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6316       break;
6317     case VariantGreat:
6318       pieces = GreatArray;
6319       gameInfo.boardWidth = 10;
6320       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6321       gameInfo.holdingsSize = 8;
6322       break;
6323     case VariantSuper:
6324       pieces = FIDEArray;
6325       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6326       gameInfo.holdingsSize = 8;
6327       startedFromSetupPosition = TRUE;
6328       break;
6329     case VariantCrazyhouse:
6330     case VariantBughouse:
6331       pieces = FIDEArray;
6332       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6333       gameInfo.holdingsSize = 5;
6334       break;
6335     case VariantWildCastle:
6336       pieces = FIDEArray;
6337       /* !!?shuffle with kings guaranteed to be on d or e file */
6338       shuffleOpenings = 1;
6339       break;
6340     case VariantNoCastle:
6341       pieces = FIDEArray;
6342       nrCastlingRights = 0;
6343       /* !!?unconstrained back-rank shuffle */
6344       shuffleOpenings = 1;
6345       break;
6346     }
6347
6348     overrule = 0;
6349     if(appData.NrFiles >= 0) {
6350         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6351         gameInfo.boardWidth = appData.NrFiles;
6352     }
6353     if(appData.NrRanks >= 0) {
6354         gameInfo.boardHeight = appData.NrRanks;
6355     }
6356     if(appData.holdingsSize >= 0) {
6357         i = appData.holdingsSize;
6358         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6359         gameInfo.holdingsSize = i;
6360     }
6361     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6362     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6363         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6364
6365     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6366     if(pawnRow < 1) pawnRow = 1;
6367     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6368        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6369     if(gameInfo.variant == VariantChu) pawnRow = 3;
6370
6371     /* User pieceToChar list overrules defaults */
6372     if(appData.pieceToCharTable != NULL)
6373         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6374
6375     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6376
6377         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6378             s = (ChessSquare) 0; /* account holding counts in guard band */
6379         for( i=0; i<BOARD_HEIGHT; i++ )
6380             initialPosition[i][j] = s;
6381
6382         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6383         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6384         initialPosition[pawnRow][j] = WhitePawn;
6385         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6386         if(gameInfo.variant == VariantXiangqi) {
6387             if(j&1) {
6388                 initialPosition[pawnRow][j] =
6389                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6390                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6391                    initialPosition[2][j] = WhiteCannon;
6392                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6393                 }
6394             }
6395         }
6396         if(gameInfo.variant == VariantChu) {
6397              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6398                initialPosition[pawnRow+1][j] = WhiteCobra,
6399                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6400              for(i=1; i<pieceRows; i++) {
6401                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6402                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6403              }
6404         }
6405         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6406             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6407                initialPosition[0][j] = WhiteRook;
6408                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6409             }
6410         }
6411         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6412     }
6413     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6414     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6415
6416             j=BOARD_LEFT+1;
6417             initialPosition[1][j] = WhiteBishop;
6418             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6419             j=BOARD_RGHT-2;
6420             initialPosition[1][j] = WhiteRook;
6421             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6422     }
6423
6424     if( nrCastlingRights == -1) {
6425         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6426         /*       This sets default castling rights from none to normal corners   */
6427         /* Variants with other castling rights must set them themselves above    */
6428         nrCastlingRights = 6;
6429
6430         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6431         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6432         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6433         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6434         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6435         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6436      }
6437
6438      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6439      if(gameInfo.variant == VariantGreat) { // promotion commoners
6440         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6441         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6442         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6443         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6444      }
6445      if( gameInfo.variant == VariantSChess ) {
6446       initialPosition[1][0] = BlackMarshall;
6447       initialPosition[2][0] = BlackAngel;
6448       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6449       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6450       initialPosition[1][1] = initialPosition[2][1] =
6451       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6452      }
6453   if (appData.debugMode) {
6454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6455   }
6456     if(shuffleOpenings) {
6457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6458         startedFromSetupPosition = TRUE;
6459     }
6460     if(startedFromPositionFile) {
6461       /* [HGM] loadPos: use PositionFile for every new game */
6462       CopyBoard(initialPosition, filePosition);
6463       for(i=0; i<nrCastlingRights; i++)
6464           initialRights[i] = filePosition[CASTLING][i];
6465       startedFromSetupPosition = TRUE;
6466     }
6467     if(*appData.men) LoadPieceDesc(appData.men);
6468
6469     CopyBoard(boards[0], initialPosition);
6470
6471     if(oldx != gameInfo.boardWidth ||
6472        oldy != gameInfo.boardHeight ||
6473        oldv != gameInfo.variant ||
6474        oldh != gameInfo.holdingsWidth
6475                                          )
6476             InitDrawingSizes(-2 ,0);
6477
6478     oldv = gameInfo.variant;
6479     if (redraw)
6480       DrawPosition(TRUE, boards[currentMove]);
6481 }
6482
6483 void
6484 SendBoard (ChessProgramState *cps, int moveNum)
6485 {
6486     char message[MSG_SIZ];
6487
6488     if (cps->useSetboard) {
6489       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6490       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6491       SendToProgram(message, cps);
6492       free(fen);
6493
6494     } else {
6495       ChessSquare *bp;
6496       int i, j, left=0, right=BOARD_WIDTH;
6497       /* Kludge to set black to move, avoiding the troublesome and now
6498        * deprecated "black" command.
6499        */
6500       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6501         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6502
6503       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6504
6505       SendToProgram("edit\n", cps);
6506       SendToProgram("#\n", cps);
6507       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6508         bp = &boards[moveNum][i][left];
6509         for (j = left; j < right; j++, bp++) {
6510           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6511           if ((int) *bp < (int) BlackPawn) {
6512             if(j == BOARD_RGHT+1)
6513                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6514             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6515             if(message[0] == '+' || message[0] == '~') {
6516               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6517                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6518                         AAA + j, ONE + i - '0');
6519             }
6520             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6521                 message[1] = BOARD_RGHT   - 1 - j + '1';
6522                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6523             }
6524             SendToProgram(message, cps);
6525           }
6526         }
6527       }
6528
6529       SendToProgram("c\n", cps);
6530       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6531         bp = &boards[moveNum][i][left];
6532         for (j = left; j < right; j++, bp++) {
6533           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6534           if (((int) *bp != (int) EmptySquare)
6535               && ((int) *bp >= (int) BlackPawn)) {
6536             if(j == BOARD_LEFT-2)
6537                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6538             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6539                     AAA + j, ONE + i - '0');
6540             if(message[0] == '+' || message[0] == '~') {
6541               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6542                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6543                         AAA + j, ONE + i - '0');
6544             }
6545             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6546                 message[1] = BOARD_RGHT   - 1 - j + '1';
6547                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6548             }
6549             SendToProgram(message, cps);
6550           }
6551         }
6552       }
6553
6554       SendToProgram(".\n", cps);
6555     }
6556     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6557 }
6558
6559 char exclusionHeader[MSG_SIZ];
6560 int exCnt, excludePtr;
6561 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6562 static Exclusion excluTab[200];
6563 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6564
6565 static void
6566 WriteMap (int s)
6567 {
6568     int j;
6569     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6570     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6571 }
6572
6573 static void
6574 ClearMap ()
6575 {
6576     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6577     excludePtr = 24; exCnt = 0;
6578     WriteMap(0);
6579 }
6580
6581 static void
6582 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6583 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6584     char buf[2*MOVE_LEN], *p;
6585     Exclusion *e = excluTab;
6586     int i;
6587     for(i=0; i<exCnt; i++)
6588         if(e[i].ff == fromX && e[i].fr == fromY &&
6589            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6590     if(i == exCnt) { // was not in exclude list; add it
6591         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6592         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6593             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6594             return; // abort
6595         }
6596         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6597         excludePtr++; e[i].mark = excludePtr++;
6598         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6599         exCnt++;
6600     }
6601     exclusionHeader[e[i].mark] = state;
6602 }
6603
6604 static int
6605 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6606 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6607     char buf[MSG_SIZ];
6608     int j, k;
6609     ChessMove moveType;
6610     if((signed char)promoChar == -1) { // kludge to indicate best move
6611         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6612             return 1; // if unparsable, abort
6613     }
6614     // update exclusion map (resolving toggle by consulting existing state)
6615     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6616     j = k%8; k >>= 3;
6617     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6618     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6619          excludeMap[k] |=   1<<j;
6620     else excludeMap[k] &= ~(1<<j);
6621     // update header
6622     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6623     // inform engine
6624     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6625     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6626     SendToBoth(buf);
6627     return (state == '+');
6628 }
6629
6630 static void
6631 ExcludeClick (int index)
6632 {
6633     int i, j;
6634     Exclusion *e = excluTab;
6635     if(index < 25) { // none, best or tail clicked
6636         if(index < 13) { // none: include all
6637             WriteMap(0); // clear map
6638             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6639             SendToBoth("include all\n"); // and inform engine
6640         } else if(index > 18) { // tail
6641             if(exclusionHeader[19] == '-') { // tail was excluded
6642                 SendToBoth("include all\n");
6643                 WriteMap(0); // clear map completely
6644                 // now re-exclude selected moves
6645                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6646                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6647             } else { // tail was included or in mixed state
6648                 SendToBoth("exclude all\n");
6649                 WriteMap(0xFF); // fill map completely
6650                 // now re-include selected moves
6651                 j = 0; // count them
6652                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6653                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6654                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6655             }
6656         } else { // best
6657             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6658         }
6659     } else {
6660         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6661             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6662             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6663             break;
6664         }
6665     }
6666 }
6667
6668 ChessSquare
6669 DefaultPromoChoice (int white)
6670 {
6671     ChessSquare result;
6672     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6673        gameInfo.variant == VariantMakruk)
6674         result = WhiteFerz; // no choice
6675     else if(gameInfo.variant == VariantASEAN)
6676         result = WhiteRook; // no choice
6677     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6678         result= WhiteKing; // in Suicide Q is the last thing we want
6679     else if(gameInfo.variant == VariantSpartan)
6680         result = white ? WhiteQueen : WhiteAngel;
6681     else result = WhiteQueen;
6682     if(!white) result = WHITE_TO_BLACK result;
6683     return result;
6684 }
6685
6686 static int autoQueen; // [HGM] oneclick
6687
6688 int
6689 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6690 {
6691     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6692     /* [HGM] add Shogi promotions */
6693     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6694     ChessSquare piece, partner;
6695     ChessMove moveType;
6696     Boolean premove;
6697
6698     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6699     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6700
6701     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6702       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6703         return FALSE;
6704
6705     piece = boards[currentMove][fromY][fromX];
6706     if(gameInfo.variant == VariantChu) {
6707         promotionZoneSize = BOARD_HEIGHT/3;
6708         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6709     } else if(gameInfo.variant == VariantShogi) {
6710         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6711         highestPromotingPiece = (int)WhiteAlfil;
6712     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6713         promotionZoneSize = 3;
6714     }
6715
6716     // Treat Lance as Pawn when it is not representing Amazon or Lance
6717     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6718         if(piece == WhiteLance) piece = WhitePawn; else
6719         if(piece == BlackLance) piece = BlackPawn;
6720     }
6721
6722     // next weed out all moves that do not touch the promotion zone at all
6723     if((int)piece >= BlackPawn) {
6724         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6725              return FALSE;
6726         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6727         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6728     } else {
6729         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6730            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6731         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6732              return FALSE;
6733     }
6734
6735     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6736
6737     // weed out mandatory Shogi promotions
6738     if(gameInfo.variant == VariantShogi) {
6739         if(piece >= BlackPawn) {
6740             if(toY == 0 && piece == BlackPawn ||
6741                toY == 0 && piece == BlackQueen ||
6742                toY <= 1 && piece == BlackKnight) {
6743                 *promoChoice = '+';
6744                 return FALSE;
6745             }
6746         } else {
6747             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6748                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6749                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6750                 *promoChoice = '+';
6751                 return FALSE;
6752             }
6753         }
6754     }
6755
6756     // weed out obviously illegal Pawn moves
6757     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6758         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6759         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6760         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6761         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6762         // note we are not allowed to test for valid (non-)capture, due to premove
6763     }
6764
6765     // we either have a choice what to promote to, or (in Shogi) whether to promote
6766     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6767        gameInfo.variant == VariantMakruk) {
6768         ChessSquare p=BlackFerz;  // no choice
6769         while(p < EmptySquare) {  //but make sure we use piece that exists
6770             *promoChoice = PieceToChar(p++);
6771             if(*promoChoice != '.') break;
6772         }
6773         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6774     }
6775     // no sense asking what we must promote to if it is going to explode...
6776     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6777         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6778         return FALSE;
6779     }
6780     // give caller the default choice even if we will not make it
6781     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6782     partner = piece; // pieces can promote if the pieceToCharTable says so
6783     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6784     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6785     if(        sweepSelect && gameInfo.variant != VariantGreat
6786                            && gameInfo.variant != VariantGrand
6787                            && gameInfo.variant != VariantSuper) return FALSE;
6788     if(autoQueen) return FALSE; // predetermined
6789
6790     // suppress promotion popup on illegal moves that are not premoves
6791     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6792               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6793     if(appData.testLegality && !premove) {
6794         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6795                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6796         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6797         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6798             return FALSE;
6799     }
6800
6801     return TRUE;
6802 }
6803
6804 int
6805 InPalace (int row, int column)
6806 {   /* [HGM] for Xiangqi */
6807     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6808          column < (BOARD_WIDTH + 4)/2 &&
6809          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6810     return FALSE;
6811 }
6812
6813 int
6814 PieceForSquare (int x, int y)
6815 {
6816   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6817      return -1;
6818   else
6819      return boards[currentMove][y][x];
6820 }
6821
6822 int
6823 OKToStartUserMove (int x, int y)
6824 {
6825     ChessSquare from_piece;
6826     int white_piece;
6827
6828     if (matchMode) return FALSE;
6829     if (gameMode == EditPosition) return TRUE;
6830
6831     if (x >= 0 && y >= 0)
6832       from_piece = boards[currentMove][y][x];
6833     else
6834       from_piece = EmptySquare;
6835
6836     if (from_piece == EmptySquare) return FALSE;
6837
6838     white_piece = (int)from_piece >= (int)WhitePawn &&
6839       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6840
6841     switch (gameMode) {
6842       case AnalyzeFile:
6843       case TwoMachinesPlay:
6844       case EndOfGame:
6845         return FALSE;
6846
6847       case IcsObserving:
6848       case IcsIdle:
6849         return FALSE;
6850
6851       case MachinePlaysWhite:
6852       case IcsPlayingBlack:
6853         if (appData.zippyPlay) return FALSE;
6854         if (white_piece) {
6855             DisplayMoveError(_("You are playing Black"));
6856             return FALSE;
6857         }
6858         break;
6859
6860       case MachinePlaysBlack:
6861       case IcsPlayingWhite:
6862         if (appData.zippyPlay) return FALSE;
6863         if (!white_piece) {
6864             DisplayMoveError(_("You are playing White"));
6865             return FALSE;
6866         }
6867         break;
6868
6869       case PlayFromGameFile:
6870             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6871       case EditGame:
6872       case AnalyzeMode:
6873         if (!white_piece && WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is White's turn"));
6875             return FALSE;
6876         }
6877         if (white_piece && !WhiteOnMove(currentMove)) {
6878             DisplayMoveError(_("It is Black's turn"));
6879             return FALSE;
6880         }
6881         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6882             /* Editing correspondence game history */
6883             /* Could disallow this or prompt for confirmation */
6884             cmailOldMove = -1;
6885         }
6886         break;
6887
6888       case BeginningOfGame:
6889         if (appData.icsActive) return FALSE;
6890         if (!appData.noChessProgram) {
6891             if (!white_piece) {
6892                 DisplayMoveError(_("You are playing White"));
6893                 return FALSE;
6894             }
6895         }
6896         break;
6897
6898       case Training:
6899         if (!white_piece && WhiteOnMove(currentMove)) {
6900             DisplayMoveError(_("It is White's turn"));
6901             return FALSE;
6902         }
6903         if (white_piece && !WhiteOnMove(currentMove)) {
6904             DisplayMoveError(_("It is Black's turn"));
6905             return FALSE;
6906         }
6907         break;
6908
6909       default:
6910       case IcsExamining:
6911         break;
6912     }
6913     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6914         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6915         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6916         && gameMode != AnalyzeFile && gameMode != Training) {
6917         DisplayMoveError(_("Displayed position is not current"));
6918         return FALSE;
6919     }
6920     return TRUE;
6921 }
6922
6923 Boolean
6924 OnlyMove (int *x, int *y, Boolean captures)
6925 {
6926     DisambiguateClosure cl;
6927     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6928     switch(gameMode) {
6929       case MachinePlaysBlack:
6930       case IcsPlayingWhite:
6931       case BeginningOfGame:
6932         if(!WhiteOnMove(currentMove)) return FALSE;
6933         break;
6934       case MachinePlaysWhite:
6935       case IcsPlayingBlack:
6936         if(WhiteOnMove(currentMove)) return FALSE;
6937         break;
6938       case EditGame:
6939         break;
6940       default:
6941         return FALSE;
6942     }
6943     cl.pieceIn = EmptySquare;
6944     cl.rfIn = *y;
6945     cl.ffIn = *x;
6946     cl.rtIn = -1;
6947     cl.ftIn = -1;
6948     cl.promoCharIn = NULLCHAR;
6949     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6950     if( cl.kind == NormalMove ||
6951         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6952         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6953         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6954       fromX = cl.ff;
6955       fromY = cl.rf;
6956       *x = cl.ft;
6957       *y = cl.rt;
6958       return TRUE;
6959     }
6960     if(cl.kind != ImpossibleMove) return FALSE;
6961     cl.pieceIn = EmptySquare;
6962     cl.rfIn = -1;
6963     cl.ffIn = -1;
6964     cl.rtIn = *y;
6965     cl.ftIn = *x;
6966     cl.promoCharIn = NULLCHAR;
6967     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6968     if( cl.kind == NormalMove ||
6969         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6970         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6971         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6972       fromX = cl.ff;
6973       fromY = cl.rf;
6974       *x = cl.ft;
6975       *y = cl.rt;
6976       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6977       return TRUE;
6978     }
6979     return FALSE;
6980 }
6981
6982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6984 int lastLoadGameUseList = FALSE;
6985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6986 ChessMove lastLoadGameStart = EndOfFile;
6987 int doubleClick;
6988 Boolean addToBookFlag;
6989 static Board rightsBoard, nullBoard;
6990
6991 void
6992 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6993 {
6994     ChessMove moveType;
6995     ChessSquare pup;
6996     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6997
6998     /* Check if the user is playing in turn.  This is complicated because we
6999        let the user "pick up" a piece before it is his turn.  So the piece he
7000        tried to pick up may have been captured by the time he puts it down!
7001        Therefore we use the color the user is supposed to be playing in this
7002        test, not the color of the piece that is currently on the starting
7003        square---except in EditGame mode, where the user is playing both
7004        sides; fortunately there the capture race can't happen.  (It can
7005        now happen in IcsExamining mode, but that's just too bad.  The user
7006        will get a somewhat confusing message in that case.)
7007        */
7008
7009     switch (gameMode) {
7010       case AnalyzeFile:
7011       case TwoMachinesPlay:
7012       case EndOfGame:
7013       case IcsObserving:
7014       case IcsIdle:
7015         /* We switched into a game mode where moves are not accepted,
7016            perhaps while the mouse button was down. */
7017         return;
7018
7019       case MachinePlaysWhite:
7020         /* User is moving for Black */
7021         if (WhiteOnMove(currentMove)) {
7022             DisplayMoveError(_("It is White's turn"));
7023             return;
7024         }
7025         break;
7026
7027       case MachinePlaysBlack:
7028         /* User is moving for White */
7029         if (!WhiteOnMove(currentMove)) {
7030             DisplayMoveError(_("It is Black's turn"));
7031             return;
7032         }
7033         break;
7034
7035       case PlayFromGameFile:
7036             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7037       case EditGame:
7038       case IcsExamining:
7039       case BeginningOfGame:
7040       case AnalyzeMode:
7041       case Training:
7042         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7043         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7044             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7045             /* User is moving for Black */
7046             if (WhiteOnMove(currentMove)) {
7047                 DisplayMoveError(_("It is White's turn"));
7048                 return;
7049             }
7050         } else {
7051             /* User is moving for White */
7052             if (!WhiteOnMove(currentMove)) {
7053                 DisplayMoveError(_("It is Black's turn"));
7054                 return;
7055             }
7056         }
7057         break;
7058
7059       case IcsPlayingBlack:
7060         /* User is moving for Black */
7061         if (WhiteOnMove(currentMove)) {
7062             if (!appData.premove) {
7063                 DisplayMoveError(_("It is White's turn"));
7064             } else if (toX >= 0 && toY >= 0) {
7065                 premoveToX = toX;
7066                 premoveToY = toY;
7067                 premoveFromX = fromX;
7068                 premoveFromY = fromY;
7069                 premovePromoChar = promoChar;
7070                 gotPremove = 1;
7071                 if (appData.debugMode)
7072                     fprintf(debugFP, "Got premove: fromX %d,"
7073                             "fromY %d, toX %d, toY %d\n",
7074                             fromX, fromY, toX, toY);
7075             }
7076             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7077             return;
7078         }
7079         break;
7080
7081       case IcsPlayingWhite:
7082         /* User is moving for White */
7083         if (!WhiteOnMove(currentMove)) {
7084             if (!appData.premove) {
7085                 DisplayMoveError(_("It is Black's turn"));
7086             } else if (toX >= 0 && toY >= 0) {
7087                 premoveToX = toX;
7088                 premoveToY = toY;
7089                 premoveFromX = fromX;
7090                 premoveFromY = fromY;
7091                 premovePromoChar = promoChar;
7092                 gotPremove = 1;
7093                 if (appData.debugMode)
7094                     fprintf(debugFP, "Got premove: fromX %d,"
7095                             "fromY %d, toX %d, toY %d\n",
7096                             fromX, fromY, toX, toY);
7097             }
7098             DrawPosition(TRUE, boards[currentMove]);
7099             return;
7100         }
7101         break;
7102
7103       default:
7104         break;
7105
7106       case EditPosition:
7107         /* EditPosition, empty square, or different color piece;
7108            click-click move is possible */
7109         if (toX == -2 || toY == -2) {
7110             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7111             DrawPosition(FALSE, boards[currentMove]);
7112             return;
7113         } else if (toX >= 0 && toY >= 0) {
7114             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7115                 ChessSquare p = boards[0][rf][ff];
7116                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7117                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7118                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7119                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7120                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7121                     gatingPiece = p;
7122                 }
7123             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7124             boards[0][toY][toX] = boards[0][fromY][fromX];
7125             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7126                 if(boards[0][fromY][0] != EmptySquare) {
7127                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7128                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7129                 }
7130             } else
7131             if(fromX == BOARD_RGHT+1) {
7132                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7133                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7134                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7135                 }
7136             } else
7137             boards[0][fromY][fromX] = gatingPiece;
7138             ClearHighlights();
7139             DrawPosition(FALSE, boards[currentMove]);
7140             return;
7141         }
7142         return;
7143     }
7144
7145     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7146     pup = boards[currentMove][toY][toX];
7147
7148     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7149     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7150          if( pup != EmptySquare ) return;
7151          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7152            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7153                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7154            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7155            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7156            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7157            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7158          fromY = DROP_RANK;
7159     }
7160
7161     /* [HGM] always test for legality, to get promotion info */
7162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7163                                          fromY, fromX, toY, toX, promoChar);
7164
7165     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7166
7167     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7168
7169     /* [HGM] but possibly ignore an IllegalMove result */
7170     if (appData.testLegality) {
7171         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7172             DisplayMoveError(_("Illegal move"));
7173             return;
7174         }
7175     }
7176
7177     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7178         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7179              ClearPremoveHighlights(); // was included
7180         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7181         DrawPosition(FALSE, NULL);
7182         return;
7183     }
7184
7185     if(addToBookFlag) { // adding moves to book
7186         char buf[MSG_SIZ], move[MSG_SIZ];
7187         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7188         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7189                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7190         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7191         AddBookMove(buf);
7192         addToBookFlag = FALSE;
7193         ClearHighlights();
7194         return;
7195     }
7196
7197     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7198 }
7199
7200 /* Common tail of UserMoveEvent and DropMenuEvent */
7201 int
7202 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7203 {
7204     char *bookHit = 0;
7205
7206     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7207         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7208         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7209         if(WhiteOnMove(currentMove)) {
7210             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7211         } else {
7212             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7213         }
7214     }
7215
7216     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7217        move type in caller when we know the move is a legal promotion */
7218     if(moveType == NormalMove && promoChar)
7219         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7220
7221     /* [HGM] <popupFix> The following if has been moved here from
7222        UserMoveEvent(). Because it seemed to belong here (why not allow
7223        piece drops in training games?), and because it can only be
7224        performed after it is known to what we promote. */
7225     if (gameMode == Training) {
7226       /* compare the move played on the board to the next move in the
7227        * game. If they match, display the move and the opponent's response.
7228        * If they don't match, display an error message.
7229        */
7230       int saveAnimate;
7231       Board testBoard;
7232       CopyBoard(testBoard, boards[currentMove]);
7233       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7234
7235       if (CompareBoards(testBoard, boards[currentMove+1])) {
7236         ForwardInner(currentMove+1);
7237
7238         /* Autoplay the opponent's response.
7239          * if appData.animate was TRUE when Training mode was entered,
7240          * the response will be animated.
7241          */
7242         saveAnimate = appData.animate;
7243         appData.animate = animateTraining;
7244         ForwardInner(currentMove+1);
7245         appData.animate = saveAnimate;
7246
7247         /* check for the end of the game */
7248         if (currentMove >= forwardMostMove) {
7249           gameMode = PlayFromGameFile;
7250           ModeHighlight();
7251           SetTrainingModeOff();
7252           DisplayInformation(_("End of game"));
7253         }
7254       } else {
7255         DisplayError(_("Incorrect move"), 0);
7256       }
7257       return 1;
7258     }
7259
7260   /* Ok, now we know that the move is good, so we can kill
7261      the previous line in Analysis Mode */
7262   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7263                                 && currentMove < forwardMostMove) {
7264     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7265     else forwardMostMove = currentMove;
7266   }
7267
7268   ClearMap();
7269
7270   /* If we need the chess program but it's dead, restart it */
7271   ResurrectChessProgram();
7272
7273   /* A user move restarts a paused game*/
7274   if (pausing)
7275     PauseEvent();
7276
7277   thinkOutput[0] = NULLCHAR;
7278
7279   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7280
7281   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7282     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7283     return 1;
7284   }
7285
7286   if (gameMode == BeginningOfGame) {
7287     if (appData.noChessProgram) {
7288       gameMode = EditGame;
7289       SetGameInfo();
7290     } else {
7291       char buf[MSG_SIZ];
7292       gameMode = MachinePlaysBlack;
7293       StartClocks();
7294       SetGameInfo();
7295       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7296       DisplayTitle(buf);
7297       if (first.sendName) {
7298         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7299         SendToProgram(buf, &first);
7300       }
7301       StartClocks();
7302     }
7303     ModeHighlight();
7304   }
7305
7306   /* Relay move to ICS or chess engine */
7307   if (appData.icsActive) {
7308     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7309         gameMode == IcsExamining) {
7310       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7311         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7312         SendToICS("draw ");
7313         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7314       }
7315       // also send plain move, in case ICS does not understand atomic claims
7316       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7317       ics_user_moved = 1;
7318     }
7319   } else {
7320     if (first.sendTime && (gameMode == BeginningOfGame ||
7321                            gameMode == MachinePlaysWhite ||
7322                            gameMode == MachinePlaysBlack)) {
7323       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7324     }
7325     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7326          // [HGM] book: if program might be playing, let it use book
7327         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7328         first.maybeThinking = TRUE;
7329     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7330         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7331         SendBoard(&first, currentMove+1);
7332         if(second.analyzing) {
7333             if(!second.useSetboard) SendToProgram("undo\n", &second);
7334             SendBoard(&second, currentMove+1);
7335         }
7336     } else {
7337         SendMoveToProgram(forwardMostMove-1, &first);
7338         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7339     }
7340     if (currentMove == cmailOldMove + 1) {
7341       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7342     }
7343   }
7344
7345   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7346
7347   switch (gameMode) {
7348   case EditGame:
7349     if(appData.testLegality)
7350     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7351     case MT_NONE:
7352     case MT_CHECK:
7353       break;
7354     case MT_CHECKMATE:
7355     case MT_STAINMATE:
7356       if (WhiteOnMove(currentMove)) {
7357         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7358       } else {
7359         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7360       }
7361       break;
7362     case MT_STALEMATE:
7363       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7364       break;
7365     }
7366     break;
7367
7368   case MachinePlaysBlack:
7369   case MachinePlaysWhite:
7370     /* disable certain menu options while machine is thinking */
7371     SetMachineThinkingEnables();
7372     break;
7373
7374   default:
7375     break;
7376   }
7377
7378   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7379   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7380
7381   if(bookHit) { // [HGM] book: simulate book reply
7382         static char bookMove[MSG_SIZ]; // a bit generous?
7383
7384         programStats.nodes = programStats.depth = programStats.time =
7385         programStats.score = programStats.got_only_move = 0;
7386         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7387
7388         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7389         strcat(bookMove, bookHit);
7390         HandleMachineMove(bookMove, &first);
7391   }
7392   return 1;
7393 }
7394
7395 void
7396 MarkByFEN(char *fen)
7397 {
7398         int r, f;
7399         if(!appData.markers || !appData.highlightDragging) return;
7400         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7401         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7402         while(*fen) {
7403             int s = 0;
7404             marker[r][f] = 0;
7405             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7406             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7407             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7408             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7409             if(*fen == 'T') marker[r][f++] = 0; else
7410             if(*fen == 'Y') marker[r][f++] = 1; else
7411             if(*fen == 'G') marker[r][f++] = 3; else
7412             if(*fen == 'B') marker[r][f++] = 4; else
7413             if(*fen == 'C') marker[r][f++] = 5; else
7414             if(*fen == 'M') marker[r][f++] = 6; else
7415             if(*fen == 'W') marker[r][f++] = 7; else
7416             if(*fen == 'D') marker[r][f++] = 8; else
7417             if(*fen == 'R') marker[r][f++] = 2; else {
7418                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7419               f += s; fen -= s>0;
7420             }
7421             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7422             if(r < 0) break;
7423             fen++;
7424         }
7425         DrawPosition(TRUE, NULL);
7426 }
7427
7428 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7429
7430 void
7431 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7432 {
7433     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7434     Markers *m = (Markers *) closure;
7435     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7436                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7437         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7438                          || kind == WhiteCapturesEnPassant
7439                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7440     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7441 }
7442
7443 static int hoverSavedValid;
7444
7445 void
7446 MarkTargetSquares (int clear)
7447 {
7448   int x, y, sum=0;
7449   if(clear) { // no reason to ever suppress clearing
7450     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7451     hoverSavedValid = 0;
7452     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7453   } else {
7454     int capt = 0;
7455     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7456        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7457     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7458     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7459       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7460       if(capt)
7461       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7462     }
7463   }
7464   DrawPosition(FALSE, NULL);
7465 }
7466
7467 int
7468 Explode (Board board, int fromX, int fromY, int toX, int toY)
7469 {
7470     if(gameInfo.variant == VariantAtomic &&
7471        (board[toY][toX] != EmptySquare ||                     // capture?
7472         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7473                          board[fromY][fromX] == BlackPawn   )
7474       )) {
7475         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7476         return TRUE;
7477     }
7478     return FALSE;
7479 }
7480
7481 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7482
7483 int
7484 CanPromote (ChessSquare piece, int y)
7485 {
7486         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7487         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7488         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7489         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7490            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7491           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7492            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7493         return (piece == BlackPawn && y <= zone ||
7494                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7495                 piece == BlackLance && y <= zone ||
7496                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7497 }
7498
7499 void
7500 HoverEvent (int xPix, int yPix, int x, int y)
7501 {
7502         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7503         int r, f;
7504         if(!first.highlight) return;
7505         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7506         if(x == oldX && y == oldY) return; // only do something if we enter new square
7507         oldFromX = fromX; oldFromY = fromY;
7508         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7509           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7510             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7511           hoverSavedValid = 1;
7512         } else if(oldX != x || oldY != y) {
7513           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7514           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7515           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7516             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7517           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7518             char buf[MSG_SIZ];
7519             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7520             SendToProgram(buf, &first);
7521           }
7522           oldX = x; oldY = y;
7523 //        SetHighlights(fromX, fromY, x, y);
7524         }
7525 }
7526
7527 void ReportClick(char *action, int x, int y)
7528 {
7529         char buf[MSG_SIZ]; // Inform engine of what user does
7530         int r, f;
7531         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7532           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7533             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7534         if(!first.highlight || gameMode == EditPosition) return;
7535         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7536         SendToProgram(buf, &first);
7537 }
7538
7539 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7540
7541 void
7542 LeftClick (ClickType clickType, int xPix, int yPix)
7543 {
7544     int x, y;
7545     Boolean saveAnimate;
7546     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7547     char promoChoice = NULLCHAR;
7548     ChessSquare piece;
7549     static TimeMark lastClickTime, prevClickTime;
7550
7551     if(flashing) return;
7552
7553     x = EventToSquare(xPix, BOARD_WIDTH);
7554     y = EventToSquare(yPix, BOARD_HEIGHT);
7555     if (!flipView && y >= 0) {
7556         y = BOARD_HEIGHT - 1 - y;
7557     }
7558     if (flipView && x >= 0) {
7559         x = BOARD_WIDTH - 1 - x;
7560     }
7561
7562     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7563         static int dummy;
7564         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7565         right = TRUE;
7566         return;
7567     }
7568
7569     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7570
7571     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7572
7573     if (clickType == Press) ErrorPopDown();
7574     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7575
7576     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7577         defaultPromoChoice = promoSweep;
7578         promoSweep = EmptySquare;   // terminate sweep
7579         promoDefaultAltered = TRUE;
7580         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7581     }
7582
7583     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7584         if(clickType == Release) return; // ignore upclick of click-click destination
7585         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7586         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7587         if(gameInfo.holdingsWidth &&
7588                 (WhiteOnMove(currentMove)
7589                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7590                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7591             // click in right holdings, for determining promotion piece
7592             ChessSquare p = boards[currentMove][y][x];
7593             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7594             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7595             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7596                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7597                 fromX = fromY = -1;
7598                 return;
7599             }
7600         }
7601         DrawPosition(FALSE, boards[currentMove]);
7602         return;
7603     }
7604
7605     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7606     if(clickType == Press
7607             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7608               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7609               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7610         return;
7611
7612     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7613         // could be static click on premove from-square: abort premove
7614         gotPremove = 0;
7615         ClearPremoveHighlights();
7616     }
7617
7618     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7619         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7620
7621     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7622         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7623                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7624         defaultPromoChoice = DefaultPromoChoice(side);
7625     }
7626
7627     autoQueen = appData.alwaysPromoteToQueen;
7628
7629     if (fromX == -1) {
7630       int originalY = y;
7631       gatingPiece = EmptySquare;
7632       if (clickType != Press) {
7633         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7634             DragPieceEnd(xPix, yPix); dragging = 0;
7635             DrawPosition(FALSE, NULL);
7636         }
7637         return;
7638       }
7639       doubleClick = FALSE;
7640       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7641         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7642       }
7643       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7644       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7645          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7646          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7647             /* First square */
7648             if (OKToStartUserMove(fromX, fromY)) {
7649                 second = 0;
7650                 ReportClick("lift", x, y);
7651                 MarkTargetSquares(0);
7652                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7653                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7654                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7655                     promoSweep = defaultPromoChoice;
7656                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7657                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7658                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7659                 }
7660                 if (appData.highlightDragging) {
7661                     SetHighlights(fromX, fromY, -1, -1);
7662                 } else {
7663                     ClearHighlights();
7664                 }
7665             } else fromX = fromY = -1;
7666             return;
7667         }
7668     }
7669
7670     /* fromX != -1 */
7671     if (clickType == Press && gameMode != EditPosition) {
7672         ChessSquare fromP;
7673         ChessSquare toP;
7674         int frc;
7675
7676         // ignore off-board to clicks
7677         if(y < 0 || x < 0) return;
7678
7679         /* Check if clicking again on the same color piece */
7680         fromP = boards[currentMove][fromY][fromX];
7681         toP = boards[currentMove][y][x];
7682         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7683         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7684             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7685            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7686              WhitePawn <= toP && toP <= WhiteKing &&
7687              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7688              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7689             (BlackPawn <= fromP && fromP <= BlackKing &&
7690              BlackPawn <= toP && toP <= BlackKing &&
7691              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7692              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7693             /* Clicked again on same color piece -- changed his mind */
7694             second = (x == fromX && y == fromY);
7695             killX = killY = kill2X = kill2Y = -1;
7696             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7697                 second = FALSE; // first double-click rather than scond click
7698                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7699             }
7700             promoDefaultAltered = FALSE;
7701            if(!second) MarkTargetSquares(1);
7702            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7703             if (appData.highlightDragging) {
7704                 SetHighlights(x, y, -1, -1);
7705             } else {
7706                 ClearHighlights();
7707             }
7708             if (OKToStartUserMove(x, y)) {
7709                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7710                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7711                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7712                  gatingPiece = boards[currentMove][fromY][fromX];
7713                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7714                 fromX = x;
7715                 fromY = y; dragging = 1;
7716                 if(!second) ReportClick("lift", x, y);
7717                 MarkTargetSquares(0);
7718                 DragPieceBegin(xPix, yPix, FALSE);
7719                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7720                     promoSweep = defaultPromoChoice;
7721                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7722                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7723                 }
7724             }
7725            }
7726            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7727            second = FALSE;
7728         }
7729         // ignore clicks on holdings
7730         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7731     }
7732
7733     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7734         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7735         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7736         return;
7737     }
7738
7739     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7740         DragPieceEnd(xPix, yPix); dragging = 0;
7741         if(clearFlag) {
7742             // a deferred attempt to click-click move an empty square on top of a piece
7743             boards[currentMove][y][x] = EmptySquare;
7744             ClearHighlights();
7745             DrawPosition(FALSE, boards[currentMove]);
7746             fromX = fromY = -1; clearFlag = 0;
7747             return;
7748         }
7749         if (appData.animateDragging) {
7750             /* Undo animation damage if any */
7751             DrawPosition(FALSE, NULL);
7752         }
7753         if (second) {
7754             /* Second up/down in same square; just abort move */
7755             second = 0;
7756             fromX = fromY = -1;
7757             gatingPiece = EmptySquare;
7758             ClearHighlights();
7759             gotPremove = 0;
7760             ClearPremoveHighlights();
7761             MarkTargetSquares(-1);
7762             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7763         } else {
7764             /* First upclick in same square; start click-click mode */
7765             SetHighlights(x, y, -1, -1);
7766         }
7767         return;
7768     }
7769
7770     clearFlag = 0;
7771
7772     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7773        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7774         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7775         DisplayMessage(_("only marked squares are legal"),"");
7776         DrawPosition(TRUE, NULL);
7777         return; // ignore to-click
7778     }
7779
7780     /* we now have a different from- and (possibly off-board) to-square */
7781     /* Completed move */
7782     if(!sweepSelecting) {
7783         toX = x;
7784         toY = y;
7785     }
7786
7787     piece = boards[currentMove][fromY][fromX];
7788
7789     saveAnimate = appData.animate;
7790     if (clickType == Press) {
7791         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7792         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7793             // must be Edit Position mode with empty-square selected
7794             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7795             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7796             return;
7797         }
7798         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7799             return;
7800         }
7801         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7802             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7803         } else
7804         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7805         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7806           if(appData.sweepSelect) {
7807             promoSweep = defaultPromoChoice;
7808             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7809             selectFlag = 0; lastX = xPix; lastY = yPix;
7810             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7811             saveFlash = appData.flashCount; appData.flashCount = 0;
7812             Sweep(0); // Pawn that is going to promote: preview promotion piece
7813             sweepSelecting = 1;
7814             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7815             MarkTargetSquares(1);
7816           }
7817           return; // promo popup appears on up-click
7818         }
7819         /* Finish clickclick move */
7820         if (appData.animate || appData.highlightLastMove) {
7821             SetHighlights(fromX, fromY, toX, toY);
7822         } else {
7823             ClearHighlights();
7824         }
7825         MarkTargetSquares(1);
7826     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7827         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7828         *promoRestrict = 0; appData.flashCount = saveFlash;
7829         if (appData.animate || appData.highlightLastMove) {
7830             SetHighlights(fromX, fromY, toX, toY);
7831         } else {
7832             ClearHighlights();
7833         }
7834         MarkTargetSquares(1);
7835     } else {
7836 #if 0
7837 // [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
7838         /* Finish drag move */
7839         if (appData.highlightLastMove) {
7840             SetHighlights(fromX, fromY, toX, toY);
7841         } else {
7842             ClearHighlights();
7843         }
7844 #endif
7845         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7846           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7847         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7848         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7849           dragging *= 2;            // flag button-less dragging if we are dragging
7850           MarkTargetSquares(1);
7851           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7852           else {
7853             kill2X = killX; kill2Y = killY;
7854             killX = x; killY = y;     // remember this square as intermediate
7855             ReportClick("put", x, y); // and inform engine
7856             ReportClick("lift", x, y);
7857             MarkTargetSquares(0);
7858             return;
7859           }
7860         }
7861         DragPieceEnd(xPix, yPix); dragging = 0;
7862         /* Don't animate move and drag both */
7863         appData.animate = FALSE;
7864         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7865     }
7866
7867     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7868     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7869         ChessSquare piece = boards[currentMove][fromY][fromX];
7870         if(gameMode == EditPosition && piece != EmptySquare &&
7871            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7872             int n;
7873
7874             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7875                 n = PieceToNumber(piece - (int)BlackPawn);
7876                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7877                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7878                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7879             } else
7880             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7881                 n = PieceToNumber(piece);
7882                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7883                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7884                 boards[currentMove][n][BOARD_WIDTH-2]++;
7885             }
7886             boards[currentMove][fromY][fromX] = EmptySquare;
7887         }
7888         ClearHighlights();
7889         fromX = fromY = -1;
7890         MarkTargetSquares(1);
7891         DrawPosition(TRUE, boards[currentMove]);
7892         return;
7893     }
7894
7895     // off-board moves should not be highlighted
7896     if(x < 0 || y < 0) ClearHighlights();
7897     else ReportClick("put", x, y);
7898
7899     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7900
7901     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7902
7903     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7904         SetHighlights(fromX, fromY, toX, toY);
7905         MarkTargetSquares(1);
7906         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7907             // [HGM] super: promotion to captured piece selected from holdings
7908             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7909             promotionChoice = TRUE;
7910             // kludge follows to temporarily execute move on display, without promoting yet
7911             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7912             boards[currentMove][toY][toX] = p;
7913             DrawPosition(FALSE, boards[currentMove]);
7914             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7915             boards[currentMove][toY][toX] = q;
7916             DisplayMessage("Click in holdings to choose piece", "");
7917             return;
7918         }
7919         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7920         PromotionPopUp(promoChoice);
7921     } else {
7922         int oldMove = currentMove;
7923         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7924         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7925         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7926         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7927         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7928            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7929             DrawPosition(TRUE, boards[currentMove]);
7930         fromX = fromY = -1;
7931         flashing = 0;
7932     }
7933     appData.animate = saveAnimate;
7934     if (appData.animate || appData.animateDragging) {
7935         /* Undo animation damage if needed */
7936 //      DrawPosition(FALSE, NULL);
7937     }
7938 }
7939
7940 int
7941 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7942 {   // front-end-free part taken out of PieceMenuPopup
7943     int whichMenu; int xSqr, ySqr;
7944
7945     if(seekGraphUp) { // [HGM] seekgraph
7946         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7947         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7948         return -2;
7949     }
7950
7951     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7952          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7953         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7954         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7955         if(action == Press)   {
7956             originalFlip = flipView;
7957             flipView = !flipView; // temporarily flip board to see game from partners perspective
7958             DrawPosition(TRUE, partnerBoard);
7959             DisplayMessage(partnerStatus, "");
7960             partnerUp = TRUE;
7961         } else if(action == Release) {
7962             flipView = originalFlip;
7963             DrawPosition(TRUE, boards[currentMove]);
7964             partnerUp = FALSE;
7965         }
7966         return -2;
7967     }
7968
7969     xSqr = EventToSquare(x, BOARD_WIDTH);
7970     ySqr = EventToSquare(y, BOARD_HEIGHT);
7971     if (action == Release) {
7972         if(pieceSweep != EmptySquare) {
7973             EditPositionMenuEvent(pieceSweep, toX, toY);
7974             pieceSweep = EmptySquare;
7975         } else UnLoadPV(); // [HGM] pv
7976     }
7977     if (action != Press) return -2; // return code to be ignored
7978     switch (gameMode) {
7979       case IcsExamining:
7980         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7981       case EditPosition:
7982         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7983         if (xSqr < 0 || ySqr < 0) return -1;
7984         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7985         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7986         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7987         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7988         NextPiece(0);
7989         return 2; // grab
7990       case IcsObserving:
7991         if(!appData.icsEngineAnalyze) return -1;
7992       case IcsPlayingWhite:
7993       case IcsPlayingBlack:
7994         if(!appData.zippyPlay) goto noZip;
7995       case AnalyzeMode:
7996       case AnalyzeFile:
7997       case MachinePlaysWhite:
7998       case MachinePlaysBlack:
7999       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8000         if (!appData.dropMenu) {
8001           LoadPV(x, y);
8002           return 2; // flag front-end to grab mouse events
8003         }
8004         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8005            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8006       case EditGame:
8007       noZip:
8008         if (xSqr < 0 || ySqr < 0) return -1;
8009         if (!appData.dropMenu || appData.testLegality &&
8010             gameInfo.variant != VariantBughouse &&
8011             gameInfo.variant != VariantCrazyhouse) return -1;
8012         whichMenu = 1; // drop menu
8013         break;
8014       default:
8015         return -1;
8016     }
8017
8018     if (((*fromX = xSqr) < 0) ||
8019         ((*fromY = ySqr) < 0)) {
8020         *fromX = *fromY = -1;
8021         return -1;
8022     }
8023     if (flipView)
8024       *fromX = BOARD_WIDTH - 1 - *fromX;
8025     else
8026       *fromY = BOARD_HEIGHT - 1 - *fromY;
8027
8028     return whichMenu;
8029 }
8030
8031 void
8032 Wheel (int dir, int x, int y)
8033 {
8034     if(gameMode == EditPosition) {
8035         int xSqr = EventToSquare(x, BOARD_WIDTH);
8036         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8037         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8038         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8039         do {
8040             boards[currentMove][ySqr][xSqr] += dir;
8041             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8042             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8043         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8044         DrawPosition(FALSE, boards[currentMove]);
8045     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8046 }
8047
8048 void
8049 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8050 {
8051 //    char * hint = lastHint;
8052     FrontEndProgramStats stats;
8053
8054     stats.which = cps == &first ? 0 : 1;
8055     stats.depth = cpstats->depth;
8056     stats.nodes = cpstats->nodes;
8057     stats.score = cpstats->score;
8058     stats.time = cpstats->time;
8059     stats.pv = cpstats->movelist;
8060     stats.hint = lastHint;
8061     stats.an_move_index = 0;
8062     stats.an_move_count = 0;
8063
8064     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8065         stats.hint = cpstats->move_name;
8066         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8067         stats.an_move_count = cpstats->nr_moves;
8068     }
8069
8070     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
8071
8072     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8073         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8074
8075     SetProgramStats( &stats );
8076 }
8077
8078 void
8079 ClearEngineOutputPane (int which)
8080 {
8081     static FrontEndProgramStats dummyStats;
8082     dummyStats.which = which;
8083     dummyStats.pv = "#";
8084     SetProgramStats( &dummyStats );
8085 }
8086
8087 #define MAXPLAYERS 500
8088
8089 char *
8090 TourneyStandings (int display)
8091 {
8092     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8093     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8094     char result, *p, *names[MAXPLAYERS];
8095
8096     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8097         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8098     names[0] = p = strdup(appData.participants);
8099     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8100
8101     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8102
8103     while(result = appData.results[nr]) {
8104         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8105         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8106         wScore = bScore = 0;
8107         switch(result) {
8108           case '+': wScore = 2; break;
8109           case '-': bScore = 2; break;
8110           case '=': wScore = bScore = 1; break;
8111           case ' ':
8112           case '*': return strdup("busy"); // tourney not finished
8113         }
8114         score[w] += wScore;
8115         score[b] += bScore;
8116         games[w]++;
8117         games[b]++;
8118         nr++;
8119     }
8120     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8121     for(w=0; w<nPlayers; w++) {
8122         bScore = -1;
8123         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8124         ranking[w] = b; points[w] = bScore; score[b] = -2;
8125     }
8126     p = malloc(nPlayers*34+1);
8127     for(w=0; w<nPlayers && w<display; w++)
8128         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8129     free(names[0]);
8130     return p;
8131 }
8132
8133 void
8134 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8135 {       // count all piece types
8136         int p, f, r;
8137         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8138         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8139         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8140                 p = board[r][f];
8141                 pCnt[p]++;
8142                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8143                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8144                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8145                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8146                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8147                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8148         }
8149 }
8150
8151 int
8152 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8153 {
8154         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8155         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8156
8157         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8158         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8159         if(myPawns == 2 && nMine == 3) // KPP
8160             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8161         if(myPawns == 1 && nMine == 2) // KP
8162             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8163         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8164             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8165         if(myPawns) return FALSE;
8166         if(pCnt[WhiteRook+side])
8167             return pCnt[BlackRook-side] ||
8168                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8169                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8170                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8171         if(pCnt[WhiteCannon+side]) {
8172             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8173             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8174         }
8175         if(pCnt[WhiteKnight+side])
8176             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8177         return FALSE;
8178 }
8179
8180 int
8181 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8182 {
8183         VariantClass v = gameInfo.variant;
8184
8185         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8186         if(v == VariantShatranj) return TRUE; // always winnable through baring
8187         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8188         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8189
8190         if(v == VariantXiangqi) {
8191                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8192
8193                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8194                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8195                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8196                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8197                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8198                 if(stale) // we have at least one last-rank P plus perhaps C
8199                     return majors // KPKX
8200                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8201                 else // KCA*E*
8202                     return pCnt[WhiteFerz+side] // KCAK
8203                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8204                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8205                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8206
8207         } else if(v == VariantKnightmate) {
8208                 if(nMine == 1) return FALSE;
8209                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8210         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8211                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8212
8213                 if(nMine == 1) return FALSE; // bare King
8214                 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
8215                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8216                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8217                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8218                 if(pCnt[WhiteKnight+side])
8219                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8220                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8221                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8222                 if(nBishops)
8223                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8224                 if(pCnt[WhiteAlfil+side])
8225                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8226                 if(pCnt[WhiteWazir+side])
8227                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8228         }
8229
8230         return TRUE;
8231 }
8232
8233 int
8234 CompareWithRights (Board b1, Board b2)
8235 {
8236     int rights = 0;
8237     if(!CompareBoards(b1, b2)) return FALSE;
8238     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8239     /* compare castling rights */
8240     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8241            rights++; /* King lost rights, while rook still had them */
8242     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8243         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8244            rights++; /* but at least one rook lost them */
8245     }
8246     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8247            rights++;
8248     if( b1[CASTLING][5] != NoRights ) {
8249         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8250            rights++;
8251     }
8252     return rights == 0;
8253 }
8254
8255 int
8256 Adjudicate (ChessProgramState *cps)
8257 {       // [HGM] some adjudications useful with buggy engines
8258         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8259         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8260         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8261         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8262         int k, drop, count = 0; static int bare = 1;
8263         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8264         Boolean canAdjudicate = !appData.icsActive;
8265
8266         // most tests only when we understand the game, i.e. legality-checking on
8267             if( appData.testLegality )
8268             {   /* [HGM] Some more adjudications for obstinate engines */
8269                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8270                 static int moveCount = 6;
8271                 ChessMove result;
8272                 char *reason = NULL;
8273
8274                 /* Count what is on board. */
8275                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8276
8277                 /* Some material-based adjudications that have to be made before stalemate test */
8278                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8279                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8280                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8281                      if(canAdjudicate && appData.checkMates) {
8282                          if(engineOpponent)
8283                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8284                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8285                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8286                          return 1;
8287                      }
8288                 }
8289
8290                 /* Bare King in Shatranj (loses) or Losers (wins) */
8291                 if( nrW == 1 || nrB == 1) {
8292                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8293                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8294                      if(canAdjudicate && appData.checkMates) {
8295                          if(engineOpponent)
8296                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8297                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8298                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8299                          return 1;
8300                      }
8301                   } else
8302                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8303                   {    /* bare King */
8304                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8305                         if(canAdjudicate && appData.checkMates) {
8306                             /* but only adjudicate if adjudication enabled */
8307                             if(engineOpponent)
8308                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8309                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8310                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8311                             return 1;
8312                         }
8313                   }
8314                 } else bare = 1;
8315
8316
8317             // don't wait for engine to announce game end if we can judge ourselves
8318             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8319               case MT_CHECK:
8320                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8321                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8322                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8323                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8324                             checkCnt++;
8325                         if(checkCnt >= 2) {
8326                             reason = "Xboard adjudication: 3rd check";
8327                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8328                             break;
8329                         }
8330                     }
8331                 }
8332               case MT_NONE:
8333               default:
8334                 break;
8335               case MT_STEALMATE:
8336               case MT_STALEMATE:
8337               case MT_STAINMATE:
8338                 reason = "Xboard adjudication: Stalemate";
8339                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8340                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8341                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8342                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8343                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8344                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8345                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8346                                                                         EP_CHECKMATE : EP_WINS);
8347                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8348                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8349                 }
8350                 break;
8351               case MT_CHECKMATE:
8352                 reason = "Xboard adjudication: Checkmate";
8353                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8354                 if(gameInfo.variant == VariantShogi) {
8355                     if(forwardMostMove > backwardMostMove
8356                        && moveList[forwardMostMove-1][1] == '@'
8357                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8358                         reason = "XBoard adjudication: pawn-drop mate";
8359                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8360                     }
8361                 }
8362                 break;
8363             }
8364
8365                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8366                     case EP_STALEMATE:
8367                         result = GameIsDrawn; break;
8368                     case EP_CHECKMATE:
8369                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8370                     case EP_WINS:
8371                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8372                     default:
8373                         result = EndOfFile;
8374                 }
8375                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8376                     if(engineOpponent)
8377                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8378                     GameEnds( result, reason, GE_XBOARD );
8379                     return 1;
8380                 }
8381
8382                 /* Next absolutely insufficient mating material. */
8383                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8384                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8385                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8386
8387                      /* always flag draws, for judging claims */
8388                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8389
8390                      if(canAdjudicate && appData.materialDraws) {
8391                          /* but only adjudicate them if adjudication enabled */
8392                          if(engineOpponent) {
8393                            SendToProgram("force\n", engineOpponent); // suppress reply
8394                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8395                          }
8396                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8397                          return 1;
8398                      }
8399                 }
8400
8401                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8402                 if(gameInfo.variant == VariantXiangqi ?
8403                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8404                  : nrW + nrB == 4 &&
8405                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8406                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8407                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8408                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8409                    ) ) {
8410                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8411                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8412                           if(engineOpponent) {
8413                             SendToProgram("force\n", engineOpponent); // suppress reply
8414                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8415                           }
8416                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8417                           return 1;
8418                      }
8419                 } else moveCount = 6;
8420             }
8421
8422         // Repetition draws and 50-move rule can be applied independently of legality testing
8423
8424                 /* Check for rep-draws */
8425                 count = 0;
8426                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8427                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8428                 for(k = forwardMostMove-2;
8429                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8430                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8431                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8432                     k-=2)
8433                 {   int rights=0;
8434                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8435                         /* compare castling rights */
8436                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8437                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8438                                 rights++; /* King lost rights, while rook still had them */
8439                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8440                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8441                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8442                                    rights++; /* but at least one rook lost them */
8443                         }
8444                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8445                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8446                                 rights++;
8447                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8448                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8449                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8450                                    rights++;
8451                         }
8452                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8453                             && appData.drawRepeats > 1) {
8454                              /* adjudicate after user-specified nr of repeats */
8455                              int result = GameIsDrawn;
8456                              char *details = "XBoard adjudication: repetition draw";
8457                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8458                                 // [HGM] xiangqi: check for forbidden perpetuals
8459                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8460                                 for(m=forwardMostMove; m>k; m-=2) {
8461                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8462                                         ourPerpetual = 0; // the current mover did not always check
8463                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8464                                         hisPerpetual = 0; // the opponent did not always check
8465                                 }
8466                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8467                                                                         ourPerpetual, hisPerpetual);
8468                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8469                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8470                                     details = "Xboard adjudication: perpetual checking";
8471                                 } else
8472                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8473                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8474                                 } else
8475                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8476                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8477                                         result = BlackWins;
8478                                         details = "Xboard adjudication: repetition";
8479                                     }
8480                                 } else // it must be XQ
8481                                 // Now check for perpetual chases
8482                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8483                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8484                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8485                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8486                                         static char resdet[MSG_SIZ];
8487                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8488                                         details = resdet;
8489                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8490                                     } else
8491                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8492                                         break; // Abort repetition-checking loop.
8493                                 }
8494                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8495                              }
8496                              if(engineOpponent) {
8497                                SendToProgram("force\n", engineOpponent); // suppress reply
8498                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8499                              }
8500                              GameEnds( result, details, GE_XBOARD );
8501                              return 1;
8502                         }
8503                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8504                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8505                     }
8506                 }
8507
8508                 /* Now we test for 50-move draws. Determine ply count */
8509                 count = forwardMostMove;
8510                 /* look for last irreversble move */
8511                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8512                     count--;
8513                 /* if we hit starting position, add initial plies */
8514                 if( count == backwardMostMove )
8515                     count -= initialRulePlies;
8516                 count = forwardMostMove - count;
8517                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8518                         // adjust reversible move counter for checks in Xiangqi
8519                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8520                         if(i < backwardMostMove) i = backwardMostMove;
8521                         while(i <= forwardMostMove) {
8522                                 lastCheck = inCheck; // check evasion does not count
8523                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8524                                 if(inCheck || lastCheck) count--; // check does not count
8525                                 i++;
8526                         }
8527                 }
8528                 if( count >= 100)
8529                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8530                          /* this is used to judge if draw claims are legal */
8531                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8532                          if(engineOpponent) {
8533                            SendToProgram("force\n", engineOpponent); // suppress reply
8534                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8535                          }
8536                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8537                          return 1;
8538                 }
8539
8540                 /* if draw offer is pending, treat it as a draw claim
8541                  * when draw condition present, to allow engines a way to
8542                  * claim draws before making their move to avoid a race
8543                  * condition occurring after their move
8544                  */
8545                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8546                          char *p = NULL;
8547                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8548                              p = "Draw claim: 50-move rule";
8549                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8550                              p = "Draw claim: 3-fold repetition";
8551                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8552                              p = "Draw claim: insufficient mating material";
8553                          if( p != NULL && canAdjudicate) {
8554                              if(engineOpponent) {
8555                                SendToProgram("force\n", engineOpponent); // suppress reply
8556                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8557                              }
8558                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8559                              return 1;
8560                          }
8561                 }
8562
8563                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8564                     if(engineOpponent) {
8565                       SendToProgram("force\n", engineOpponent); // suppress reply
8566                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8567                     }
8568                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8569                     return 1;
8570                 }
8571         return 0;
8572 }
8573
8574 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8575 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8576 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8577
8578 static int
8579 BitbaseProbe ()
8580 {
8581     int pieces[10], squares[10], cnt=0, r, f, res;
8582     static int loaded;
8583     static PPROBE_EGBB probeBB;
8584     if(!appData.testLegality) return 10;
8585     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8586     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8587     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8588     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8589         ChessSquare piece = boards[forwardMostMove][r][f];
8590         int black = (piece >= BlackPawn);
8591         int type = piece - black*BlackPawn;
8592         if(piece == EmptySquare) continue;
8593         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8594         if(type == WhiteKing) type = WhiteQueen + 1;
8595         type = egbbCode[type];
8596         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8597         pieces[cnt] = type + black*6;
8598         if(++cnt > 5) return 11;
8599     }
8600     pieces[cnt] = squares[cnt] = 0;
8601     // probe EGBB
8602     if(loaded == 2) return 13; // loading failed before
8603     if(loaded == 0) {
8604         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8605         HMODULE lib;
8606         PLOAD_EGBB loadBB;
8607         loaded = 2; // prepare for failure
8608         if(!path) return 13; // no egbb installed
8609         strncpy(buf, path + 8, MSG_SIZ);
8610         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8611         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8612         lib = LoadLibrary(buf);
8613         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8614         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8615         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8616         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8617         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8618         loaded = 1; // success!
8619     }
8620     res = probeBB(forwardMostMove & 1, pieces, squares);
8621     return res > 0 ? 1 : res < 0 ? -1 : 0;
8622 }
8623
8624 char *
8625 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8626 {   // [HGM] book: this routine intercepts moves to simulate book replies
8627     char *bookHit = NULL;
8628
8629     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8630         char buf[MSG_SIZ];
8631         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8632         SendToProgram(buf, cps);
8633     }
8634     //first determine if the incoming move brings opponent into his book
8635     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8636         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8637     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8638     if(bookHit != NULL && !cps->bookSuspend) {
8639         // make sure opponent is not going to reply after receiving move to book position
8640         SendToProgram("force\n", cps);
8641         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8642     }
8643     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8644     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8645     // now arrange restart after book miss
8646     if(bookHit) {
8647         // after a book hit we never send 'go', and the code after the call to this routine
8648         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8649         char buf[MSG_SIZ], *move = bookHit;
8650         if(cps->useSAN) {
8651             int fromX, fromY, toX, toY;
8652             char promoChar;
8653             ChessMove moveType;
8654             move = buf + 30;
8655             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8656                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8657                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8658                                     PosFlags(forwardMostMove),
8659                                     fromY, fromX, toY, toX, promoChar, move);
8660             } else {
8661                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8662                 bookHit = NULL;
8663             }
8664         }
8665         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8666         SendToProgram(buf, cps);
8667         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8668     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8669         SendToProgram("go\n", cps);
8670         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8671     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8672         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8673             SendToProgram("go\n", cps);
8674         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8675     }
8676     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8677 }
8678
8679 int
8680 LoadError (char *errmess, ChessProgramState *cps)
8681 {   // unloads engine and switches back to -ncp mode if it was first
8682     if(cps->initDone) return FALSE;
8683     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8684     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8685     cps->pr = NoProc;
8686     if(cps == &first) {
8687         appData.noChessProgram = TRUE;
8688         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8689         gameMode = BeginningOfGame; ModeHighlight();
8690         SetNCPMode();
8691     }
8692     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8693     DisplayMessage("", ""); // erase waiting message
8694     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8695     return TRUE;
8696 }
8697
8698 char *savedMessage;
8699 ChessProgramState *savedState;
8700 void
8701 DeferredBookMove (void)
8702 {
8703         if(savedState->lastPing != savedState->lastPong)
8704                     ScheduleDelayedEvent(DeferredBookMove, 10);
8705         else
8706         HandleMachineMove(savedMessage, savedState);
8707 }
8708
8709 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8710 static ChessProgramState *stalledEngine;
8711 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8712
8713 void
8714 HandleMachineMove (char *message, ChessProgramState *cps)
8715 {
8716     static char firstLeg[20], legs;
8717     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8718     char realname[MSG_SIZ];
8719     int fromX, fromY, toX, toY;
8720     ChessMove moveType;
8721     char promoChar, roar;
8722     char *p, *pv=buf1;
8723     int oldError;
8724     char *bookHit;
8725
8726     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8727         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8728         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8729             DisplayError(_("Invalid pairing from pairing engine"), 0);
8730             return;
8731         }
8732         pairingReceived = 1;
8733         NextMatchGame();
8734         return; // Skim the pairing messages here.
8735     }
8736
8737     oldError = cps->userError; cps->userError = 0;
8738
8739 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8740     /*
8741      * Kludge to ignore BEL characters
8742      */
8743     while (*message == '\007') message++;
8744
8745     /*
8746      * [HGM] engine debug message: ignore lines starting with '#' character
8747      */
8748     if(cps->debug && *message == '#') return;
8749
8750     /*
8751      * Look for book output
8752      */
8753     if (cps == &first && bookRequested) {
8754         if (message[0] == '\t' || message[0] == ' ') {
8755             /* Part of the book output is here; append it */
8756             strcat(bookOutput, message);
8757             strcat(bookOutput, "  \n");
8758             return;
8759         } else if (bookOutput[0] != NULLCHAR) {
8760             /* All of book output has arrived; display it */
8761             char *p = bookOutput;
8762             while (*p != NULLCHAR) {
8763                 if (*p == '\t') *p = ' ';
8764                 p++;
8765             }
8766             DisplayInformation(bookOutput);
8767             bookRequested = FALSE;
8768             /* Fall through to parse the current output */
8769         }
8770     }
8771
8772     /*
8773      * Look for machine move.
8774      */
8775     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8776         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8777     {
8778         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8779             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8780             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8781             stalledEngine = cps;
8782             if(appData.ponderNextMove) { // bring opponent out of ponder
8783                 if(gameMode == TwoMachinesPlay) {
8784                     if(cps->other->pause)
8785                         PauseEngine(cps->other);
8786                     else
8787                         SendToProgram("easy\n", cps->other);
8788                 }
8789             }
8790             StopClocks();
8791             return;
8792         }
8793
8794       if(cps->usePing) {
8795
8796         /* This method is only useful on engines that support ping */
8797         if(abortEngineThink) {
8798             if (appData.debugMode) {
8799                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8800             }
8801             SendToProgram("undo\n", cps);
8802             return;
8803         }
8804
8805         if (cps->lastPing != cps->lastPong) {
8806             /* Extra move from before last new; ignore */
8807             if (appData.debugMode) {
8808                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8809             }
8810           return;
8811         }
8812
8813       } else {
8814
8815         int machineWhite = FALSE;
8816
8817         switch (gameMode) {
8818           case BeginningOfGame:
8819             /* Extra move from before last reset; ignore */
8820             if (appData.debugMode) {
8821                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8822             }
8823             return;
8824
8825           case EndOfGame:
8826           case IcsIdle:
8827           default:
8828             /* Extra move after we tried to stop.  The mode test is
8829                not a reliable way of detecting this problem, but it's
8830                the best we can do on engines that don't support ping.
8831             */
8832             if (appData.debugMode) {
8833                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8834                         cps->which, gameMode);
8835             }
8836             SendToProgram("undo\n", cps);
8837             return;
8838
8839           case MachinePlaysWhite:
8840           case IcsPlayingWhite:
8841             machineWhite = TRUE;
8842             break;
8843
8844           case MachinePlaysBlack:
8845           case IcsPlayingBlack:
8846             machineWhite = FALSE;
8847             break;
8848
8849           case TwoMachinesPlay:
8850             machineWhite = (cps->twoMachinesColor[0] == 'w');
8851             break;
8852         }
8853         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8854             if (appData.debugMode) {
8855                 fprintf(debugFP,
8856                         "Ignoring move out of turn by %s, gameMode %d"
8857                         ", forwardMost %d\n",
8858                         cps->which, gameMode, forwardMostMove);
8859             }
8860             return;
8861         }
8862       }
8863
8864         if(cps->alphaRank) AlphaRank(machineMove, 4);
8865
8866         // [HGM] lion: (some very limited) support for Alien protocol
8867         killX = killY = kill2X = kill2Y = -1;
8868         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8869             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8870             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8871             return;
8872         }
8873         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8874             char *q = strchr(p+1, ',');            // second comma?
8875             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8876             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8877             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8878         }
8879         if(firstLeg[0]) { // there was a previous leg;
8880             // only support case where same piece makes two step
8881             char buf[20], *p = machineMove+1, *q = buf+1, f;
8882             safeStrCpy(buf, machineMove, 20);
8883             while(isdigit(*q)) q++; // find start of to-square
8884             safeStrCpy(machineMove, firstLeg, 20);
8885             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8886             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
8887             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)
8888             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8889             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8890             firstLeg[0] = NULLCHAR; legs = 0;
8891         }
8892
8893         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8894                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8895             /* Machine move could not be parsed; ignore it. */
8896           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8897                     machineMove, _(cps->which));
8898             DisplayMoveError(buf1);
8899             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8900                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8901             if (gameMode == TwoMachinesPlay) {
8902               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8903                        buf1, GE_XBOARD);
8904             }
8905             return;
8906         }
8907
8908         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8909         /* So we have to redo legality test with true e.p. status here,  */
8910         /* to make sure an illegal e.p. capture does not slip through,   */
8911         /* to cause a forfeit on a justified illegal-move complaint      */
8912         /* of the opponent.                                              */
8913         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8914            ChessMove moveType;
8915            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8916                              fromY, fromX, toY, toX, promoChar);
8917             if(moveType == IllegalMove) {
8918               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8919                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8920                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8921                            buf1, GE_XBOARD);
8922                 return;
8923            } else if(!appData.fischerCastling)
8924            /* [HGM] Kludge to handle engines that send FRC-style castling
8925               when they shouldn't (like TSCP-Gothic) */
8926            switch(moveType) {
8927              case WhiteASideCastleFR:
8928              case BlackASideCastleFR:
8929                toX+=2;
8930                currentMoveString[2]++;
8931                break;
8932              case WhiteHSideCastleFR:
8933              case BlackHSideCastleFR:
8934                toX--;
8935                currentMoveString[2]--;
8936                break;
8937              default: ; // nothing to do, but suppresses warning of pedantic compilers
8938            }
8939         }
8940         hintRequested = FALSE;
8941         lastHint[0] = NULLCHAR;
8942         bookRequested = FALSE;
8943         /* Program may be pondering now */
8944         cps->maybeThinking = TRUE;
8945         if (cps->sendTime == 2) cps->sendTime = 1;
8946         if (cps->offeredDraw) cps->offeredDraw--;
8947
8948         /* [AS] Save move info*/
8949         pvInfoList[ forwardMostMove ].score = programStats.score;
8950         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8951         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8952
8953         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8954
8955         /* Test suites abort the 'game' after one move */
8956         if(*appData.finger) {
8957            static FILE *f;
8958            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8959            if(!f) f = fopen(appData.finger, "w");
8960            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8961            else { DisplayFatalError("Bad output file", errno, 0); return; }
8962            free(fen);
8963            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8964         }
8965         if(appData.epd) {
8966            if(solvingTime >= 0) {
8967               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8968               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8969            } else {
8970               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8971               if(solvingTime == -2) second.matchWins++;
8972            }
8973            OutputKibitz(2, buf1);
8974            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8975         }
8976
8977         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8978         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8979             int count = 0;
8980
8981             while( count < adjudicateLossPlies ) {
8982                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8983
8984                 if( count & 1 ) {
8985                     score = -score; /* Flip score for winning side */
8986                 }
8987
8988                 if( score > appData.adjudicateLossThreshold ) {
8989                     break;
8990                 }
8991
8992                 count++;
8993             }
8994
8995             if( count >= adjudicateLossPlies ) {
8996                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8997
8998                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8999                     "Xboard adjudication",
9000                     GE_XBOARD );
9001
9002                 return;
9003             }
9004         }
9005
9006         if(Adjudicate(cps)) {
9007             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9008             return; // [HGM] adjudicate: for all automatic game ends
9009         }
9010
9011 #if ZIPPY
9012         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9013             first.initDone) {
9014           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9015                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9016                 SendToICS("draw ");
9017                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9018           }
9019           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9020           ics_user_moved = 1;
9021           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9022                 char buf[3*MSG_SIZ];
9023
9024                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9025                         programStats.score / 100.,
9026                         programStats.depth,
9027                         programStats.time / 100.,
9028                         (unsigned int)programStats.nodes,
9029                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9030                         programStats.movelist);
9031                 SendToICS(buf);
9032           }
9033         }
9034 #endif
9035
9036         /* [AS] Clear stats for next move */
9037         ClearProgramStats();
9038         thinkOutput[0] = NULLCHAR;
9039         hiddenThinkOutputState = 0;
9040
9041         bookHit = NULL;
9042         if (gameMode == TwoMachinesPlay) {
9043             /* [HGM] relaying draw offers moved to after reception of move */
9044             /* and interpreting offer as claim if it brings draw condition */
9045             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9046                 SendToProgram("draw\n", cps->other);
9047             }
9048             if (cps->other->sendTime) {
9049                 SendTimeRemaining(cps->other,
9050                                   cps->other->twoMachinesColor[0] == 'w');
9051             }
9052             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9053             if (firstMove && !bookHit) {
9054                 firstMove = FALSE;
9055                 if (cps->other->useColors) {
9056                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9057                 }
9058                 SendToProgram("go\n", cps->other);
9059             }
9060             cps->other->maybeThinking = TRUE;
9061         }
9062
9063         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9064
9065         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9066
9067         if (!pausing && appData.ringBellAfterMoves) {
9068             if(!roar) RingBell();
9069         }
9070
9071         /*
9072          * Reenable menu items that were disabled while
9073          * machine was thinking
9074          */
9075         if (gameMode != TwoMachinesPlay)
9076             SetUserThinkingEnables();
9077
9078         // [HGM] book: after book hit opponent has received move and is now in force mode
9079         // force the book reply into it, and then fake that it outputted this move by jumping
9080         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9081         if(bookHit) {
9082                 static char bookMove[MSG_SIZ]; // a bit generous?
9083
9084                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9085                 strcat(bookMove, bookHit);
9086                 message = bookMove;
9087                 cps = cps->other;
9088                 programStats.nodes = programStats.depth = programStats.time =
9089                 programStats.score = programStats.got_only_move = 0;
9090                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9091
9092                 if(cps->lastPing != cps->lastPong) {
9093                     savedMessage = message; // args for deferred call
9094                     savedState = cps;
9095                     ScheduleDelayedEvent(DeferredBookMove, 10);
9096                     return;
9097                 }
9098                 goto FakeBookMove;
9099         }
9100
9101         return;
9102     }
9103
9104     /* Set special modes for chess engines.  Later something general
9105      *  could be added here; for now there is just one kludge feature,
9106      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9107      *  when "xboard" is given as an interactive command.
9108      */
9109     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9110         cps->useSigint = FALSE;
9111         cps->useSigterm = FALSE;
9112     }
9113     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9114       ParseFeatures(message+8, cps);
9115       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9116     }
9117
9118     if (!strncmp(message, "setup ", 6) && 
9119         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9120           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9121                                         ) { // [HGM] allow first engine to define opening position
9122       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9123       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9124       *buf = NULLCHAR;
9125       if(sscanf(message, "setup (%s", buf) == 1) {
9126         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9127         ASSIGN(appData.pieceToCharTable, buf);
9128       }
9129       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9130       if(dummy >= 3) {
9131         while(message[s] && message[s++] != ' ');
9132         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9133            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9134             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9135             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9136             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9137           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9138           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9139           startedFromSetupPosition = FALSE;
9140         }
9141       }
9142       if(startedFromSetupPosition) return;
9143       ParseFEN(boards[0], &dummy, message+s, FALSE);
9144       DrawPosition(TRUE, boards[0]);
9145       CopyBoard(initialPosition, boards[0]);
9146       startedFromSetupPosition = TRUE;
9147       return;
9148     }
9149     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9150       ChessSquare piece = WhitePawn;
9151       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9152       if(*p == '+') promoted++, ID = *++p;
9153       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9154       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9155       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9156       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9157       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9158       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9159       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9160       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9161                                                && gameInfo.variant != VariantGreat
9162                                                && gameInfo.variant != VariantFairy    ) return;
9163       if(piece < EmptySquare) {
9164         pieceDefs = TRUE;
9165         ASSIGN(pieceDesc[piece], buf1);
9166         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9167       }
9168       return;
9169     }
9170     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9171       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9172       Sweep(0);
9173       return;
9174     }
9175     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9176      * want this, I was asked to put it in, and obliged.
9177      */
9178     if (!strncmp(message, "setboard ", 9)) {
9179         Board initial_position;
9180
9181         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9182
9183         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9184             DisplayError(_("Bad FEN received from engine"), 0);
9185             return ;
9186         } else {
9187            Reset(TRUE, FALSE);
9188            CopyBoard(boards[0], initial_position);
9189            initialRulePlies = FENrulePlies;
9190            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9191            else gameMode = MachinePlaysBlack;
9192            DrawPosition(FALSE, boards[currentMove]);
9193         }
9194         return;
9195     }
9196
9197     /*
9198      * Look for communication commands
9199      */
9200     if (!strncmp(message, "telluser ", 9)) {
9201         if(message[9] == '\\' && message[10] == '\\')
9202             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9203         PlayTellSound();
9204         DisplayNote(message + 9);
9205         return;
9206     }
9207     if (!strncmp(message, "tellusererror ", 14)) {
9208         cps->userError = 1;
9209         if(message[14] == '\\' && message[15] == '\\')
9210             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9211         PlayTellSound();
9212         DisplayError(message + 14, 0);
9213         return;
9214     }
9215     if (!strncmp(message, "tellopponent ", 13)) {
9216       if (appData.icsActive) {
9217         if (loggedOn) {
9218           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9219           SendToICS(buf1);
9220         }
9221       } else {
9222         DisplayNote(message + 13);
9223       }
9224       return;
9225     }
9226     if (!strncmp(message, "tellothers ", 11)) {
9227       if (appData.icsActive) {
9228         if (loggedOn) {
9229           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9230           SendToICS(buf1);
9231         }
9232       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9233       return;
9234     }
9235     if (!strncmp(message, "tellall ", 8)) {
9236       if (appData.icsActive) {
9237         if (loggedOn) {
9238           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9239           SendToICS(buf1);
9240         }
9241       } else {
9242         DisplayNote(message + 8);
9243       }
9244       return;
9245     }
9246     if (strncmp(message, "warning", 7) == 0) {
9247         /* Undocumented feature, use tellusererror in new code */
9248         DisplayError(message, 0);
9249         return;
9250     }
9251     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9252         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9253         strcat(realname, " query");
9254         AskQuestion(realname, buf2, buf1, cps->pr);
9255         return;
9256     }
9257     /* Commands from the engine directly to ICS.  We don't allow these to be
9258      *  sent until we are logged on. Crafty kibitzes have been known to
9259      *  interfere with the login process.
9260      */
9261     if (loggedOn) {
9262         if (!strncmp(message, "tellics ", 8)) {
9263             SendToICS(message + 8);
9264             SendToICS("\n");
9265             return;
9266         }
9267         if (!strncmp(message, "tellicsnoalias ", 15)) {
9268             SendToICS(ics_prefix);
9269             SendToICS(message + 15);
9270             SendToICS("\n");
9271             return;
9272         }
9273         /* The following are for backward compatibility only */
9274         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9275             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9276             SendToICS(ics_prefix);
9277             SendToICS(message);
9278             SendToICS("\n");
9279             return;
9280         }
9281     }
9282     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9283         if(initPing == cps->lastPong) {
9284             if(gameInfo.variant == VariantUnknown) {
9285                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9286                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9287                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9288             }
9289             initPing = -1;
9290         }
9291         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9292             abortEngineThink = FALSE;
9293             DisplayMessage("", "");
9294             ThawUI();
9295         }
9296         return;
9297     }
9298     if(!strncmp(message, "highlight ", 10)) {
9299         if(appData.testLegality && !*engineVariant && appData.markers) return;
9300         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9301         return;
9302     }
9303     if(!strncmp(message, "click ", 6)) {
9304         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9305         if(appData.testLegality || !appData.oneClick) return;
9306         sscanf(message+6, "%c%d%c", &f, &y, &c);
9307         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9308         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9309         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9310         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9311         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9312         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9313             LeftClick(Release, lastLeftX, lastLeftY);
9314         controlKey  = (c == ',');
9315         LeftClick(Press, x, y);
9316         LeftClick(Release, x, y);
9317         first.highlight = f;
9318         return;
9319     }
9320     /*
9321      * If the move is illegal, cancel it and redraw the board.
9322      * Also deal with other error cases.  Matching is rather loose
9323      * here to accommodate engines written before the spec.
9324      */
9325     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9326         strncmp(message, "Error", 5) == 0) {
9327         if (StrStr(message, "name") ||
9328             StrStr(message, "rating") || StrStr(message, "?") ||
9329             StrStr(message, "result") || StrStr(message, "board") ||
9330             StrStr(message, "bk") || StrStr(message, "computer") ||
9331             StrStr(message, "variant") || StrStr(message, "hint") ||
9332             StrStr(message, "random") || StrStr(message, "depth") ||
9333             StrStr(message, "accepted")) {
9334             return;
9335         }
9336         if (StrStr(message, "protover")) {
9337           /* Program is responding to input, so it's apparently done
9338              initializing, and this error message indicates it is
9339              protocol version 1.  So we don't need to wait any longer
9340              for it to initialize and send feature commands. */
9341           FeatureDone(cps, 1);
9342           cps->protocolVersion = 1;
9343           return;
9344         }
9345         cps->maybeThinking = FALSE;
9346
9347         if (StrStr(message, "draw")) {
9348             /* Program doesn't have "draw" command */
9349             cps->sendDrawOffers = 0;
9350             return;
9351         }
9352         if (cps->sendTime != 1 &&
9353             (StrStr(message, "time") || StrStr(message, "otim"))) {
9354           /* Program apparently doesn't have "time" or "otim" command */
9355           cps->sendTime = 0;
9356           return;
9357         }
9358         if (StrStr(message, "analyze")) {
9359             cps->analysisSupport = FALSE;
9360             cps->analyzing = FALSE;
9361 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9362             EditGameEvent(); // [HGM] try to preserve loaded game
9363             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9364             DisplayError(buf2, 0);
9365             return;
9366         }
9367         if (StrStr(message, "(no matching move)st")) {
9368           /* Special kludge for GNU Chess 4 only */
9369           cps->stKludge = TRUE;
9370           SendTimeControl(cps, movesPerSession, timeControl,
9371                           timeIncrement, appData.searchDepth,
9372                           searchTime);
9373           return;
9374         }
9375         if (StrStr(message, "(no matching move)sd")) {
9376           /* Special kludge for GNU Chess 4 only */
9377           cps->sdKludge = TRUE;
9378           SendTimeControl(cps, movesPerSession, timeControl,
9379                           timeIncrement, appData.searchDepth,
9380                           searchTime);
9381           return;
9382         }
9383         if (!StrStr(message, "llegal")) {
9384             return;
9385         }
9386         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9387             gameMode == IcsIdle) return;
9388         if (forwardMostMove <= backwardMostMove) return;
9389         if (pausing) PauseEvent();
9390       if(appData.forceIllegal) {
9391             // [HGM] illegal: machine refused move; force position after move into it
9392           SendToProgram("force\n", cps);
9393           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9394                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9395                 // when black is to move, while there might be nothing on a2 or black
9396                 // might already have the move. So send the board as if white has the move.
9397                 // But first we must change the stm of the engine, as it refused the last move
9398                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9399                 if(WhiteOnMove(forwardMostMove)) {
9400                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9401                     SendBoard(cps, forwardMostMove); // kludgeless board
9402                 } else {
9403                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9404                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9405                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9406                 }
9407           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9408             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9409                  gameMode == TwoMachinesPlay)
9410               SendToProgram("go\n", cps);
9411             return;
9412       } else
9413         if (gameMode == PlayFromGameFile) {
9414             /* Stop reading this game file */
9415             gameMode = EditGame;
9416             ModeHighlight();
9417         }
9418         /* [HGM] illegal-move claim should forfeit game when Xboard */
9419         /* only passes fully legal moves                            */
9420         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9421             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9422                                 "False illegal-move claim", GE_XBOARD );
9423             return; // do not take back move we tested as valid
9424         }
9425         currentMove = forwardMostMove-1;
9426         DisplayMove(currentMove-1); /* before DisplayMoveError */
9427         SwitchClocks(forwardMostMove-1); // [HGM] race
9428         DisplayBothClocks();
9429         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9430                 parseList[currentMove], _(cps->which));
9431         DisplayMoveError(buf1);
9432         DrawPosition(FALSE, boards[currentMove]);
9433
9434         SetUserThinkingEnables();
9435         return;
9436     }
9437     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9438         /* Program has a broken "time" command that
9439            outputs a string not ending in newline.
9440            Don't use it. */
9441         cps->sendTime = 0;
9442     }
9443     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9444         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9445             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9446     }
9447
9448     /*
9449      * If chess program startup fails, exit with an error message.
9450      * Attempts to recover here are futile. [HGM] Well, we try anyway
9451      */
9452     if ((StrStr(message, "unknown host") != NULL)
9453         || (StrStr(message, "No remote directory") != NULL)
9454         || (StrStr(message, "not found") != NULL)
9455         || (StrStr(message, "No such file") != NULL)
9456         || (StrStr(message, "can't alloc") != NULL)
9457         || (StrStr(message, "Permission denied") != NULL)) {
9458
9459         cps->maybeThinking = FALSE;
9460         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9461                 _(cps->which), cps->program, cps->host, message);
9462         RemoveInputSource(cps->isr);
9463         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9464             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9465             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9466         }
9467         return;
9468     }
9469
9470     /*
9471      * Look for hint output
9472      */
9473     if (sscanf(message, "Hint: %s", buf1) == 1) {
9474         if (cps == &first && hintRequested) {
9475             hintRequested = FALSE;
9476             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9477                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9478                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9479                                     PosFlags(forwardMostMove),
9480                                     fromY, fromX, toY, toX, promoChar, buf1);
9481                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9482                 DisplayInformation(buf2);
9483             } else {
9484                 /* Hint move could not be parsed!? */
9485               snprintf(buf2, sizeof(buf2),
9486                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9487                         buf1, _(cps->which));
9488                 DisplayError(buf2, 0);
9489             }
9490         } else {
9491           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9492         }
9493         return;
9494     }
9495
9496     /*
9497      * Ignore other messages if game is not in progress
9498      */
9499     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9500         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9501
9502     /*
9503      * look for win, lose, draw, or draw offer
9504      */
9505     if (strncmp(message, "1-0", 3) == 0) {
9506         char *p, *q, *r = "";
9507         p = strchr(message, '{');
9508         if (p) {
9509             q = strchr(p, '}');
9510             if (q) {
9511                 *q = NULLCHAR;
9512                 r = p + 1;
9513             }
9514         }
9515         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9516         return;
9517     } else if (strncmp(message, "0-1", 3) == 0) {
9518         char *p, *q, *r = "";
9519         p = strchr(message, '{');
9520         if (p) {
9521             q = strchr(p, '}');
9522             if (q) {
9523                 *q = NULLCHAR;
9524                 r = p + 1;
9525             }
9526         }
9527         /* Kludge for Arasan 4.1 bug */
9528         if (strcmp(r, "Black resigns") == 0) {
9529             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9530             return;
9531         }
9532         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9533         return;
9534     } else if (strncmp(message, "1/2", 3) == 0) {
9535         char *p, *q, *r = "";
9536         p = strchr(message, '{');
9537         if (p) {
9538             q = strchr(p, '}');
9539             if (q) {
9540                 *q = NULLCHAR;
9541                 r = p + 1;
9542             }
9543         }
9544
9545         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9546         return;
9547
9548     } else if (strncmp(message, "White resign", 12) == 0) {
9549         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9550         return;
9551     } else if (strncmp(message, "Black resign", 12) == 0) {
9552         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9553         return;
9554     } else if (strncmp(message, "White matches", 13) == 0 ||
9555                strncmp(message, "Black matches", 13) == 0   ) {
9556         /* [HGM] ignore GNUShogi noises */
9557         return;
9558     } else if (strncmp(message, "White", 5) == 0 &&
9559                message[5] != '(' &&
9560                StrStr(message, "Black") == NULL) {
9561         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9562         return;
9563     } else if (strncmp(message, "Black", 5) == 0 &&
9564                message[5] != '(') {
9565         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9566         return;
9567     } else if (strcmp(message, "resign") == 0 ||
9568                strcmp(message, "computer resigns") == 0) {
9569         switch (gameMode) {
9570           case MachinePlaysBlack:
9571           case IcsPlayingBlack:
9572             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9573             break;
9574           case MachinePlaysWhite:
9575           case IcsPlayingWhite:
9576             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9577             break;
9578           case TwoMachinesPlay:
9579             if (cps->twoMachinesColor[0] == 'w')
9580               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9581             else
9582               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9583             break;
9584           default:
9585             /* can't happen */
9586             break;
9587         }
9588         return;
9589     } else if (strncmp(message, "opponent mates", 14) == 0) {
9590         switch (gameMode) {
9591           case MachinePlaysBlack:
9592           case IcsPlayingBlack:
9593             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9594             break;
9595           case MachinePlaysWhite:
9596           case IcsPlayingWhite:
9597             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9598             break;
9599           case TwoMachinesPlay:
9600             if (cps->twoMachinesColor[0] == 'w')
9601               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9602             else
9603               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9604             break;
9605           default:
9606             /* can't happen */
9607             break;
9608         }
9609         return;
9610     } else if (strncmp(message, "computer mates", 14) == 0) {
9611         switch (gameMode) {
9612           case MachinePlaysBlack:
9613           case IcsPlayingBlack:
9614             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9615             break;
9616           case MachinePlaysWhite:
9617           case IcsPlayingWhite:
9618             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9619             break;
9620           case TwoMachinesPlay:
9621             if (cps->twoMachinesColor[0] == 'w')
9622               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9623             else
9624               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9625             break;
9626           default:
9627             /* can't happen */
9628             break;
9629         }
9630         return;
9631     } else if (strncmp(message, "checkmate", 9) == 0) {
9632         if (WhiteOnMove(forwardMostMove)) {
9633             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9634         } else {
9635             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9636         }
9637         return;
9638     } else if (strstr(message, "Draw") != NULL ||
9639                strstr(message, "game is a draw") != NULL) {
9640         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9641         return;
9642     } else if (strstr(message, "offer") != NULL &&
9643                strstr(message, "draw") != NULL) {
9644 #if ZIPPY
9645         if (appData.zippyPlay && first.initDone) {
9646             /* Relay offer to ICS */
9647             SendToICS(ics_prefix);
9648             SendToICS("draw\n");
9649         }
9650 #endif
9651         cps->offeredDraw = 2; /* valid until this engine moves twice */
9652         if (gameMode == TwoMachinesPlay) {
9653             if (cps->other->offeredDraw) {
9654                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9655             /* [HGM] in two-machine mode we delay relaying draw offer      */
9656             /* until after we also have move, to see if it is really claim */
9657             }
9658         } else if (gameMode == MachinePlaysWhite ||
9659                    gameMode == MachinePlaysBlack) {
9660           if (userOfferedDraw) {
9661             DisplayInformation(_("Machine accepts your draw offer"));
9662             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9663           } else {
9664             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9665           }
9666         }
9667     }
9668
9669
9670     /*
9671      * Look for thinking output
9672      */
9673     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9674           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9675                                 ) {
9676         int plylev, mvleft, mvtot, curscore, time;
9677         char mvname[MOVE_LEN];
9678         u64 nodes; // [DM]
9679         char plyext;
9680         int ignore = FALSE;
9681         int prefixHint = FALSE;
9682         mvname[0] = NULLCHAR;
9683
9684         switch (gameMode) {
9685           case MachinePlaysBlack:
9686           case IcsPlayingBlack:
9687             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9688             break;
9689           case MachinePlaysWhite:
9690           case IcsPlayingWhite:
9691             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9692             break;
9693           case AnalyzeMode:
9694           case AnalyzeFile:
9695             break;
9696           case IcsObserving: /* [DM] icsEngineAnalyze */
9697             if (!appData.icsEngineAnalyze) ignore = TRUE;
9698             break;
9699           case TwoMachinesPlay:
9700             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9701                 ignore = TRUE;
9702             }
9703             break;
9704           default:
9705             ignore = TRUE;
9706             break;
9707         }
9708
9709         if (!ignore) {
9710             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9711             int solved = 0;
9712             buf1[0] = NULLCHAR;
9713             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9714                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9715                 char score_buf[MSG_SIZ];
9716
9717                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9718                     nodes += u64Const(0x100000000);
9719
9720                 if (plyext != ' ' && plyext != '\t') {
9721                     time *= 100;
9722                 }
9723
9724                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9725                 if( cps->scoreIsAbsolute &&
9726                     ( gameMode == MachinePlaysBlack ||
9727                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9728                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9729                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9730                      !WhiteOnMove(currentMove)
9731                     ) )
9732                 {
9733                     curscore = -curscore;
9734                 }
9735
9736                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9737
9738                 if(*bestMove) { // rememer time best EPD move was first found
9739                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9740                     ChessMove mt; char *p = bestMove;
9741                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9742                     solved = 0;
9743                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9744                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9745                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9746                             solved = 1;
9747                             break;
9748                         }
9749                         while(*p && *p != ' ') p++;
9750                         while(*p == ' ') p++;
9751                     }
9752                     if(!solved) solvingTime = -1;
9753                 }
9754                 if(*avoidMove && !solved) {
9755                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9756                     ChessMove mt; char *p = avoidMove, solved = 1;
9757                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9758                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9759                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9760                             solved = 0; solvingTime = -2;
9761                             break;
9762                         }
9763                         while(*p && *p != ' ') p++;
9764                         while(*p == ' ') p++;
9765                     }
9766                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9767                 }
9768
9769                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9770                         char buf[MSG_SIZ];
9771                         FILE *f;
9772                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9773                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9774                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9775                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9776                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9777                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9778                                 fclose(f);
9779                         }
9780                         else
9781                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9782                           DisplayError(_("failed writing PV"), 0);
9783                 }
9784
9785                 tempStats.depth = plylev;
9786                 tempStats.nodes = nodes;
9787                 tempStats.time = time;
9788                 tempStats.score = curscore;
9789                 tempStats.got_only_move = 0;
9790
9791                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9792                         int ticklen;
9793
9794                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9795                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9796                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9797                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9798                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9799                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9800                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9801                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9802                 }
9803
9804                 /* Buffer overflow protection */
9805                 if (pv[0] != NULLCHAR) {
9806                     if (strlen(pv) >= sizeof(tempStats.movelist)
9807                         && appData.debugMode) {
9808                         fprintf(debugFP,
9809                                 "PV is too long; using the first %u bytes.\n",
9810                                 (unsigned) sizeof(tempStats.movelist) - 1);
9811                     }
9812
9813                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9814                 } else {
9815                     sprintf(tempStats.movelist, " no PV\n");
9816                 }
9817
9818                 if (tempStats.seen_stat) {
9819                     tempStats.ok_to_send = 1;
9820                 }
9821
9822                 if (strchr(tempStats.movelist, '(') != NULL) {
9823                     tempStats.line_is_book = 1;
9824                     tempStats.nr_moves = 0;
9825                     tempStats.moves_left = 0;
9826                 } else {
9827                     tempStats.line_is_book = 0;
9828                 }
9829
9830                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9831                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9832
9833                 SendProgramStatsToFrontend( cps, &tempStats );
9834
9835                 /*
9836                     [AS] Protect the thinkOutput buffer from overflow... this
9837                     is only useful if buf1 hasn't overflowed first!
9838                 */
9839                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9840                 if(curscore >= MATE_SCORE) 
9841                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9842                 else if(curscore <= -MATE_SCORE) 
9843                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9844                 else
9845                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9846                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9847                          plylev,
9848                          (gameMode == TwoMachinesPlay ?
9849                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9850                          score_buf,
9851                          prefixHint ? lastHint : "",
9852                          prefixHint ? " " : "" );
9853
9854                 if( buf1[0] != NULLCHAR ) {
9855                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9856
9857                     if( strlen(pv) > max_len ) {
9858                         if( appData.debugMode) {
9859                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9860                         }
9861                         pv[max_len+1] = '\0';
9862                     }
9863
9864                     strcat( thinkOutput, pv);
9865                 }
9866
9867                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9868                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9869                     DisplayMove(currentMove - 1);
9870                 }
9871                 return;
9872
9873             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9874                 /* crafty (9.25+) says "(only move) <move>"
9875                  * if there is only 1 legal move
9876                  */
9877                 sscanf(p, "(only move) %s", buf1);
9878                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9879                 sprintf(programStats.movelist, "%s (only move)", buf1);
9880                 programStats.depth = 1;
9881                 programStats.nr_moves = 1;
9882                 programStats.moves_left = 1;
9883                 programStats.nodes = 1;
9884                 programStats.time = 1;
9885                 programStats.got_only_move = 1;
9886
9887                 /* Not really, but we also use this member to
9888                    mean "line isn't going to change" (Crafty
9889                    isn't searching, so stats won't change) */
9890                 programStats.line_is_book = 1;
9891
9892                 SendProgramStatsToFrontend( cps, &programStats );
9893
9894                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9895                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9896                     DisplayMove(currentMove - 1);
9897                 }
9898                 return;
9899             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9900                               &time, &nodes, &plylev, &mvleft,
9901                               &mvtot, mvname) >= 5) {
9902                 /* The stat01: line is from Crafty (9.29+) in response
9903                    to the "." command */
9904                 programStats.seen_stat = 1;
9905                 cps->maybeThinking = TRUE;
9906
9907                 if (programStats.got_only_move || !appData.periodicUpdates)
9908                   return;
9909
9910                 programStats.depth = plylev;
9911                 programStats.time = time;
9912                 programStats.nodes = nodes;
9913                 programStats.moves_left = mvleft;
9914                 programStats.nr_moves = mvtot;
9915                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9916                 programStats.ok_to_send = 1;
9917                 programStats.movelist[0] = '\0';
9918
9919                 SendProgramStatsToFrontend( cps, &programStats );
9920
9921                 return;
9922
9923             } else if (strncmp(message,"++",2) == 0) {
9924                 /* Crafty 9.29+ outputs this */
9925                 programStats.got_fail = 2;
9926                 return;
9927
9928             } else if (strncmp(message,"--",2) == 0) {
9929                 /* Crafty 9.29+ outputs this */
9930                 programStats.got_fail = 1;
9931                 return;
9932
9933             } else if (thinkOutput[0] != NULLCHAR &&
9934                        strncmp(message, "    ", 4) == 0) {
9935                 unsigned message_len;
9936
9937                 p = message;
9938                 while (*p && *p == ' ') p++;
9939
9940                 message_len = strlen( p );
9941
9942                 /* [AS] Avoid buffer overflow */
9943                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9944                     strcat(thinkOutput, " ");
9945                     strcat(thinkOutput, p);
9946                 }
9947
9948                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9949                     strcat(programStats.movelist, " ");
9950                     strcat(programStats.movelist, p);
9951                 }
9952
9953                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9954                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9955                     DisplayMove(currentMove - 1);
9956                 }
9957                 return;
9958             }
9959         }
9960         else {
9961             buf1[0] = NULLCHAR;
9962
9963             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9964                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9965             {
9966                 ChessProgramStats cpstats;
9967
9968                 if (plyext != ' ' && plyext != '\t') {
9969                     time *= 100;
9970                 }
9971
9972                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9973                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9974                     curscore = -curscore;
9975                 }
9976
9977                 cpstats.depth = plylev;
9978                 cpstats.nodes = nodes;
9979                 cpstats.time = time;
9980                 cpstats.score = curscore;
9981                 cpstats.got_only_move = 0;
9982                 cpstats.movelist[0] = '\0';
9983
9984                 if (buf1[0] != NULLCHAR) {
9985                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9986                 }
9987
9988                 cpstats.ok_to_send = 0;
9989                 cpstats.line_is_book = 0;
9990                 cpstats.nr_moves = 0;
9991                 cpstats.moves_left = 0;
9992
9993                 SendProgramStatsToFrontend( cps, &cpstats );
9994             }
9995         }
9996     }
9997 }
9998
9999
10000 /* Parse a game score from the character string "game", and
10001    record it as the history of the current game.  The game
10002    score is NOT assumed to start from the standard position.
10003    The display is not updated in any way.
10004    */
10005 void
10006 ParseGameHistory (char *game)
10007 {
10008     ChessMove moveType;
10009     int fromX, fromY, toX, toY, boardIndex;
10010     char promoChar;
10011     char *p, *q;
10012     char buf[MSG_SIZ];
10013
10014     if (appData.debugMode)
10015       fprintf(debugFP, "Parsing game history: %s\n", game);
10016
10017     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10018     gameInfo.site = StrSave(appData.icsHost);
10019     gameInfo.date = PGNDate();
10020     gameInfo.round = StrSave("-");
10021
10022     /* Parse out names of players */
10023     while (*game == ' ') game++;
10024     p = buf;
10025     while (*game != ' ') *p++ = *game++;
10026     *p = NULLCHAR;
10027     gameInfo.white = StrSave(buf);
10028     while (*game == ' ') game++;
10029     p = buf;
10030     while (*game != ' ' && *game != '\n') *p++ = *game++;
10031     *p = NULLCHAR;
10032     gameInfo.black = StrSave(buf);
10033
10034     /* Parse moves */
10035     boardIndex = blackPlaysFirst ? 1 : 0;
10036     yynewstr(game);
10037     for (;;) {
10038         yyboardindex = boardIndex;
10039         moveType = (ChessMove) Myylex();
10040         switch (moveType) {
10041           case IllegalMove:             /* maybe suicide chess, etc. */
10042   if (appData.debugMode) {
10043     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10044     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10045     setbuf(debugFP, NULL);
10046   }
10047           case WhitePromotion:
10048           case BlackPromotion:
10049           case WhiteNonPromotion:
10050           case BlackNonPromotion:
10051           case NormalMove:
10052           case FirstLeg:
10053           case WhiteCapturesEnPassant:
10054           case BlackCapturesEnPassant:
10055           case WhiteKingSideCastle:
10056           case WhiteQueenSideCastle:
10057           case BlackKingSideCastle:
10058           case BlackQueenSideCastle:
10059           case WhiteKingSideCastleWild:
10060           case WhiteQueenSideCastleWild:
10061           case BlackKingSideCastleWild:
10062           case BlackQueenSideCastleWild:
10063           /* PUSH Fabien */
10064           case WhiteHSideCastleFR:
10065           case WhiteASideCastleFR:
10066           case BlackHSideCastleFR:
10067           case BlackASideCastleFR:
10068           /* POP Fabien */
10069             fromX = currentMoveString[0] - AAA;
10070             fromY = currentMoveString[1] - ONE;
10071             toX = currentMoveString[2] - AAA;
10072             toY = currentMoveString[3] - ONE;
10073             promoChar = currentMoveString[4];
10074             break;
10075           case WhiteDrop:
10076           case BlackDrop:
10077             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10078             fromX = moveType == WhiteDrop ?
10079               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10080             (int) CharToPiece(ToLower(currentMoveString[0]));
10081             fromY = DROP_RANK;
10082             toX = currentMoveString[2] - AAA;
10083             toY = currentMoveString[3] - ONE;
10084             promoChar = NULLCHAR;
10085             break;
10086           case AmbiguousMove:
10087             /* bug? */
10088             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10089   if (appData.debugMode) {
10090     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10091     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10092     setbuf(debugFP, NULL);
10093   }
10094             DisplayError(buf, 0);
10095             return;
10096           case ImpossibleMove:
10097             /* bug? */
10098             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10099   if (appData.debugMode) {
10100     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10101     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10102     setbuf(debugFP, NULL);
10103   }
10104             DisplayError(buf, 0);
10105             return;
10106           case EndOfFile:
10107             if (boardIndex < backwardMostMove) {
10108                 /* Oops, gap.  How did that happen? */
10109                 DisplayError(_("Gap in move list"), 0);
10110                 return;
10111             }
10112             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10113             if (boardIndex > forwardMostMove) {
10114                 forwardMostMove = boardIndex;
10115             }
10116             return;
10117           case ElapsedTime:
10118             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10119                 strcat(parseList[boardIndex-1], " ");
10120                 strcat(parseList[boardIndex-1], yy_text);
10121             }
10122             continue;
10123           case Comment:
10124           case PGNTag:
10125           case NAG:
10126           default:
10127             /* ignore */
10128             continue;
10129           case WhiteWins:
10130           case BlackWins:
10131           case GameIsDrawn:
10132           case GameUnfinished:
10133             if (gameMode == IcsExamining) {
10134                 if (boardIndex < backwardMostMove) {
10135                     /* Oops, gap.  How did that happen? */
10136                     return;
10137                 }
10138                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10139                 return;
10140             }
10141             gameInfo.result = moveType;
10142             p = strchr(yy_text, '{');
10143             if (p == NULL) p = strchr(yy_text, '(');
10144             if (p == NULL) {
10145                 p = yy_text;
10146                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10147             } else {
10148                 q = strchr(p, *p == '{' ? '}' : ')');
10149                 if (q != NULL) *q = NULLCHAR;
10150                 p++;
10151             }
10152             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10153             gameInfo.resultDetails = StrSave(p);
10154             continue;
10155         }
10156         if (boardIndex >= forwardMostMove &&
10157             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10158             backwardMostMove = blackPlaysFirst ? 1 : 0;
10159             return;
10160         }
10161         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10162                                  fromY, fromX, toY, toX, promoChar,
10163                                  parseList[boardIndex]);
10164         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10165         /* currentMoveString is set as a side-effect of yylex */
10166         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10167         strcat(moveList[boardIndex], "\n");
10168         boardIndex++;
10169         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10170         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10171           case MT_NONE:
10172           case MT_STALEMATE:
10173           default:
10174             break;
10175           case MT_CHECK:
10176             if(!IS_SHOGI(gameInfo.variant))
10177                 strcat(parseList[boardIndex - 1], "+");
10178             break;
10179           case MT_CHECKMATE:
10180           case MT_STAINMATE:
10181             strcat(parseList[boardIndex - 1], "#");
10182             break;
10183         }
10184     }
10185 }
10186
10187
10188 /* Apply a move to the given board  */
10189 void
10190 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10191 {
10192   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10193   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10194
10195     /* [HGM] compute & store e.p. status and castling rights for new position */
10196     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10197
10198       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10199       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10200       board[EP_STATUS] = EP_NONE;
10201       board[EP_FILE] = board[EP_RANK] = 100;
10202
10203   if (fromY == DROP_RANK) {
10204         /* must be first */
10205         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10206             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10207             return;
10208         }
10209         piece = board[toY][toX] = (ChessSquare) fromX;
10210   } else {
10211 //      ChessSquare victim;
10212       int i;
10213
10214       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10215 //           victim = board[killY][killX],
10216            killed = board[killY][killX],
10217            board[killY][killX] = EmptySquare,
10218            board[EP_STATUS] = EP_CAPTURE;
10219            if( kill2X >= 0 && kill2Y >= 0)
10220              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10221       }
10222
10223       if( board[toY][toX] != EmptySquare ) {
10224            board[EP_STATUS] = EP_CAPTURE;
10225            if( (fromX != toX || fromY != toY) && // not igui!
10226                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10227                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10228                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10229            }
10230       }
10231
10232       pawn = board[fromY][fromX];
10233       if( pawn == WhiteLance || pawn == BlackLance ) {
10234            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10235                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10236                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10237            }
10238       }
10239       if( pawn == WhitePawn ) {
10240            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10241                board[EP_STATUS] = EP_PAWN_MOVE;
10242            if( toY-fromY>=2) {
10243                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10244                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10245                         gameInfo.variant != VariantBerolina || toX < fromX)
10246                       board[EP_STATUS] = toX | berolina;
10247                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10248                         gameInfo.variant != VariantBerolina || toX > fromX)
10249                       board[EP_STATUS] = toX;
10250            }
10251       } else
10252       if( pawn == BlackPawn ) {
10253            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10254                board[EP_STATUS] = EP_PAWN_MOVE;
10255            if( toY-fromY<= -2) {
10256                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10257                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10258                         gameInfo.variant != VariantBerolina || toX < fromX)
10259                       board[EP_STATUS] = toX | berolina;
10260                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10261                         gameInfo.variant != VariantBerolina || toX > fromX)
10262                       board[EP_STATUS] = toX;
10263            }
10264        }
10265
10266        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10267        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10268        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10269        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10270
10271        for(i=0; i<nrCastlingRights; i++) {
10272            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10273               board[CASTLING][i] == toX   && castlingRank[i] == toY
10274              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10275        }
10276
10277        if(gameInfo.variant == VariantSChess) { // update virginity
10278            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10279            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10280            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10281            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10282        }
10283
10284      if (fromX == toX && fromY == toY && killX < 0) return;
10285
10286      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10287      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10288      if(gameInfo.variant == VariantKnightmate)
10289          king += (int) WhiteUnicorn - (int) WhiteKing;
10290
10291     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10292        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10293         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10294         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10295         board[EP_STATUS] = EP_NONE; // capture was fake!
10296     } else
10297     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10298         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10299         board[toY][toX] = piece;
10300         board[EP_STATUS] = EP_NONE; // capture was fake!
10301     } else
10302     /* Code added by Tord: */
10303     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10304     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10305         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10306       board[EP_STATUS] = EP_NONE; // capture was fake!
10307       board[fromY][fromX] = EmptySquare;
10308       board[toY][toX] = EmptySquare;
10309       if((toX > fromX) != (piece == WhiteRook)) {
10310         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10311       } else {
10312         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10313       }
10314     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10315                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10316       board[EP_STATUS] = EP_NONE;
10317       board[fromY][fromX] = EmptySquare;
10318       board[toY][toX] = EmptySquare;
10319       if((toX > fromX) != (piece == BlackRook)) {
10320         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10321       } else {
10322         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10323       }
10324     /* End of code added by Tord */
10325
10326     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10327         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10328         board[toY][toX] = piece;
10329     } else if (board[fromY][fromX] == king
10330         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10331         && toY == fromY && toX > fromX+1) {
10332         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10333                                                                                              ; // castle with nearest piece
10334         board[fromY][toX-1] = board[fromY][rookX];
10335         board[fromY][rookX] = EmptySquare;
10336         board[fromY][fromX] = EmptySquare;
10337         board[toY][toX] = king;
10338     } else if (board[fromY][fromX] == king
10339         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10340                && toY == fromY && toX < fromX-1) {
10341         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10342                                                                                   ; // castle with nearest piece
10343         board[fromY][toX+1] = board[fromY][rookX];
10344         board[fromY][rookX] = EmptySquare;
10345         board[fromY][fromX] = EmptySquare;
10346         board[toY][toX] = king;
10347     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10348                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10349                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10350                ) {
10351         /* white pawn promotion */
10352         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10353         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10354             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10355         board[fromY][fromX] = EmptySquare;
10356     } else if ((fromY >= BOARD_HEIGHT>>1)
10357                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10358                && (toX != fromX)
10359                && gameInfo.variant != VariantXiangqi
10360                && gameInfo.variant != VariantBerolina
10361                && (pawn == WhitePawn)
10362                && (board[toY][toX] == EmptySquare)) {
10363         board[fromY][fromX] = EmptySquare;
10364         board[toY][toX] = piece;
10365         if(toY == epRank - 128 + 1)
10366             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10367         else
10368             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10369     } else if ((fromY == BOARD_HEIGHT-4)
10370                && (toX == fromX)
10371                && gameInfo.variant == VariantBerolina
10372                && (board[fromY][fromX] == WhitePawn)
10373                && (board[toY][toX] == EmptySquare)) {
10374         board[fromY][fromX] = EmptySquare;
10375         board[toY][toX] = WhitePawn;
10376         if(oldEP & EP_BEROLIN_A) {
10377                 captured = board[fromY][fromX-1];
10378                 board[fromY][fromX-1] = EmptySquare;
10379         }else{  captured = board[fromY][fromX+1];
10380                 board[fromY][fromX+1] = EmptySquare;
10381         }
10382     } else if (board[fromY][fromX] == king
10383         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10384                && toY == fromY && toX > fromX+1) {
10385         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10386                                                                                              ;
10387         board[fromY][toX-1] = board[fromY][rookX];
10388         board[fromY][rookX] = EmptySquare;
10389         board[fromY][fromX] = EmptySquare;
10390         board[toY][toX] = king;
10391     } else if (board[fromY][fromX] == king
10392         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10393                && toY == fromY && toX < fromX-1) {
10394         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10395                                                                                 ;
10396         board[fromY][toX+1] = board[fromY][rookX];
10397         board[fromY][rookX] = EmptySquare;
10398         board[fromY][fromX] = EmptySquare;
10399         board[toY][toX] = king;
10400     } else if (fromY == 7 && fromX == 3
10401                && board[fromY][fromX] == BlackKing
10402                && toY == 7 && toX == 5) {
10403         board[fromY][fromX] = EmptySquare;
10404         board[toY][toX] = BlackKing;
10405         board[fromY][7] = EmptySquare;
10406         board[toY][4] = BlackRook;
10407     } else if (fromY == 7 && fromX == 3
10408                && board[fromY][fromX] == BlackKing
10409                && toY == 7 && toX == 1) {
10410         board[fromY][fromX] = EmptySquare;
10411         board[toY][toX] = BlackKing;
10412         board[fromY][0] = EmptySquare;
10413         board[toY][2] = BlackRook;
10414     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10415                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10416                && toY < promoRank && promoChar
10417                ) {
10418         /* black pawn promotion */
10419         board[toY][toX] = CharToPiece(ToLower(promoChar));
10420         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10421             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10422         board[fromY][fromX] = EmptySquare;
10423     } else if ((fromY < BOARD_HEIGHT>>1)
10424                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10425                && (toX != fromX)
10426                && gameInfo.variant != VariantXiangqi
10427                && gameInfo.variant != VariantBerolina
10428                && (pawn == BlackPawn)
10429                && (board[toY][toX] == EmptySquare)) {
10430         board[fromY][fromX] = EmptySquare;
10431         board[toY][toX] = piece;
10432         if(toY == epRank - 128 - 1)
10433             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10434         else
10435             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10436     } else if ((fromY == 3)
10437                && (toX == fromX)
10438                && gameInfo.variant == VariantBerolina
10439                && (board[fromY][fromX] == BlackPawn)
10440                && (board[toY][toX] == EmptySquare)) {
10441         board[fromY][fromX] = EmptySquare;
10442         board[toY][toX] = BlackPawn;
10443         if(oldEP & EP_BEROLIN_A) {
10444                 captured = board[fromY][fromX-1];
10445                 board[fromY][fromX-1] = EmptySquare;
10446         }else{  captured = board[fromY][fromX+1];
10447                 board[fromY][fromX+1] = EmptySquare;
10448         }
10449     } else {
10450         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10451         board[fromY][fromX] = EmptySquare;
10452         board[toY][toX] = piece;
10453     }
10454   }
10455
10456     if (gameInfo.holdingsWidth != 0) {
10457
10458       /* !!A lot more code needs to be written to support holdings  */
10459       /* [HGM] OK, so I have written it. Holdings are stored in the */
10460       /* penultimate board files, so they are automaticlly stored   */
10461       /* in the game history.                                       */
10462       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10463                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10464         /* Delete from holdings, by decreasing count */
10465         /* and erasing image if necessary            */
10466         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10467         if(p < (int) BlackPawn) { /* white drop */
10468              p -= (int)WhitePawn;
10469                  p = PieceToNumber((ChessSquare)p);
10470              if(p >= gameInfo.holdingsSize) p = 0;
10471              if(--board[p][BOARD_WIDTH-2] <= 0)
10472                   board[p][BOARD_WIDTH-1] = EmptySquare;
10473              if((int)board[p][BOARD_WIDTH-2] < 0)
10474                         board[p][BOARD_WIDTH-2] = 0;
10475         } else {                  /* black drop */
10476              p -= (int)BlackPawn;
10477                  p = PieceToNumber((ChessSquare)p);
10478              if(p >= gameInfo.holdingsSize) p = 0;
10479              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10480                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10481              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10482                         board[BOARD_HEIGHT-1-p][1] = 0;
10483         }
10484       }
10485       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10486           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10487         /* [HGM] holdings: Add to holdings, if holdings exist */
10488         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10489                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10490                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10491         }
10492         p = (int) captured;
10493         if (p >= (int) BlackPawn) {
10494           p -= (int)BlackPawn;
10495           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10496                   /* Restore shogi-promoted piece to its original  first */
10497                   captured = (ChessSquare) (DEMOTED(captured));
10498                   p = DEMOTED(p);
10499           }
10500           p = PieceToNumber((ChessSquare)p);
10501           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10502           board[p][BOARD_WIDTH-2]++;
10503           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10504         } else {
10505           p -= (int)WhitePawn;
10506           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10507                   captured = (ChessSquare) (DEMOTED(captured));
10508                   p = DEMOTED(p);
10509           }
10510           p = PieceToNumber((ChessSquare)p);
10511           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10512           board[BOARD_HEIGHT-1-p][1]++;
10513           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10514         }
10515       }
10516     } else if (gameInfo.variant == VariantAtomic) {
10517       if (captured != EmptySquare) {
10518         int y, x;
10519         for (y = toY-1; y <= toY+1; y++) {
10520           for (x = toX-1; x <= toX+1; x++) {
10521             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10522                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10523               board[y][x] = EmptySquare;
10524             }
10525           }
10526         }
10527         board[toY][toX] = EmptySquare;
10528       }
10529     }
10530
10531     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10532         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10533     } else
10534     if(promoChar == '+') {
10535         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10536         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10537         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10538           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10539     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10540         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10541         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10542            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10543         board[toY][toX] = newPiece;
10544     }
10545     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10546                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10547         // [HGM] superchess: take promotion piece out of holdings
10548         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10549         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10550             if(!--board[k][BOARD_WIDTH-2])
10551                 board[k][BOARD_WIDTH-1] = EmptySquare;
10552         } else {
10553             if(!--board[BOARD_HEIGHT-1-k][1])
10554                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10555         }
10556     }
10557 }
10558
10559 /* Updates forwardMostMove */
10560 void
10561 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10562 {
10563     int x = toX, y = toY;
10564     char *s = parseList[forwardMostMove];
10565     ChessSquare p = boards[forwardMostMove][toY][toX];
10566 //    forwardMostMove++; // [HGM] bare: moved downstream
10567
10568     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10569     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10570     (void) CoordsToAlgebraic(boards[forwardMostMove],
10571                              PosFlags(forwardMostMove),
10572                              fromY, fromX, y, x, (killX < 0)*promoChar,
10573                              s);
10574     if(kill2X >= 0 && kill2Y >= 0)
10575         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10576     if(killX >= 0 && killY >= 0)
10577         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10578                                            toX + AAA, toY + ONE - '0', promoChar);
10579
10580     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10581         int timeLeft; static int lastLoadFlag=0; int king, piece;
10582         piece = boards[forwardMostMove][fromY][fromX];
10583         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10584         if(gameInfo.variant == VariantKnightmate)
10585             king += (int) WhiteUnicorn - (int) WhiteKing;
10586         if(forwardMostMove == 0) {
10587             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10588                 fprintf(serverMoves, "%s;", UserName());
10589             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10590                 fprintf(serverMoves, "%s;", second.tidy);
10591             fprintf(serverMoves, "%s;", first.tidy);
10592             if(gameMode == MachinePlaysWhite)
10593                 fprintf(serverMoves, "%s;", UserName());
10594             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10595                 fprintf(serverMoves, "%s;", second.tidy);
10596         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10597         lastLoadFlag = loadFlag;
10598         // print base move
10599         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10600         // print castling suffix
10601         if( toY == fromY && piece == king ) {
10602             if(toX-fromX > 1)
10603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10604             if(fromX-toX >1)
10605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10606         }
10607         // e.p. suffix
10608         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10609              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10610              boards[forwardMostMove][toY][toX] == EmptySquare
10611              && fromX != toX && fromY != toY)
10612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10613         // promotion suffix
10614         if(promoChar != NULLCHAR) {
10615             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10616                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10617                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10618             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10619         }
10620         if(!loadFlag) {
10621                 char buf[MOVE_LEN*2], *p; int len;
10622             fprintf(serverMoves, "/%d/%d",
10623                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10624             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10625             else                      timeLeft = blackTimeRemaining/1000;
10626             fprintf(serverMoves, "/%d", timeLeft);
10627                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10628                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10629                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10630                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10631             fprintf(serverMoves, "/%s", buf);
10632         }
10633         fflush(serverMoves);
10634     }
10635
10636     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10637         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10638       return;
10639     }
10640     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10641     if (commentList[forwardMostMove+1] != NULL) {
10642         free(commentList[forwardMostMove+1]);
10643         commentList[forwardMostMove+1] = NULL;
10644     }
10645     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10646     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10647     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10648     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10649     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10650     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10651     adjustedClock = FALSE;
10652     gameInfo.result = GameUnfinished;
10653     if (gameInfo.resultDetails != NULL) {
10654         free(gameInfo.resultDetails);
10655         gameInfo.resultDetails = NULL;
10656     }
10657     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10658                               moveList[forwardMostMove - 1]);
10659     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10660       case MT_NONE:
10661       case MT_STALEMATE:
10662       default:
10663         break;
10664       case MT_CHECK:
10665         if(!IS_SHOGI(gameInfo.variant))
10666             strcat(parseList[forwardMostMove - 1], "+");
10667         break;
10668       case MT_CHECKMATE:
10669       case MT_STAINMATE:
10670         strcat(parseList[forwardMostMove - 1], "#");
10671         break;
10672     }
10673 }
10674
10675 /* Updates currentMove if not pausing */
10676 void
10677 ShowMove (int fromX, int fromY, int toX, int toY)
10678 {
10679     int instant = (gameMode == PlayFromGameFile) ?
10680         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10681     if(appData.noGUI) return;
10682     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10683         if (!instant) {
10684             if (forwardMostMove == currentMove + 1) {
10685                 AnimateMove(boards[forwardMostMove - 1],
10686                             fromX, fromY, toX, toY);
10687             }
10688         }
10689         currentMove = forwardMostMove;
10690     }
10691
10692     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10693
10694     if (instant) return;
10695
10696     DisplayMove(currentMove - 1);
10697     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10698             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10699                 SetHighlights(fromX, fromY, toX, toY);
10700             }
10701     }
10702     DrawPosition(FALSE, boards[currentMove]);
10703     DisplayBothClocks();
10704     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10705 }
10706
10707 void
10708 SendEgtPath (ChessProgramState *cps)
10709 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10710         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10711
10712         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10713
10714         while(*p) {
10715             char c, *q = name+1, *r, *s;
10716
10717             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10718             while(*p && *p != ',') *q++ = *p++;
10719             *q++ = ':'; *q = 0;
10720             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10721                 strcmp(name, ",nalimov:") == 0 ) {
10722                 // take nalimov path from the menu-changeable option first, if it is defined
10723               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10724                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10725             } else
10726             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10727                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10728                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10729                 s = r = StrStr(s, ":") + 1; // beginning of path info
10730                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10731                 c = *r; *r = 0;             // temporarily null-terminate path info
10732                     *--q = 0;               // strip of trailig ':' from name
10733                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10734                 *r = c;
10735                 SendToProgram(buf,cps);     // send egtbpath command for this format
10736             }
10737             if(*p == ',') p++; // read away comma to position for next format name
10738         }
10739 }
10740
10741 static int
10742 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10743 {
10744       int width = 8, height = 8, holdings = 0;             // most common sizes
10745       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10746       // correct the deviations default for each variant
10747       if( v == VariantXiangqi ) width = 9,  height = 10;
10748       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10749       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10750       if( v == VariantCapablanca || v == VariantCapaRandom ||
10751           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10752                                 width = 10;
10753       if( v == VariantCourier ) width = 12;
10754       if( v == VariantSuper )                            holdings = 8;
10755       if( v == VariantGreat )   width = 10,              holdings = 8;
10756       if( v == VariantSChess )                           holdings = 7;
10757       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10758       if( v == VariantChuChess) width = 10, height = 10;
10759       if( v == VariantChu )     width = 12, height = 12;
10760       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10761              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10762              holdingsSize >= 0 && holdingsSize != holdings;
10763 }
10764
10765 char variantError[MSG_SIZ];
10766
10767 char *
10768 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10769 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10770       char *p, *variant = VariantName(v);
10771       static char b[MSG_SIZ];
10772       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10773            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10774                                                holdingsSize, variant); // cook up sized variant name
10775            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10776            if(StrStr(list, b) == NULL) {
10777                // specific sized variant not known, check if general sizing allowed
10778                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10779                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10780                             boardWidth, boardHeight, holdingsSize, engine);
10781                    return NULL;
10782                }
10783                /* [HGM] here we really should compare with the maximum supported board size */
10784            }
10785       } else snprintf(b, MSG_SIZ,"%s", variant);
10786       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10787       p = StrStr(list, b);
10788       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10789       if(p == NULL) {
10790           // occurs not at all in list, or only as sub-string
10791           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10792           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10793               int l = strlen(variantError);
10794               char *q;
10795               while(p != list && p[-1] != ',') p--;
10796               q = strchr(p, ',');
10797               if(q) *q = NULLCHAR;
10798               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10799               if(q) *q= ',';
10800           }
10801           return NULL;
10802       }
10803       return b;
10804 }
10805
10806 void
10807 InitChessProgram (ChessProgramState *cps, int setup)
10808 /* setup needed to setup FRC opening position */
10809 {
10810     char buf[MSG_SIZ], *b;
10811     if (appData.noChessProgram) return;
10812     hintRequested = FALSE;
10813     bookRequested = FALSE;
10814
10815     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10816     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10817     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10818     if(cps->memSize) { /* [HGM] memory */
10819       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10820         SendToProgram(buf, cps);
10821     }
10822     SendEgtPath(cps); /* [HGM] EGT */
10823     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10824       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10825         SendToProgram(buf, cps);
10826     }
10827
10828     setboardSpoiledMachineBlack = FALSE;
10829     SendToProgram(cps->initString, cps);
10830     if (gameInfo.variant != VariantNormal &&
10831         gameInfo.variant != VariantLoadable
10832         /* [HGM] also send variant if board size non-standard */
10833         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10834
10835       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10836                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10837
10838       if (b == NULL) {
10839         VariantClass v;
10840         char c, *q = cps->variants, *p = strchr(q, ',');
10841         if(p) *p = NULLCHAR;
10842         v = StringToVariant(q);
10843         DisplayError(variantError, 0);
10844         if(v != VariantUnknown && cps == &first) {
10845             int w, h, s;
10846             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10847                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10848             ASSIGN(appData.variant, q);
10849             Reset(TRUE, FALSE);
10850         }
10851         if(p) *p = ',';
10852         return;
10853       }
10854
10855       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10856       SendToProgram(buf, cps);
10857     }
10858     currentlyInitializedVariant = gameInfo.variant;
10859
10860     /* [HGM] send opening position in FRC to first engine */
10861     if(setup) {
10862           SendToProgram("force\n", cps);
10863           SendBoard(cps, 0);
10864           /* engine is now in force mode! Set flag to wake it up after first move. */
10865           setboardSpoiledMachineBlack = 1;
10866     }
10867
10868     if (cps->sendICS) {
10869       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10870       SendToProgram(buf, cps);
10871     }
10872     cps->maybeThinking = FALSE;
10873     cps->offeredDraw = 0;
10874     if (!appData.icsActive) {
10875         SendTimeControl(cps, movesPerSession, timeControl,
10876                         timeIncrement, appData.searchDepth,
10877                         searchTime);
10878     }
10879     if (appData.showThinking
10880         // [HGM] thinking: four options require thinking output to be sent
10881         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10882                                 ) {
10883         SendToProgram("post\n", cps);
10884     }
10885     SendToProgram("hard\n", cps);
10886     if (!appData.ponderNextMove) {
10887         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10888            it without being sure what state we are in first.  "hard"
10889            is not a toggle, so that one is OK.
10890          */
10891         SendToProgram("easy\n", cps);
10892     }
10893     if (cps->usePing) {
10894       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10895       SendToProgram(buf, cps);
10896     }
10897     cps->initDone = TRUE;
10898     ClearEngineOutputPane(cps == &second);
10899 }
10900
10901
10902 void
10903 ResendOptions (ChessProgramState *cps)
10904 { // send the stored value of the options
10905   int i;
10906   char buf[MSG_SIZ];
10907   Option *opt = cps->option;
10908   for(i=0; i<cps->nrOptions; i++, opt++) {
10909       switch(opt->type) {
10910         case Spin:
10911         case Slider:
10912         case CheckBox:
10913             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10914           break;
10915         case ComboBox:
10916           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10917           break;
10918         default:
10919             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10920           break;
10921         case Button:
10922         case SaveButton:
10923           continue;
10924       }
10925       SendToProgram(buf, cps);
10926   }
10927 }
10928
10929 void
10930 StartChessProgram (ChessProgramState *cps)
10931 {
10932     char buf[MSG_SIZ];
10933     int err;
10934
10935     if (appData.noChessProgram) return;
10936     cps->initDone = FALSE;
10937
10938     if (strcmp(cps->host, "localhost") == 0) {
10939         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10940     } else if (*appData.remoteShell == NULLCHAR) {
10941         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10942     } else {
10943         if (*appData.remoteUser == NULLCHAR) {
10944           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10945                     cps->program);
10946         } else {
10947           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10948                     cps->host, appData.remoteUser, cps->program);
10949         }
10950         err = StartChildProcess(buf, "", &cps->pr);
10951     }
10952
10953     if (err != 0) {
10954       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10955         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10956         if(cps != &first) return;
10957         appData.noChessProgram = TRUE;
10958         ThawUI();
10959         SetNCPMode();
10960 //      DisplayFatalError(buf, err, 1);
10961 //      cps->pr = NoProc;
10962 //      cps->isr = NULL;
10963         return;
10964     }
10965
10966     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10967     if (cps->protocolVersion > 1) {
10968       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10969       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10970         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10971         cps->comboCnt = 0;  //                and values of combo boxes
10972       }
10973       SendToProgram(buf, cps);
10974       if(cps->reload) ResendOptions(cps);
10975     } else {
10976       SendToProgram("xboard\n", cps);
10977     }
10978 }
10979
10980 void
10981 TwoMachinesEventIfReady P((void))
10982 {
10983   static int curMess = 0;
10984   if (first.lastPing != first.lastPong) {
10985     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10986     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10987     return;
10988   }
10989   if (second.lastPing != second.lastPong) {
10990     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10991     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10992     return;
10993   }
10994   DisplayMessage("", ""); curMess = 0;
10995   TwoMachinesEvent();
10996 }
10997
10998 char *
10999 MakeName (char *template)
11000 {
11001     time_t clock;
11002     struct tm *tm;
11003     static char buf[MSG_SIZ];
11004     char *p = buf;
11005     int i;
11006
11007     clock = time((time_t *)NULL);
11008     tm = localtime(&clock);
11009
11010     while(*p++ = *template++) if(p[-1] == '%') {
11011         switch(*template++) {
11012           case 0:   *p = 0; return buf;
11013           case 'Y': i = tm->tm_year+1900; break;
11014           case 'y': i = tm->tm_year-100; break;
11015           case 'M': i = tm->tm_mon+1; break;
11016           case 'd': i = tm->tm_mday; break;
11017           case 'h': i = tm->tm_hour; break;
11018           case 'm': i = tm->tm_min; break;
11019           case 's': i = tm->tm_sec; break;
11020           default:  i = 0;
11021         }
11022         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11023     }
11024     return buf;
11025 }
11026
11027 int
11028 CountPlayers (char *p)
11029 {
11030     int n = 0;
11031     while(p = strchr(p, '\n')) p++, n++; // count participants
11032     return n;
11033 }
11034
11035 FILE *
11036 WriteTourneyFile (char *results, FILE *f)
11037 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11038     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11039     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11040         // create a file with tournament description
11041         fprintf(f, "-participants {%s}\n", appData.participants);
11042         fprintf(f, "-seedBase %d\n", appData.seedBase);
11043         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11044         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11045         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11046         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11047         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11048         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11049         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11050         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11051         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11052         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11053         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11054         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11055         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11056         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11057         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11058         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11059         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11060         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11061         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11062         fprintf(f, "-smpCores %d\n", appData.smpCores);
11063         if(searchTime > 0)
11064                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11065         else {
11066                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11067                 fprintf(f, "-tc %s\n", appData.timeControl);
11068                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11069         }
11070         fprintf(f, "-results \"%s\"\n", results);
11071     }
11072     return f;
11073 }
11074
11075 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11076
11077 void
11078 Substitute (char *participants, int expunge)
11079 {
11080     int i, changed, changes=0, nPlayers=0;
11081     char *p, *q, *r, buf[MSG_SIZ];
11082     if(participants == NULL) return;
11083     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11084     r = p = participants; q = appData.participants;
11085     while(*p && *p == *q) {
11086         if(*p == '\n') r = p+1, nPlayers++;
11087         p++; q++;
11088     }
11089     if(*p) { // difference
11090         while(*p && *p++ != '\n')
11091                                  ;
11092         while(*q && *q++ != '\n')
11093                                  ;
11094       changed = nPlayers;
11095         changes = 1 + (strcmp(p, q) != 0);
11096     }
11097     if(changes == 1) { // a single engine mnemonic was changed
11098         q = r; while(*q) nPlayers += (*q++ == '\n');
11099         p = buf; while(*r && (*p = *r++) != '\n') p++;
11100         *p = NULLCHAR;
11101         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11102         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11103         if(mnemonic[i]) { // The substitute is valid
11104             FILE *f;
11105             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11106                 flock(fileno(f), LOCK_EX);
11107                 ParseArgsFromFile(f);
11108                 fseek(f, 0, SEEK_SET);
11109                 FREE(appData.participants); appData.participants = participants;
11110                 if(expunge) { // erase results of replaced engine
11111                     int len = strlen(appData.results), w, b, dummy;
11112                     for(i=0; i<len; i++) {
11113                         Pairing(i, nPlayers, &w, &b, &dummy);
11114                         if((w == changed || b == changed) && appData.results[i] == '*') {
11115                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11116                             fclose(f);
11117                             return;
11118                         }
11119                     }
11120                     for(i=0; i<len; i++) {
11121                         Pairing(i, nPlayers, &w, &b, &dummy);
11122                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11123                     }
11124                 }
11125                 WriteTourneyFile(appData.results, f);
11126                 fclose(f); // release lock
11127                 return;
11128             }
11129         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11130     }
11131     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11132     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11133     free(participants);
11134     return;
11135 }
11136
11137 int
11138 CheckPlayers (char *participants)
11139 {
11140         int i;
11141         char buf[MSG_SIZ], *p;
11142         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11143         while(p = strchr(participants, '\n')) {
11144             *p = NULLCHAR;
11145             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11146             if(!mnemonic[i]) {
11147                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11148                 *p = '\n';
11149                 DisplayError(buf, 0);
11150                 return 1;
11151             }
11152             *p = '\n';
11153             participants = p + 1;
11154         }
11155         return 0;
11156 }
11157
11158 int
11159 CreateTourney (char *name)
11160 {
11161         FILE *f;
11162         if(matchMode && strcmp(name, appData.tourneyFile)) {
11163              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11164         }
11165         if(name[0] == NULLCHAR) {
11166             if(appData.participants[0])
11167                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11168             return 0;
11169         }
11170         f = fopen(name, "r");
11171         if(f) { // file exists
11172             ASSIGN(appData.tourneyFile, name);
11173             ParseArgsFromFile(f); // parse it
11174         } else {
11175             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11176             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11177                 DisplayError(_("Not enough participants"), 0);
11178                 return 0;
11179             }
11180             if(CheckPlayers(appData.participants)) return 0;
11181             ASSIGN(appData.tourneyFile, name);
11182             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11183             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11184         }
11185         fclose(f);
11186         appData.noChessProgram = FALSE;
11187         appData.clockMode = TRUE;
11188         SetGNUMode();
11189         return 1;
11190 }
11191
11192 int
11193 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11194 {
11195     char buf[MSG_SIZ], *p, *q;
11196     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11197     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11198     skip = !all && group[0]; // if group requested, we start in skip mode
11199     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11200         p = names; q = buf; header = 0;
11201         while(*p && *p != '\n') *q++ = *p++;
11202         *q = 0;
11203         if(*p == '\n') p++;
11204         if(buf[0] == '#') {
11205             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11206             depth++; // we must be entering a new group
11207             if(all) continue; // suppress printing group headers when complete list requested
11208             header = 1;
11209             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11210         }
11211         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11212         if(engineList[i]) free(engineList[i]);
11213         engineList[i] = strdup(buf);
11214         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11215         if(engineMnemonic[i]) free(engineMnemonic[i]);
11216         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11217             strcat(buf, " (");
11218             sscanf(q + 8, "%s", buf + strlen(buf));
11219             strcat(buf, ")");
11220         }
11221         engineMnemonic[i] = strdup(buf);
11222         i++;
11223     }
11224     engineList[i] = engineMnemonic[i] = NULL;
11225     return i;
11226 }
11227
11228 // following implemented as macro to avoid type limitations
11229 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11230
11231 void
11232 SwapEngines (int n)
11233 {   // swap settings for first engine and other engine (so far only some selected options)
11234     int h;
11235     char *p;
11236     if(n == 0) return;
11237     SWAP(directory, p)
11238     SWAP(chessProgram, p)
11239     SWAP(isUCI, h)
11240     SWAP(hasOwnBookUCI, h)
11241     SWAP(protocolVersion, h)
11242     SWAP(reuse, h)
11243     SWAP(scoreIsAbsolute, h)
11244     SWAP(timeOdds, h)
11245     SWAP(logo, p)
11246     SWAP(pgnName, p)
11247     SWAP(pvSAN, h)
11248     SWAP(engOptions, p)
11249     SWAP(engInitString, p)
11250     SWAP(computerString, p)
11251     SWAP(features, p)
11252     SWAP(fenOverride, p)
11253     SWAP(NPS, h)
11254     SWAP(accumulateTC, h)
11255     SWAP(drawDepth, h)
11256     SWAP(host, p)
11257     SWAP(pseudo, h)
11258 }
11259
11260 int
11261 GetEngineLine (char *s, int n)
11262 {
11263     int i;
11264     char buf[MSG_SIZ];
11265     extern char *icsNames;
11266     if(!s || !*s) return 0;
11267     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11268     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11269     if(!mnemonic[i]) return 0;
11270     if(n == 11) return 1; // just testing if there was a match
11271     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11272     if(n == 1) SwapEngines(n);
11273     ParseArgsFromString(buf);
11274     if(n == 1) SwapEngines(n);
11275     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11276         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11277         ParseArgsFromString(buf);
11278     }
11279     return 1;
11280 }
11281
11282 int
11283 SetPlayer (int player, char *p)
11284 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11285     int i;
11286     char buf[MSG_SIZ], *engineName;
11287     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11288     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11289     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11290     if(mnemonic[i]) {
11291         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11292         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11293         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11294         ParseArgsFromString(buf);
11295     } else { // no engine with this nickname is installed!
11296         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11297         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11298         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11299         ModeHighlight();
11300         DisplayError(buf, 0);
11301         return 0;
11302     }
11303     free(engineName);
11304     return i;
11305 }
11306
11307 char *recentEngines;
11308
11309 void
11310 RecentEngineEvent (int nr)
11311 {
11312     int n;
11313 //    SwapEngines(1); // bump first to second
11314 //    ReplaceEngine(&second, 1); // and load it there
11315     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11316     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11317     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11318         ReplaceEngine(&first, 0);
11319         FloatToFront(&appData.recentEngineList, command[n]);
11320     }
11321 }
11322
11323 int
11324 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11325 {   // determine players from game number
11326     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11327
11328     if(appData.tourneyType == 0) {
11329         roundsPerCycle = (nPlayers - 1) | 1;
11330         pairingsPerRound = nPlayers / 2;
11331     } else if(appData.tourneyType > 0) {
11332         roundsPerCycle = nPlayers - appData.tourneyType;
11333         pairingsPerRound = appData.tourneyType;
11334     }
11335     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11336     gamesPerCycle = gamesPerRound * roundsPerCycle;
11337     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11338     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11339     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11340     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11341     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11342     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11343
11344     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11345     if(appData.roundSync) *syncInterval = gamesPerRound;
11346
11347     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11348
11349     if(appData.tourneyType == 0) {
11350         if(curPairing == (nPlayers-1)/2 ) {
11351             *whitePlayer = curRound;
11352             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11353         } else {
11354             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11355             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11356             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11357             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11358         }
11359     } else if(appData.tourneyType > 1) {
11360         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11361         *whitePlayer = curRound + appData.tourneyType;
11362     } else if(appData.tourneyType > 0) {
11363         *whitePlayer = curPairing;
11364         *blackPlayer = curRound + appData.tourneyType;
11365     }
11366
11367     // take care of white/black alternation per round.
11368     // For cycles and games this is already taken care of by default, derived from matchGame!
11369     return curRound & 1;
11370 }
11371
11372 int
11373 NextTourneyGame (int nr, int *swapColors)
11374 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11375     char *p, *q;
11376     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11377     FILE *tf;
11378     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11379     tf = fopen(appData.tourneyFile, "r");
11380     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11381     ParseArgsFromFile(tf); fclose(tf);
11382     InitTimeControls(); // TC might be altered from tourney file
11383
11384     nPlayers = CountPlayers(appData.participants); // count participants
11385     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11386     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11387
11388     if(syncInterval) {
11389         p = q = appData.results;
11390         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11391         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11392             DisplayMessage(_("Waiting for other game(s)"),"");
11393             waitingForGame = TRUE;
11394             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11395             return 0;
11396         }
11397         waitingForGame = FALSE;
11398     }
11399
11400     if(appData.tourneyType < 0) {
11401         if(nr>=0 && !pairingReceived) {
11402             char buf[1<<16];
11403             if(pairing.pr == NoProc) {
11404                 if(!appData.pairingEngine[0]) {
11405                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11406                     return 0;
11407                 }
11408                 StartChessProgram(&pairing); // starts the pairing engine
11409             }
11410             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11411             SendToProgram(buf, &pairing);
11412             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11413             SendToProgram(buf, &pairing);
11414             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11415         }
11416         pairingReceived = 0;                              // ... so we continue here
11417         *swapColors = 0;
11418         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11419         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11420         matchGame = 1; roundNr = nr / syncInterval + 1;
11421     }
11422
11423     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11424
11425     // redefine engines, engine dir, etc.
11426     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11427     if(first.pr == NoProc) {
11428       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11429       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11430     }
11431     if(second.pr == NoProc) {
11432       SwapEngines(1);
11433       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11434       SwapEngines(1);         // and make that valid for second engine by swapping
11435       InitEngine(&second, 1);
11436     }
11437     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11438     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11439     return OK;
11440 }
11441
11442 void
11443 NextMatchGame ()
11444 {   // performs game initialization that does not invoke engines, and then tries to start the game
11445     int res, firstWhite, swapColors = 0;
11446     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11447     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
11448         char buf[MSG_SIZ];
11449         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11450         if(strcmp(buf, currentDebugFile)) { // name has changed
11451             FILE *f = fopen(buf, "w");
11452             if(f) { // if opening the new file failed, just keep using the old one
11453                 ASSIGN(currentDebugFile, buf);
11454                 fclose(debugFP);
11455                 debugFP = f;
11456             }
11457             if(appData.serverFileName) {
11458                 if(serverFP) fclose(serverFP);
11459                 serverFP = fopen(appData.serverFileName, "w");
11460                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11461                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11462             }
11463         }
11464     }
11465     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11466     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11467     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11468     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11469     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11470     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11471     Reset(FALSE, first.pr != NoProc);
11472     res = LoadGameOrPosition(matchGame); // setup game
11473     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11474     if(!res) return; // abort when bad game/pos file
11475     if(appData.epd) {// in EPD mode we make sure first engine is to move
11476         firstWhite = !(forwardMostMove & 1);
11477         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11478         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11479     }
11480     TwoMachinesEvent();
11481 }
11482
11483 void
11484 UserAdjudicationEvent (int result)
11485 {
11486     ChessMove gameResult = GameIsDrawn;
11487
11488     if( result > 0 ) {
11489         gameResult = WhiteWins;
11490     }
11491     else if( result < 0 ) {
11492         gameResult = BlackWins;
11493     }
11494
11495     if( gameMode == TwoMachinesPlay ) {
11496         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11497     }
11498 }
11499
11500
11501 // [HGM] save: calculate checksum of game to make games easily identifiable
11502 int
11503 StringCheckSum (char *s)
11504 {
11505         int i = 0;
11506         if(s==NULL) return 0;
11507         while(*s) i = i*259 + *s++;
11508         return i;
11509 }
11510
11511 int
11512 GameCheckSum ()
11513 {
11514         int i, sum=0;
11515         for(i=backwardMostMove; i<forwardMostMove; i++) {
11516                 sum += pvInfoList[i].depth;
11517                 sum += StringCheckSum(parseList[i]);
11518                 sum += StringCheckSum(commentList[i]);
11519                 sum *= 261;
11520         }
11521         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11522         return sum + StringCheckSum(commentList[i]);
11523 } // end of save patch
11524
11525 void
11526 GameEnds (ChessMove result, char *resultDetails, int whosays)
11527 {
11528     GameMode nextGameMode;
11529     int isIcsGame;
11530     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11531
11532     if(endingGame) return; /* [HGM] crash: forbid recursion */
11533     endingGame = 1;
11534     if(twoBoards) { // [HGM] dual: switch back to one board
11535         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11536         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11537     }
11538     if (appData.debugMode) {
11539       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11540               result, resultDetails ? resultDetails : "(null)", whosays);
11541     }
11542
11543     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11544
11545     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11546
11547     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11548         /* If we are playing on ICS, the server decides when the
11549            game is over, but the engine can offer to draw, claim
11550            a draw, or resign.
11551          */
11552 #if ZIPPY
11553         if (appData.zippyPlay && first.initDone) {
11554             if (result == GameIsDrawn) {
11555                 /* In case draw still needs to be claimed */
11556                 SendToICS(ics_prefix);
11557                 SendToICS("draw\n");
11558             } else if (StrCaseStr(resultDetails, "resign")) {
11559                 SendToICS(ics_prefix);
11560                 SendToICS("resign\n");
11561             }
11562         }
11563 #endif
11564         endingGame = 0; /* [HGM] crash */
11565         return;
11566     }
11567
11568     /* If we're loading the game from a file, stop */
11569     if (whosays == GE_FILE) {
11570       (void) StopLoadGameTimer();
11571       gameFileFP = NULL;
11572     }
11573
11574     /* Cancel draw offers */
11575     first.offeredDraw = second.offeredDraw = 0;
11576
11577     /* If this is an ICS game, only ICS can really say it's done;
11578        if not, anyone can. */
11579     isIcsGame = (gameMode == IcsPlayingWhite ||
11580                  gameMode == IcsPlayingBlack ||
11581                  gameMode == IcsObserving    ||
11582                  gameMode == IcsExamining);
11583
11584     if (!isIcsGame || whosays == GE_ICS) {
11585         /* OK -- not an ICS game, or ICS said it was done */
11586         StopClocks();
11587         if (!isIcsGame && !appData.noChessProgram)
11588           SetUserThinkingEnables();
11589
11590         /* [HGM] if a machine claims the game end we verify this claim */
11591         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11592             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11593                 char claimer;
11594                 ChessMove trueResult = (ChessMove) -1;
11595
11596                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11597                                             first.twoMachinesColor[0] :
11598                                             second.twoMachinesColor[0] ;
11599
11600                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11601                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11602                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11603                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11604                 } else
11605                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11606                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11607                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11608                 } else
11609                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11610                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11611                 }
11612
11613                 // now verify win claims, but not in drop games, as we don't understand those yet
11614                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11615                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11616                     (result == WhiteWins && claimer == 'w' ||
11617                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11618                       if (appData.debugMode) {
11619                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11620                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11621                       }
11622                       if(result != trueResult) {
11623                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11624                               result = claimer == 'w' ? BlackWins : WhiteWins;
11625                               resultDetails = buf;
11626                       }
11627                 } else
11628                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11629                     && (forwardMostMove <= backwardMostMove ||
11630                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11631                         (claimer=='b')==(forwardMostMove&1))
11632                                                                                   ) {
11633                       /* [HGM] verify: draws that were not flagged are false claims */
11634                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11635                       result = claimer == 'w' ? BlackWins : WhiteWins;
11636                       resultDetails = buf;
11637                 }
11638                 /* (Claiming a loss is accepted no questions asked!) */
11639             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11640                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11641                 result = GameUnfinished;
11642                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11643             }
11644             /* [HGM] bare: don't allow bare King to win */
11645             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11646                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11647                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11648                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11649                && result != GameIsDrawn)
11650             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11651                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11652                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11653                         if(p >= 0 && p <= (int)WhiteKing) k++;
11654                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11655                 }
11656                 if (appData.debugMode) {
11657                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11658                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11659                 }
11660                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11661                         result = GameIsDrawn;
11662                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11663                         resultDetails = buf;
11664                 }
11665             }
11666         }
11667
11668
11669         if(serverMoves != NULL && !loadFlag) { char c = '=';
11670             if(result==WhiteWins) c = '+';
11671             if(result==BlackWins) c = '-';
11672             if(resultDetails != NULL)
11673                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11674         }
11675         if (resultDetails != NULL) {
11676             gameInfo.result = result;
11677             gameInfo.resultDetails = StrSave(resultDetails);
11678
11679             /* display last move only if game was not loaded from file */
11680             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11681                 DisplayMove(currentMove - 1);
11682
11683             if (forwardMostMove != 0) {
11684                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11685                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11686                                                                 ) {
11687                     if (*appData.saveGameFile != NULLCHAR) {
11688                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11689                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11690                         else
11691                         SaveGameToFile(appData.saveGameFile, TRUE);
11692                     } else if (appData.autoSaveGames) {
11693                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11694                     }
11695                     if (*appData.savePositionFile != NULLCHAR) {
11696                         SavePositionToFile(appData.savePositionFile);
11697                     }
11698                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11699                 }
11700             }
11701
11702             /* Tell program how game ended in case it is learning */
11703             /* [HGM] Moved this to after saving the PGN, just in case */
11704             /* engine died and we got here through time loss. In that */
11705             /* case we will get a fatal error writing the pipe, which */
11706             /* would otherwise lose us the PGN.                       */
11707             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11708             /* output during GameEnds should never be fatal anymore   */
11709             if (gameMode == MachinePlaysWhite ||
11710                 gameMode == MachinePlaysBlack ||
11711                 gameMode == TwoMachinesPlay ||
11712                 gameMode == IcsPlayingWhite ||
11713                 gameMode == IcsPlayingBlack ||
11714                 gameMode == BeginningOfGame) {
11715                 char buf[MSG_SIZ];
11716                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11717                         resultDetails);
11718                 if (first.pr != NoProc) {
11719                     SendToProgram(buf, &first);
11720                 }
11721                 if (second.pr != NoProc &&
11722                     gameMode == TwoMachinesPlay) {
11723                     SendToProgram(buf, &second);
11724                 }
11725             }
11726         }
11727
11728         if (appData.icsActive) {
11729             if (appData.quietPlay &&
11730                 (gameMode == IcsPlayingWhite ||
11731                  gameMode == IcsPlayingBlack)) {
11732                 SendToICS(ics_prefix);
11733                 SendToICS("set shout 1\n");
11734             }
11735             nextGameMode = IcsIdle;
11736             ics_user_moved = FALSE;
11737             /* clean up premove.  It's ugly when the game has ended and the
11738              * premove highlights are still on the board.
11739              */
11740             if (gotPremove) {
11741               gotPremove = FALSE;
11742               ClearPremoveHighlights();
11743               DrawPosition(FALSE, boards[currentMove]);
11744             }
11745             if (whosays == GE_ICS) {
11746                 switch (result) {
11747                 case WhiteWins:
11748                     if (gameMode == IcsPlayingWhite)
11749                         PlayIcsWinSound();
11750                     else if(gameMode == IcsPlayingBlack)
11751                         PlayIcsLossSound();
11752                     break;
11753                 case BlackWins:
11754                     if (gameMode == IcsPlayingBlack)
11755                         PlayIcsWinSound();
11756                     else if(gameMode == IcsPlayingWhite)
11757                         PlayIcsLossSound();
11758                     break;
11759                 case GameIsDrawn:
11760                     PlayIcsDrawSound();
11761                     break;
11762                 default:
11763                     PlayIcsUnfinishedSound();
11764                 }
11765             }
11766             if(appData.quitNext) { ExitEvent(0); return; }
11767         } else if (gameMode == EditGame ||
11768                    gameMode == PlayFromGameFile ||
11769                    gameMode == AnalyzeMode ||
11770                    gameMode == AnalyzeFile) {
11771             nextGameMode = gameMode;
11772         } else {
11773             nextGameMode = EndOfGame;
11774         }
11775         pausing = FALSE;
11776         ModeHighlight();
11777     } else {
11778         nextGameMode = gameMode;
11779     }
11780
11781     if (appData.noChessProgram) {
11782         gameMode = nextGameMode;
11783         ModeHighlight();
11784         endingGame = 0; /* [HGM] crash */
11785         return;
11786     }
11787
11788     if (first.reuse) {
11789         /* Put first chess program into idle state */
11790         if (first.pr != NoProc &&
11791             (gameMode == MachinePlaysWhite ||
11792              gameMode == MachinePlaysBlack ||
11793              gameMode == TwoMachinesPlay ||
11794              gameMode == IcsPlayingWhite ||
11795              gameMode == IcsPlayingBlack ||
11796              gameMode == BeginningOfGame)) {
11797             SendToProgram("force\n", &first);
11798             if (first.usePing) {
11799               char buf[MSG_SIZ];
11800               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11801               SendToProgram(buf, &first);
11802             }
11803         }
11804     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11805         /* Kill off first chess program */
11806         if (first.isr != NULL)
11807           RemoveInputSource(first.isr);
11808         first.isr = NULL;
11809
11810         if (first.pr != NoProc) {
11811             ExitAnalyzeMode();
11812             DoSleep( appData.delayBeforeQuit );
11813             SendToProgram("quit\n", &first);
11814             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11815             first.reload = TRUE;
11816         }
11817         first.pr = NoProc;
11818     }
11819     if (second.reuse) {
11820         /* Put second chess program into idle state */
11821         if (second.pr != NoProc &&
11822             gameMode == TwoMachinesPlay) {
11823             SendToProgram("force\n", &second);
11824             if (second.usePing) {
11825               char buf[MSG_SIZ];
11826               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11827               SendToProgram(buf, &second);
11828             }
11829         }
11830     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11831         /* Kill off second chess program */
11832         if (second.isr != NULL)
11833           RemoveInputSource(second.isr);
11834         second.isr = NULL;
11835
11836         if (second.pr != NoProc) {
11837             DoSleep( appData.delayBeforeQuit );
11838             SendToProgram("quit\n", &second);
11839             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11840             second.reload = TRUE;
11841         }
11842         second.pr = NoProc;
11843     }
11844
11845     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11846         char resChar = '=';
11847         switch (result) {
11848         case WhiteWins:
11849           resChar = '+';
11850           if (first.twoMachinesColor[0] == 'w') {
11851             first.matchWins++;
11852           } else {
11853             second.matchWins++;
11854           }
11855           break;
11856         case BlackWins:
11857           resChar = '-';
11858           if (first.twoMachinesColor[0] == 'b') {
11859             first.matchWins++;
11860           } else {
11861             second.matchWins++;
11862           }
11863           break;
11864         case GameUnfinished:
11865           resChar = ' ';
11866         default:
11867           break;
11868         }
11869
11870         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11871         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11872             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11873             ReserveGame(nextGame, resChar); // sets nextGame
11874             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11875             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11876         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11877
11878         if (nextGame <= appData.matchGames && !abortMatch) {
11879             gameMode = nextGameMode;
11880             matchGame = nextGame; // this will be overruled in tourney mode!
11881             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11882             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11883             endingGame = 0; /* [HGM] crash */
11884             return;
11885         } else {
11886             gameMode = nextGameMode;
11887             if(appData.epd) {
11888                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11889                 OutputKibitz(2, buf);
11890                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11891                 OutputKibitz(2, buf);
11892                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11893                 if(second.matchWins) OutputKibitz(2, buf);
11894                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11895                 OutputKibitz(2, buf);
11896             }
11897             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11898                      first.tidy, second.tidy,
11899                      first.matchWins, second.matchWins,
11900                      appData.matchGames - (first.matchWins + second.matchWins));
11901             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11902             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11903             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11904             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11905                 first.twoMachinesColor = "black\n";
11906                 second.twoMachinesColor = "white\n";
11907             } else {
11908                 first.twoMachinesColor = "white\n";
11909                 second.twoMachinesColor = "black\n";
11910             }
11911         }
11912     }
11913     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11914         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11915       ExitAnalyzeMode();
11916     gameMode = nextGameMode;
11917     ModeHighlight();
11918     endingGame = 0;  /* [HGM] crash */
11919     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11920         if(matchMode == TRUE) { // match through command line: exit with or without popup
11921             if(ranking) {
11922                 ToNrEvent(forwardMostMove);
11923                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11924                 else ExitEvent(0);
11925             } else DisplayFatalError(buf, 0, 0);
11926         } else { // match through menu; just stop, with or without popup
11927             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11928             ModeHighlight();
11929             if(ranking){
11930                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11931             } else DisplayNote(buf);
11932       }
11933       if(ranking) free(ranking);
11934     }
11935 }
11936
11937 /* Assumes program was just initialized (initString sent).
11938    Leaves program in force mode. */
11939 void
11940 FeedMovesToProgram (ChessProgramState *cps, int upto)
11941 {
11942     int i;
11943
11944     if (appData.debugMode)
11945       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11946               startedFromSetupPosition ? "position and " : "",
11947               backwardMostMove, upto, cps->which);
11948     if(currentlyInitializedVariant != gameInfo.variant) {
11949       char buf[MSG_SIZ];
11950         // [HGM] variantswitch: make engine aware of new variant
11951         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11952                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11953                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11954         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11955         SendToProgram(buf, cps);
11956         currentlyInitializedVariant = gameInfo.variant;
11957     }
11958     SendToProgram("force\n", cps);
11959     if (startedFromSetupPosition) {
11960         SendBoard(cps, backwardMostMove);
11961     if (appData.debugMode) {
11962         fprintf(debugFP, "feedMoves\n");
11963     }
11964     }
11965     for (i = backwardMostMove; i < upto; i++) {
11966         SendMoveToProgram(i, cps);
11967     }
11968 }
11969
11970
11971 int
11972 ResurrectChessProgram ()
11973 {
11974      /* The chess program may have exited.
11975         If so, restart it and feed it all the moves made so far. */
11976     static int doInit = 0;
11977
11978     if (appData.noChessProgram) return 1;
11979
11980     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11981         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11982         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11983         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11984     } else {
11985         if (first.pr != NoProc) return 1;
11986         StartChessProgram(&first);
11987     }
11988     InitChessProgram(&first, FALSE);
11989     FeedMovesToProgram(&first, currentMove);
11990
11991     if (!first.sendTime) {
11992         /* can't tell gnuchess what its clock should read,
11993            so we bow to its notion. */
11994         ResetClocks();
11995         timeRemaining[0][currentMove] = whiteTimeRemaining;
11996         timeRemaining[1][currentMove] = blackTimeRemaining;
11997     }
11998
11999     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12000                 appData.icsEngineAnalyze) && first.analysisSupport) {
12001       SendToProgram("analyze\n", &first);
12002       first.analyzing = TRUE;
12003     }
12004     return 1;
12005 }
12006
12007 /*
12008  * Button procedures
12009  */
12010 void
12011 Reset (int redraw, int init)
12012 {
12013     int i;
12014
12015     if (appData.debugMode) {
12016         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12017                 redraw, init, gameMode);
12018     }
12019     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12020     deadRanks = 0; // assume entire board is used
12021     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12022     CleanupTail(); // [HGM] vari: delete any stored variations
12023     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12024     pausing = pauseExamInvalid = FALSE;
12025     startedFromSetupPosition = blackPlaysFirst = FALSE;
12026     firstMove = TRUE;
12027     whiteFlag = blackFlag = FALSE;
12028     userOfferedDraw = FALSE;
12029     hintRequested = bookRequested = FALSE;
12030     first.maybeThinking = FALSE;
12031     second.maybeThinking = FALSE;
12032     first.bookSuspend = FALSE; // [HGM] book
12033     second.bookSuspend = FALSE;
12034     thinkOutput[0] = NULLCHAR;
12035     lastHint[0] = NULLCHAR;
12036     ClearGameInfo(&gameInfo);
12037     gameInfo.variant = StringToVariant(appData.variant);
12038     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12039         gameInfo.variant = VariantUnknown;
12040         strncpy(engineVariant, appData.variant, MSG_SIZ);
12041     }
12042     ics_user_moved = ics_clock_paused = FALSE;
12043     ics_getting_history = H_FALSE;
12044     ics_gamenum = -1;
12045     white_holding[0] = black_holding[0] = NULLCHAR;
12046     ClearProgramStats();
12047     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12048
12049     ResetFrontEnd();
12050     ClearHighlights();
12051     flipView = appData.flipView;
12052     ClearPremoveHighlights();
12053     gotPremove = FALSE;
12054     alarmSounded = FALSE;
12055     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12056
12057     GameEnds(EndOfFile, NULL, GE_PLAYER);
12058     if(appData.serverMovesName != NULL) {
12059         /* [HGM] prepare to make moves file for broadcasting */
12060         clock_t t = clock();
12061         if(serverMoves != NULL) fclose(serverMoves);
12062         serverMoves = fopen(appData.serverMovesName, "r");
12063         if(serverMoves != NULL) {
12064             fclose(serverMoves);
12065             /* delay 15 sec before overwriting, so all clients can see end */
12066             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12067         }
12068         serverMoves = fopen(appData.serverMovesName, "w");
12069     }
12070
12071     ExitAnalyzeMode();
12072     gameMode = BeginningOfGame;
12073     ModeHighlight();
12074     if(appData.icsActive) gameInfo.variant = VariantNormal;
12075     currentMove = forwardMostMove = backwardMostMove = 0;
12076     MarkTargetSquares(1);
12077     InitPosition(redraw);
12078     for (i = 0; i < MAX_MOVES; i++) {
12079         if (commentList[i] != NULL) {
12080             free(commentList[i]);
12081             commentList[i] = NULL;
12082         }
12083     }
12084     ResetClocks();
12085     timeRemaining[0][0] = whiteTimeRemaining;
12086     timeRemaining[1][0] = blackTimeRemaining;
12087
12088     if (first.pr == NoProc) {
12089         StartChessProgram(&first);
12090     }
12091     if (init) {
12092             InitChessProgram(&first, startedFromSetupPosition);
12093     }
12094     DisplayTitle("");
12095     DisplayMessage("", "");
12096     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12097     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12098     ClearMap();        // [HGM] exclude: invalidate map
12099 }
12100
12101 void
12102 AutoPlayGameLoop ()
12103 {
12104     for (;;) {
12105         if (!AutoPlayOneMove())
12106           return;
12107         if (matchMode || appData.timeDelay == 0)
12108           continue;
12109         if (appData.timeDelay < 0)
12110           return;
12111         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12112         break;
12113     }
12114 }
12115
12116 void
12117 AnalyzeNextGame()
12118 {
12119     ReloadGame(1); // next game
12120 }
12121
12122 int
12123 AutoPlayOneMove ()
12124 {
12125     int fromX, fromY, toX, toY;
12126
12127     if (appData.debugMode) {
12128       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12129     }
12130
12131     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12132       return FALSE;
12133
12134     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12135       pvInfoList[currentMove].depth = programStats.depth;
12136       pvInfoList[currentMove].score = programStats.score;
12137       pvInfoList[currentMove].time  = 0;
12138       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12139       else { // append analysis of final position as comment
12140         char buf[MSG_SIZ];
12141         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12142         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12143       }
12144       programStats.depth = 0;
12145     }
12146
12147     if (currentMove >= forwardMostMove) {
12148       if(gameMode == AnalyzeFile) {
12149           if(appData.loadGameIndex == -1) {
12150             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12151           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12152           } else {
12153           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12154         }
12155       }
12156 //      gameMode = EndOfGame;
12157 //      ModeHighlight();
12158
12159       /* [AS] Clear current move marker at the end of a game */
12160       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12161
12162       return FALSE;
12163     }
12164
12165     toX = moveList[currentMove][2] - AAA;
12166     toY = moveList[currentMove][3] - ONE;
12167
12168     if (moveList[currentMove][1] == '@') {
12169         if (appData.highlightLastMove) {
12170             SetHighlights(-1, -1, toX, toY);
12171         }
12172     } else {
12173         fromX = moveList[currentMove][0] - AAA;
12174         fromY = moveList[currentMove][1] - ONE;
12175
12176         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12177
12178         if(moveList[currentMove][4] == ';') { // multi-leg
12179             killX = moveList[currentMove][5] - AAA;
12180             killY = moveList[currentMove][6] - ONE;
12181         }
12182         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12183         killX = killY = -1;
12184
12185         if (appData.highlightLastMove) {
12186             SetHighlights(fromX, fromY, toX, toY);
12187         }
12188     }
12189     DisplayMove(currentMove);
12190     SendMoveToProgram(currentMove++, &first);
12191     DisplayBothClocks();
12192     DrawPosition(FALSE, boards[currentMove]);
12193     // [HGM] PV info: always display, routine tests if empty
12194     DisplayComment(currentMove - 1, commentList[currentMove]);
12195     return TRUE;
12196 }
12197
12198
12199 int
12200 LoadGameOneMove (ChessMove readAhead)
12201 {
12202     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12203     char promoChar = NULLCHAR;
12204     ChessMove moveType;
12205     char move[MSG_SIZ];
12206     char *p, *q;
12207
12208     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12209         gameMode != AnalyzeMode && gameMode != Training) {
12210         gameFileFP = NULL;
12211         return FALSE;
12212     }
12213
12214     yyboardindex = forwardMostMove;
12215     if (readAhead != EndOfFile) {
12216       moveType = readAhead;
12217     } else {
12218       if (gameFileFP == NULL)
12219           return FALSE;
12220       moveType = (ChessMove) Myylex();
12221     }
12222
12223     done = FALSE;
12224     switch (moveType) {
12225       case Comment:
12226         if (appData.debugMode)
12227           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12228         p = yy_text;
12229
12230         /* append the comment but don't display it */
12231         AppendComment(currentMove, p, FALSE);
12232         return TRUE;
12233
12234       case WhiteCapturesEnPassant:
12235       case BlackCapturesEnPassant:
12236       case WhitePromotion:
12237       case BlackPromotion:
12238       case WhiteNonPromotion:
12239       case BlackNonPromotion:
12240       case NormalMove:
12241       case FirstLeg:
12242       case WhiteKingSideCastle:
12243       case WhiteQueenSideCastle:
12244       case BlackKingSideCastle:
12245       case BlackQueenSideCastle:
12246       case WhiteKingSideCastleWild:
12247       case WhiteQueenSideCastleWild:
12248       case BlackKingSideCastleWild:
12249       case BlackQueenSideCastleWild:
12250       /* PUSH Fabien */
12251       case WhiteHSideCastleFR:
12252       case WhiteASideCastleFR:
12253       case BlackHSideCastleFR:
12254       case BlackASideCastleFR:
12255       /* POP Fabien */
12256         if (appData.debugMode)
12257           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12258         fromX = currentMoveString[0] - AAA;
12259         fromY = currentMoveString[1] - ONE;
12260         toX = currentMoveString[2] - AAA;
12261         toY = currentMoveString[3] - ONE;
12262         promoChar = currentMoveString[4];
12263         if(promoChar == ';') promoChar = currentMoveString[7];
12264         break;
12265
12266       case WhiteDrop:
12267       case BlackDrop:
12268         if (appData.debugMode)
12269           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12270         fromX = moveType == WhiteDrop ?
12271           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12272         (int) CharToPiece(ToLower(currentMoveString[0]));
12273         fromY = DROP_RANK;
12274         toX = currentMoveString[2] - AAA;
12275         toY = currentMoveString[3] - ONE;
12276         break;
12277
12278       case WhiteWins:
12279       case BlackWins:
12280       case GameIsDrawn:
12281       case GameUnfinished:
12282         if (appData.debugMode)
12283           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12284         p = strchr(yy_text, '{');
12285         if (p == NULL) p = strchr(yy_text, '(');
12286         if (p == NULL) {
12287             p = yy_text;
12288             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12289         } else {
12290             q = strchr(p, *p == '{' ? '}' : ')');
12291             if (q != NULL) *q = NULLCHAR;
12292             p++;
12293         }
12294         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12295         GameEnds(moveType, p, GE_FILE);
12296         done = TRUE;
12297         if (cmailMsgLoaded) {
12298             ClearHighlights();
12299             flipView = WhiteOnMove(currentMove);
12300             if (moveType == GameUnfinished) flipView = !flipView;
12301             if (appData.debugMode)
12302               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12303         }
12304         break;
12305
12306       case EndOfFile:
12307         if (appData.debugMode)
12308           fprintf(debugFP, "Parser hit end of file\n");
12309         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12310           case MT_NONE:
12311           case MT_CHECK:
12312             break;
12313           case MT_CHECKMATE:
12314           case MT_STAINMATE:
12315             if (WhiteOnMove(currentMove)) {
12316                 GameEnds(BlackWins, "Black mates", GE_FILE);
12317             } else {
12318                 GameEnds(WhiteWins, "White mates", GE_FILE);
12319             }
12320             break;
12321           case MT_STALEMATE:
12322             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12323             break;
12324         }
12325         done = TRUE;
12326         break;
12327
12328       case MoveNumberOne:
12329         if (lastLoadGameStart == GNUChessGame) {
12330             /* GNUChessGames have numbers, but they aren't move numbers */
12331             if (appData.debugMode)
12332               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12333                       yy_text, (int) moveType);
12334             return LoadGameOneMove(EndOfFile); /* tail recursion */
12335         }
12336         /* else fall thru */
12337
12338       case XBoardGame:
12339       case GNUChessGame:
12340       case PGNTag:
12341         /* Reached start of next game in file */
12342         if (appData.debugMode)
12343           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12344         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12345           case MT_NONE:
12346           case MT_CHECK:
12347             break;
12348           case MT_CHECKMATE:
12349           case MT_STAINMATE:
12350             if (WhiteOnMove(currentMove)) {
12351                 GameEnds(BlackWins, "Black mates", GE_FILE);
12352             } else {
12353                 GameEnds(WhiteWins, "White mates", GE_FILE);
12354             }
12355             break;
12356           case MT_STALEMATE:
12357             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12358             break;
12359         }
12360         done = TRUE;
12361         break;
12362
12363       case PositionDiagram:     /* should not happen; ignore */
12364       case ElapsedTime:         /* ignore */
12365       case NAG:                 /* ignore */
12366         if (appData.debugMode)
12367           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12368                   yy_text, (int) moveType);
12369         return LoadGameOneMove(EndOfFile); /* tail recursion */
12370
12371       case IllegalMove:
12372         if (appData.testLegality) {
12373             if (appData.debugMode)
12374               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12375             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12376                     (forwardMostMove / 2) + 1,
12377                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12378             DisplayError(move, 0);
12379             done = TRUE;
12380         } else {
12381             if (appData.debugMode)
12382               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12383                       yy_text, currentMoveString);
12384             if(currentMoveString[1] == '@') {
12385                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12386                 fromY = DROP_RANK;
12387             } else {
12388                 fromX = currentMoveString[0] - AAA;
12389                 fromY = currentMoveString[1] - ONE;
12390             }
12391             toX = currentMoveString[2] - AAA;
12392             toY = currentMoveString[3] - ONE;
12393             promoChar = currentMoveString[4];
12394         }
12395         break;
12396
12397       case AmbiguousMove:
12398         if (appData.debugMode)
12399           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12400         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12401                 (forwardMostMove / 2) + 1,
12402                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12403         DisplayError(move, 0);
12404         done = TRUE;
12405         break;
12406
12407       default:
12408       case ImpossibleMove:
12409         if (appData.debugMode)
12410           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12411         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12412                 (forwardMostMove / 2) + 1,
12413                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12414         DisplayError(move, 0);
12415         done = TRUE;
12416         break;
12417     }
12418
12419     if (done) {
12420         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12421             DrawPosition(FALSE, boards[currentMove]);
12422             DisplayBothClocks();
12423             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12424               DisplayComment(currentMove - 1, commentList[currentMove]);
12425         }
12426         (void) StopLoadGameTimer();
12427         gameFileFP = NULL;
12428         cmailOldMove = forwardMostMove;
12429         return FALSE;
12430     } else {
12431         /* currentMoveString is set as a side-effect of yylex */
12432
12433         thinkOutput[0] = NULLCHAR;
12434         MakeMove(fromX, fromY, toX, toY, promoChar);
12435         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12436         currentMove = forwardMostMove;
12437         return TRUE;
12438     }
12439 }
12440
12441 /* Load the nth game from the given file */
12442 int
12443 LoadGameFromFile (char *filename, int n, char *title, int useList)
12444 {
12445     FILE *f;
12446     char buf[MSG_SIZ];
12447
12448     if (strcmp(filename, "-") == 0) {
12449         f = stdin;
12450         title = "stdin";
12451     } else {
12452         f = fopen(filename, "rb");
12453         if (f == NULL) {
12454           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12455             DisplayError(buf, errno);
12456             return FALSE;
12457         }
12458     }
12459     if (fseek(f, 0, 0) == -1) {
12460         /* f is not seekable; probably a pipe */
12461         useList = FALSE;
12462     }
12463     if (useList && n == 0) {
12464         int error = GameListBuild(f);
12465         if (error) {
12466             DisplayError(_("Cannot build game list"), error);
12467         } else if (!ListEmpty(&gameList) &&
12468                    ((ListGame *) gameList.tailPred)->number > 1) {
12469             GameListPopUp(f, title);
12470             return TRUE;
12471         }
12472         GameListDestroy();
12473         n = 1;
12474     }
12475     if (n == 0) n = 1;
12476     return LoadGame(f, n, title, FALSE);
12477 }
12478
12479
12480 void
12481 MakeRegisteredMove ()
12482 {
12483     int fromX, fromY, toX, toY;
12484     char promoChar;
12485     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12486         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12487           case CMAIL_MOVE:
12488           case CMAIL_DRAW:
12489             if (appData.debugMode)
12490               fprintf(debugFP, "Restoring %s for game %d\n",
12491                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12492
12493             thinkOutput[0] = NULLCHAR;
12494             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12495             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12496             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12497             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12498             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12499             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12500             MakeMove(fromX, fromY, toX, toY, promoChar);
12501             ShowMove(fromX, fromY, toX, toY);
12502
12503             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12504               case MT_NONE:
12505               case MT_CHECK:
12506                 break;
12507
12508               case MT_CHECKMATE:
12509               case MT_STAINMATE:
12510                 if (WhiteOnMove(currentMove)) {
12511                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12512                 } else {
12513                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12514                 }
12515                 break;
12516
12517               case MT_STALEMATE:
12518                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12519                 break;
12520             }
12521
12522             break;
12523
12524           case CMAIL_RESIGN:
12525             if (WhiteOnMove(currentMove)) {
12526                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12527             } else {
12528                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12529             }
12530             break;
12531
12532           case CMAIL_ACCEPT:
12533             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12534             break;
12535
12536           default:
12537             break;
12538         }
12539     }
12540
12541     return;
12542 }
12543
12544 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12545 int
12546 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12547 {
12548     int retVal;
12549
12550     if (gameNumber > nCmailGames) {
12551         DisplayError(_("No more games in this message"), 0);
12552         return FALSE;
12553     }
12554     if (f == lastLoadGameFP) {
12555         int offset = gameNumber - lastLoadGameNumber;
12556         if (offset == 0) {
12557             cmailMsg[0] = NULLCHAR;
12558             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12559                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12560                 nCmailMovesRegistered--;
12561             }
12562             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12563             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12564                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12565             }
12566         } else {
12567             if (! RegisterMove()) return FALSE;
12568         }
12569     }
12570
12571     retVal = LoadGame(f, gameNumber, title, useList);
12572
12573     /* Make move registered during previous look at this game, if any */
12574     MakeRegisteredMove();
12575
12576     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12577         commentList[currentMove]
12578           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12579         DisplayComment(currentMove - 1, commentList[currentMove]);
12580     }
12581
12582     return retVal;
12583 }
12584
12585 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12586 int
12587 ReloadGame (int offset)
12588 {
12589     int gameNumber = lastLoadGameNumber + offset;
12590     if (lastLoadGameFP == NULL) {
12591         DisplayError(_("No game has been loaded yet"), 0);
12592         return FALSE;
12593     }
12594     if (gameNumber <= 0) {
12595         DisplayError(_("Can't back up any further"), 0);
12596         return FALSE;
12597     }
12598     if (cmailMsgLoaded) {
12599         return CmailLoadGame(lastLoadGameFP, gameNumber,
12600                              lastLoadGameTitle, lastLoadGameUseList);
12601     } else {
12602         return LoadGame(lastLoadGameFP, gameNumber,
12603                         lastLoadGameTitle, lastLoadGameUseList);
12604     }
12605 }
12606
12607 int keys[EmptySquare+1];
12608
12609 int
12610 PositionMatches (Board b1, Board b2)
12611 {
12612     int r, f, sum=0;
12613     switch(appData.searchMode) {
12614         case 1: return CompareWithRights(b1, b2);
12615         case 2:
12616             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12617                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12618             }
12619             return TRUE;
12620         case 3:
12621             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12622               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12623                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12624             }
12625             return sum==0;
12626         case 4:
12627             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12628                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12629             }
12630             return sum==0;
12631     }
12632     return TRUE;
12633 }
12634
12635 #define Q_PROMO  4
12636 #define Q_EP     3
12637 #define Q_BCASTL 2
12638 #define Q_WCASTL 1
12639
12640 int pieceList[256], quickBoard[256];
12641 ChessSquare pieceType[256] = { EmptySquare };
12642 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12643 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12644 int soughtTotal, turn;
12645 Boolean epOK, flipSearch;
12646
12647 typedef struct {
12648     unsigned char piece, to;
12649 } Move;
12650
12651 #define DSIZE (250000)
12652
12653 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12654 Move *moveDatabase = initialSpace;
12655 unsigned int movePtr, dataSize = DSIZE;
12656
12657 int
12658 MakePieceList (Board board, int *counts)
12659 {
12660     int r, f, n=Q_PROMO, total=0;
12661     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12662     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12663         int sq = f + (r<<4);
12664         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12665             quickBoard[sq] = ++n;
12666             pieceList[n] = sq;
12667             pieceType[n] = board[r][f];
12668             counts[board[r][f]]++;
12669             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12670             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12671             total++;
12672         }
12673     }
12674     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12675     return total;
12676 }
12677
12678 void
12679 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12680 {
12681     int sq = fromX + (fromY<<4);
12682     int piece = quickBoard[sq], rook;
12683     quickBoard[sq] = 0;
12684     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12685     if(piece == pieceList[1] && fromY == toY) {
12686       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12687         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12688         moveDatabase[movePtr++].piece = Q_WCASTL;
12689         quickBoard[sq] = piece;
12690         piece = quickBoard[from]; quickBoard[from] = 0;
12691         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12692       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12693         quickBoard[sq] = 0; // remove Rook
12694         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12695         moveDatabase[movePtr++].piece = Q_WCASTL;
12696         quickBoard[sq] = pieceList[1]; // put King
12697         piece = rook;
12698         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12699       }
12700     } else
12701     if(piece == pieceList[2] && fromY == toY) {
12702       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12703         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12704         moveDatabase[movePtr++].piece = Q_BCASTL;
12705         quickBoard[sq] = piece;
12706         piece = quickBoard[from]; quickBoard[from] = 0;
12707         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12708       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12709         quickBoard[sq] = 0; // remove Rook
12710         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12711         moveDatabase[movePtr++].piece = Q_BCASTL;
12712         quickBoard[sq] = pieceList[2]; // put King
12713         piece = rook;
12714         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12715       }
12716     } else
12717     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12718         quickBoard[(fromY<<4)+toX] = 0;
12719         moveDatabase[movePtr].piece = Q_EP;
12720         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12721         moveDatabase[movePtr].to = sq;
12722     } else
12723     if(promoPiece != pieceType[piece]) {
12724         moveDatabase[movePtr++].piece = Q_PROMO;
12725         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12726     }
12727     moveDatabase[movePtr].piece = piece;
12728     quickBoard[sq] = piece;
12729     movePtr++;
12730 }
12731
12732 int
12733 PackGame (Board board)
12734 {
12735     Move *newSpace = NULL;
12736     moveDatabase[movePtr].piece = 0; // terminate previous game
12737     if(movePtr > dataSize) {
12738         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12739         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12740         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12741         if(newSpace) {
12742             int i;
12743             Move *p = moveDatabase, *q = newSpace;
12744             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12745             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12746             moveDatabase = newSpace;
12747         } else { // calloc failed, we must be out of memory. Too bad...
12748             dataSize = 0; // prevent calloc events for all subsequent games
12749             return 0;     // and signal this one isn't cached
12750         }
12751     }
12752     movePtr++;
12753     MakePieceList(board, counts);
12754     return movePtr;
12755 }
12756
12757 int
12758 QuickCompare (Board board, int *minCounts, int *maxCounts)
12759 {   // compare according to search mode
12760     int r, f;
12761     switch(appData.searchMode)
12762     {
12763       case 1: // exact position match
12764         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12765         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12766             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12767         }
12768         break;
12769       case 2: // can have extra material on empty squares
12770         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12771             if(board[r][f] == EmptySquare) continue;
12772             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12773         }
12774         break;
12775       case 3: // material with exact Pawn structure
12776         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12777             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12778             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12779         } // fall through to material comparison
12780       case 4: // exact material
12781         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12782         break;
12783       case 6: // material range with given imbalance
12784         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12785         // fall through to range comparison
12786       case 5: // material range
12787         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12788     }
12789     return TRUE;
12790 }
12791
12792 int
12793 QuickScan (Board board, Move *move)
12794 {   // reconstruct game,and compare all positions in it
12795     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12796     do {
12797         int piece = move->piece;
12798         int to = move->to, from = pieceList[piece];
12799         if(found < 0) { // if already found just scan to game end for final piece count
12800           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12801            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12802            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12803                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12804             ) {
12805             static int lastCounts[EmptySquare+1];
12806             int i;
12807             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12808             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12809           } else stretch = 0;
12810           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12811           if(found >= 0 && !appData.minPieces) return found;
12812         }
12813         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12814           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12815           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12816             piece = (++move)->piece;
12817             from = pieceList[piece];
12818             counts[pieceType[piece]]--;
12819             pieceType[piece] = (ChessSquare) move->to;
12820             counts[move->to]++;
12821           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12822             counts[pieceType[quickBoard[to]]]--;
12823             quickBoard[to] = 0; total--;
12824             move++;
12825             continue;
12826           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12827             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12828             from  = pieceList[piece]; // so this must be King
12829             quickBoard[from] = 0;
12830             pieceList[piece] = to;
12831             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12832             quickBoard[from] = 0; // rook
12833             quickBoard[to] = piece;
12834             to = move->to; piece = move->piece;
12835             goto aftercastle;
12836           }
12837         }
12838         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12839         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12840         quickBoard[from] = 0;
12841       aftercastle:
12842         quickBoard[to] = piece;
12843         pieceList[piece] = to;
12844         cnt++; turn ^= 3;
12845         move++;
12846     } while(1);
12847 }
12848
12849 void
12850 InitSearch ()
12851 {
12852     int r, f;
12853     flipSearch = FALSE;
12854     CopyBoard(soughtBoard, boards[currentMove]);
12855     soughtTotal = MakePieceList(soughtBoard, maxSought);
12856     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12857     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12858     CopyBoard(reverseBoard, boards[currentMove]);
12859     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12860         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12861         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12862         reverseBoard[r][f] = piece;
12863     }
12864     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12865     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12866     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12867                  || (boards[currentMove][CASTLING][2] == NoRights ||
12868                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12869                  && (boards[currentMove][CASTLING][5] == NoRights ||
12870                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12871       ) {
12872         flipSearch = TRUE;
12873         CopyBoard(flipBoard, soughtBoard);
12874         CopyBoard(rotateBoard, reverseBoard);
12875         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12876             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12877             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12878         }
12879     }
12880     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12881     if(appData.searchMode >= 5) {
12882         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12883         MakePieceList(soughtBoard, minSought);
12884         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12885     }
12886     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12887         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12888 }
12889
12890 GameInfo dummyInfo;
12891 static int creatingBook;
12892
12893 int
12894 GameContainsPosition (FILE *f, ListGame *lg)
12895 {
12896     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12897     int fromX, fromY, toX, toY;
12898     char promoChar;
12899     static int initDone=FALSE;
12900
12901     // weed out games based on numerical tag comparison
12902     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12903     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12904     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12905     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12906     if(!initDone) {
12907         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12908         initDone = TRUE;
12909     }
12910     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12911     else CopyBoard(boards[scratch], initialPosition); // default start position
12912     if(lg->moves) {
12913         turn = btm + 1;
12914         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12915         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12916     }
12917     if(btm) plyNr++;
12918     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12919     fseek(f, lg->offset, 0);
12920     yynewfile(f);
12921     while(1) {
12922         yyboardindex = scratch;
12923         quickFlag = plyNr+1;
12924         next = Myylex();
12925         quickFlag = 0;
12926         switch(next) {
12927             case PGNTag:
12928                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12929             default:
12930                 continue;
12931
12932             case XBoardGame:
12933             case GNUChessGame:
12934                 if(plyNr) return -1; // after we have seen moves, this is for new game
12935               continue;
12936
12937             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12938             case ImpossibleMove:
12939             case WhiteWins: // game ends here with these four
12940             case BlackWins:
12941             case GameIsDrawn:
12942             case GameUnfinished:
12943                 return -1;
12944
12945             case IllegalMove:
12946                 if(appData.testLegality) return -1;
12947             case WhiteCapturesEnPassant:
12948             case BlackCapturesEnPassant:
12949             case WhitePromotion:
12950             case BlackPromotion:
12951             case WhiteNonPromotion:
12952             case BlackNonPromotion:
12953             case NormalMove:
12954             case FirstLeg:
12955             case WhiteKingSideCastle:
12956             case WhiteQueenSideCastle:
12957             case BlackKingSideCastle:
12958             case BlackQueenSideCastle:
12959             case WhiteKingSideCastleWild:
12960             case WhiteQueenSideCastleWild:
12961             case BlackKingSideCastleWild:
12962             case BlackQueenSideCastleWild:
12963             case WhiteHSideCastleFR:
12964             case WhiteASideCastleFR:
12965             case BlackHSideCastleFR:
12966             case BlackASideCastleFR:
12967                 fromX = currentMoveString[0] - AAA;
12968                 fromY = currentMoveString[1] - ONE;
12969                 toX = currentMoveString[2] - AAA;
12970                 toY = currentMoveString[3] - ONE;
12971                 promoChar = currentMoveString[4];
12972                 break;
12973             case WhiteDrop:
12974             case BlackDrop:
12975                 fromX = next == WhiteDrop ?
12976                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12977                   (int) CharToPiece(ToLower(currentMoveString[0]));
12978                 fromY = DROP_RANK;
12979                 toX = currentMoveString[2] - AAA;
12980                 toY = currentMoveString[3] - ONE;
12981                 promoChar = 0;
12982                 break;
12983         }
12984         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12985         plyNr++;
12986         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12987         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12988         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12989         if(appData.findMirror) {
12990             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12991             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12992         }
12993     }
12994 }
12995
12996 /* Load the nth game from open file f */
12997 int
12998 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12999 {
13000     ChessMove cm;
13001     char buf[MSG_SIZ];
13002     int gn = gameNumber;
13003     ListGame *lg = NULL;
13004     int numPGNTags = 0, i;
13005     int err, pos = -1;
13006     GameMode oldGameMode;
13007     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13008     char oldName[MSG_SIZ];
13009
13010     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13011
13012     if (appData.debugMode)
13013         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13014
13015     if (gameMode == Training )
13016         SetTrainingModeOff();
13017
13018     oldGameMode = gameMode;
13019     if (gameMode != BeginningOfGame) {
13020       Reset(FALSE, TRUE);
13021     }
13022     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13023
13024     gameFileFP = f;
13025     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13026         fclose(lastLoadGameFP);
13027     }
13028
13029     if (useList) {
13030         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13031
13032         if (lg) {
13033             fseek(f, lg->offset, 0);
13034             GameListHighlight(gameNumber);
13035             pos = lg->position;
13036             gn = 1;
13037         }
13038         else {
13039             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13040               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13041             else
13042             DisplayError(_("Game number out of range"), 0);
13043             return FALSE;
13044         }
13045     } else {
13046         GameListDestroy();
13047         if (fseek(f, 0, 0) == -1) {
13048             if (f == lastLoadGameFP ?
13049                 gameNumber == lastLoadGameNumber + 1 :
13050                 gameNumber == 1) {
13051                 gn = 1;
13052             } else {
13053                 DisplayError(_("Can't seek on game file"), 0);
13054                 return FALSE;
13055             }
13056         }
13057     }
13058     lastLoadGameFP = f;
13059     lastLoadGameNumber = gameNumber;
13060     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13061     lastLoadGameUseList = useList;
13062
13063     yynewfile(f);
13064
13065     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13066       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13067                 lg->gameInfo.black);
13068             DisplayTitle(buf);
13069     } else if (*title != NULLCHAR) {
13070         if (gameNumber > 1) {
13071           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13072             DisplayTitle(buf);
13073         } else {
13074             DisplayTitle(title);
13075         }
13076     }
13077
13078     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13079         gameMode = PlayFromGameFile;
13080         ModeHighlight();
13081     }
13082
13083     currentMove = forwardMostMove = backwardMostMove = 0;
13084     CopyBoard(boards[0], initialPosition);
13085     StopClocks();
13086
13087     /*
13088      * Skip the first gn-1 games in the file.
13089      * Also skip over anything that precedes an identifiable
13090      * start of game marker, to avoid being confused by
13091      * garbage at the start of the file.  Currently
13092      * recognized start of game markers are the move number "1",
13093      * the pattern "gnuchess .* game", the pattern
13094      * "^[#;%] [^ ]* game file", and a PGN tag block.
13095      * A game that starts with one of the latter two patterns
13096      * will also have a move number 1, possibly
13097      * following a position diagram.
13098      * 5-4-02: Let's try being more lenient and allowing a game to
13099      * start with an unnumbered move.  Does that break anything?
13100      */
13101     cm = lastLoadGameStart = EndOfFile;
13102     while (gn > 0) {
13103         yyboardindex = forwardMostMove;
13104         cm = (ChessMove) Myylex();
13105         switch (cm) {
13106           case EndOfFile:
13107             if (cmailMsgLoaded) {
13108                 nCmailGames = CMAIL_MAX_GAMES - gn;
13109             } else {
13110                 Reset(TRUE, TRUE);
13111                 DisplayError(_("Game not found in file"), 0);
13112             }
13113             return FALSE;
13114
13115           case GNUChessGame:
13116           case XBoardGame:
13117             gn--;
13118             lastLoadGameStart = cm;
13119             break;
13120
13121           case MoveNumberOne:
13122             switch (lastLoadGameStart) {
13123               case GNUChessGame:
13124               case XBoardGame:
13125               case PGNTag:
13126                 break;
13127               case MoveNumberOne:
13128               case EndOfFile:
13129                 gn--;           /* count this game */
13130                 lastLoadGameStart = cm;
13131                 break;
13132               default:
13133                 /* impossible */
13134                 break;
13135             }
13136             break;
13137
13138           case PGNTag:
13139             switch (lastLoadGameStart) {
13140               case GNUChessGame:
13141               case PGNTag:
13142               case MoveNumberOne:
13143               case EndOfFile:
13144                 gn--;           /* count this game */
13145                 lastLoadGameStart = cm;
13146                 break;
13147               case XBoardGame:
13148                 lastLoadGameStart = cm; /* game counted already */
13149                 break;
13150               default:
13151                 /* impossible */
13152                 break;
13153             }
13154             if (gn > 0) {
13155                 do {
13156                     yyboardindex = forwardMostMove;
13157                     cm = (ChessMove) Myylex();
13158                 } while (cm == PGNTag || cm == Comment);
13159             }
13160             break;
13161
13162           case WhiteWins:
13163           case BlackWins:
13164           case GameIsDrawn:
13165             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13166                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13167                     != CMAIL_OLD_RESULT) {
13168                     nCmailResults ++ ;
13169                     cmailResult[  CMAIL_MAX_GAMES
13170                                 - gn - 1] = CMAIL_OLD_RESULT;
13171                 }
13172             }
13173             break;
13174
13175           case NormalMove:
13176           case FirstLeg:
13177             /* Only a NormalMove can be at the start of a game
13178              * without a position diagram. */
13179             if (lastLoadGameStart == EndOfFile ) {
13180               gn--;
13181               lastLoadGameStart = MoveNumberOne;
13182             }
13183             break;
13184
13185           default:
13186             break;
13187         }
13188     }
13189
13190     if (appData.debugMode)
13191       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13192
13193     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13194
13195     if (cm == XBoardGame) {
13196         /* Skip any header junk before position diagram and/or move 1 */
13197         for (;;) {
13198             yyboardindex = forwardMostMove;
13199             cm = (ChessMove) Myylex();
13200
13201             if (cm == EndOfFile ||
13202                 cm == GNUChessGame || cm == XBoardGame) {
13203                 /* Empty game; pretend end-of-file and handle later */
13204                 cm = EndOfFile;
13205                 break;
13206             }
13207
13208             if (cm == MoveNumberOne || cm == PositionDiagram ||
13209                 cm == PGNTag || cm == Comment)
13210               break;
13211         }
13212     } else if (cm == GNUChessGame) {
13213         if (gameInfo.event != NULL) {
13214             free(gameInfo.event);
13215         }
13216         gameInfo.event = StrSave(yy_text);
13217     }
13218
13219     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13220     while (cm == PGNTag) {
13221         if (appData.debugMode)
13222           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13223         err = ParsePGNTag(yy_text, &gameInfo);
13224         if (!err) numPGNTags++;
13225
13226         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13227         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13228             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13229             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13230             InitPosition(TRUE);
13231             oldVariant = gameInfo.variant;
13232             if (appData.debugMode)
13233               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13234         }
13235
13236
13237         if (gameInfo.fen != NULL) {
13238           Board initial_position;
13239           startedFromSetupPosition = TRUE;
13240           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13241             Reset(TRUE, TRUE);
13242             DisplayError(_("Bad FEN position in file"), 0);
13243             return FALSE;
13244           }
13245           CopyBoard(boards[0], initial_position);
13246           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13247             CopyBoard(initialPosition, initial_position);
13248           if (blackPlaysFirst) {
13249             currentMove = forwardMostMove = backwardMostMove = 1;
13250             CopyBoard(boards[1], initial_position);
13251             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13252             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13253             timeRemaining[0][1] = whiteTimeRemaining;
13254             timeRemaining[1][1] = blackTimeRemaining;
13255             if (commentList[0] != NULL) {
13256               commentList[1] = commentList[0];
13257               commentList[0] = NULL;
13258             }
13259           } else {
13260             currentMove = forwardMostMove = backwardMostMove = 0;
13261           }
13262           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13263           {   int i;
13264               initialRulePlies = FENrulePlies;
13265               for( i=0; i< nrCastlingRights; i++ )
13266                   initialRights[i] = initial_position[CASTLING][i];
13267           }
13268           yyboardindex = forwardMostMove;
13269           free(gameInfo.fen);
13270           gameInfo.fen = NULL;
13271         }
13272
13273         yyboardindex = forwardMostMove;
13274         cm = (ChessMove) Myylex();
13275
13276         /* Handle comments interspersed among the tags */
13277         while (cm == Comment) {
13278             char *p;
13279             if (appData.debugMode)
13280               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13281             p = yy_text;
13282             AppendComment(currentMove, p, FALSE);
13283             yyboardindex = forwardMostMove;
13284             cm = (ChessMove) Myylex();
13285         }
13286     }
13287
13288     /* don't rely on existence of Event tag since if game was
13289      * pasted from clipboard the Event tag may not exist
13290      */
13291     if (numPGNTags > 0){
13292         char *tags;
13293         if (gameInfo.variant == VariantNormal) {
13294           VariantClass v = StringToVariant(gameInfo.event);
13295           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13296           if(v < VariantShogi) gameInfo.variant = v;
13297         }
13298         if (!matchMode) {
13299           if( appData.autoDisplayTags ) {
13300             tags = PGNTags(&gameInfo);
13301             TagsPopUp(tags, CmailMsg());
13302             free(tags);
13303           }
13304         }
13305     } else {
13306         /* Make something up, but don't display it now */
13307         SetGameInfo();
13308         TagsPopDown();
13309     }
13310
13311     if (cm == PositionDiagram) {
13312         int i, j;
13313         char *p;
13314         Board initial_position;
13315
13316         if (appData.debugMode)
13317           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13318
13319         if (!startedFromSetupPosition) {
13320             p = yy_text;
13321             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13322               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13323                 switch (*p) {
13324                   case '{':
13325                   case '[':
13326                   case '-':
13327                   case ' ':
13328                   case '\t':
13329                   case '\n':
13330                   case '\r':
13331                     break;
13332                   default:
13333                     initial_position[i][j++] = CharToPiece(*p);
13334                     break;
13335                 }
13336             while (*p == ' ' || *p == '\t' ||
13337                    *p == '\n' || *p == '\r') p++;
13338
13339             if (strncmp(p, "black", strlen("black"))==0)
13340               blackPlaysFirst = TRUE;
13341             else
13342               blackPlaysFirst = FALSE;
13343             startedFromSetupPosition = TRUE;
13344
13345             CopyBoard(boards[0], initial_position);
13346             if (blackPlaysFirst) {
13347                 currentMove = forwardMostMove = backwardMostMove = 1;
13348                 CopyBoard(boards[1], initial_position);
13349                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13350                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13351                 timeRemaining[0][1] = whiteTimeRemaining;
13352                 timeRemaining[1][1] = blackTimeRemaining;
13353                 if (commentList[0] != NULL) {
13354                     commentList[1] = commentList[0];
13355                     commentList[0] = NULL;
13356                 }
13357             } else {
13358                 currentMove = forwardMostMove = backwardMostMove = 0;
13359             }
13360         }
13361         yyboardindex = forwardMostMove;
13362         cm = (ChessMove) Myylex();
13363     }
13364
13365   if(!creatingBook) {
13366     if (first.pr == NoProc) {
13367         StartChessProgram(&first);
13368     }
13369     InitChessProgram(&first, FALSE);
13370     if(gameInfo.variant == VariantUnknown && *oldName) {
13371         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13372         gameInfo.variant = v;
13373     }
13374     SendToProgram("force\n", &first);
13375     if (startedFromSetupPosition) {
13376         SendBoard(&first, forwardMostMove);
13377     if (appData.debugMode) {
13378         fprintf(debugFP, "Load Game\n");
13379     }
13380         DisplayBothClocks();
13381     }
13382   }
13383
13384     /* [HGM] server: flag to write setup moves in broadcast file as one */
13385     loadFlag = appData.suppressLoadMoves;
13386
13387     while (cm == Comment) {
13388         char *p;
13389         if (appData.debugMode)
13390           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13391         p = yy_text;
13392         AppendComment(currentMove, p, FALSE);
13393         yyboardindex = forwardMostMove;
13394         cm = (ChessMove) Myylex();
13395     }
13396
13397     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13398         cm == WhiteWins || cm == BlackWins ||
13399         cm == GameIsDrawn || cm == GameUnfinished) {
13400         DisplayMessage("", _("No moves in game"));
13401         if (cmailMsgLoaded) {
13402             if (appData.debugMode)
13403               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13404             ClearHighlights();
13405             flipView = FALSE;
13406         }
13407         DrawPosition(FALSE, boards[currentMove]);
13408         DisplayBothClocks();
13409         gameMode = EditGame;
13410         ModeHighlight();
13411         gameFileFP = NULL;
13412         cmailOldMove = 0;
13413         return TRUE;
13414     }
13415
13416     // [HGM] PV info: routine tests if comment empty
13417     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13418         DisplayComment(currentMove - 1, commentList[currentMove]);
13419     }
13420     if (!matchMode && appData.timeDelay != 0)
13421       DrawPosition(FALSE, boards[currentMove]);
13422
13423     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13424       programStats.ok_to_send = 1;
13425     }
13426
13427     /* if the first token after the PGN tags is a move
13428      * and not move number 1, retrieve it from the parser
13429      */
13430     if (cm != MoveNumberOne)
13431         LoadGameOneMove(cm);
13432
13433     /* load the remaining moves from the file */
13434     while (LoadGameOneMove(EndOfFile)) {
13435       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13436       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13437     }
13438
13439     /* rewind to the start of the game */
13440     currentMove = backwardMostMove;
13441
13442     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13443
13444     if (oldGameMode == AnalyzeFile) {
13445       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13446       AnalyzeFileEvent();
13447     } else
13448     if (oldGameMode == AnalyzeMode) {
13449       AnalyzeFileEvent();
13450     }
13451
13452     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13453         long int w, b; // [HGM] adjourn: restore saved clock times
13454         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13455         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13456             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13457             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13458         }
13459     }
13460
13461     if(creatingBook) return TRUE;
13462     if (!matchMode && pos > 0) {
13463         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13464     } else
13465     if (matchMode || appData.timeDelay == 0) {
13466       ToEndEvent();
13467     } else if (appData.timeDelay > 0) {
13468       AutoPlayGameLoop();
13469     }
13470
13471     if (appData.debugMode)
13472         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13473
13474     loadFlag = 0; /* [HGM] true game starts */
13475     return TRUE;
13476 }
13477
13478 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13479 int
13480 ReloadPosition (int offset)
13481 {
13482     int positionNumber = lastLoadPositionNumber + offset;
13483     if (lastLoadPositionFP == NULL) {
13484         DisplayError(_("No position has been loaded yet"), 0);
13485         return FALSE;
13486     }
13487     if (positionNumber <= 0) {
13488         DisplayError(_("Can't back up any further"), 0);
13489         return FALSE;
13490     }
13491     return LoadPosition(lastLoadPositionFP, positionNumber,
13492                         lastLoadPositionTitle);
13493 }
13494
13495 /* Load the nth position from the given file */
13496 int
13497 LoadPositionFromFile (char *filename, int n, char *title)
13498 {
13499     FILE *f;
13500     char buf[MSG_SIZ];
13501
13502     if (strcmp(filename, "-") == 0) {
13503         return LoadPosition(stdin, n, "stdin");
13504     } else {
13505         f = fopen(filename, "rb");
13506         if (f == NULL) {
13507             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13508             DisplayError(buf, errno);
13509             return FALSE;
13510         } else {
13511             return LoadPosition(f, n, title);
13512         }
13513     }
13514 }
13515
13516 /* Load the nth position from the given open file, and close it */
13517 int
13518 LoadPosition (FILE *f, int positionNumber, char *title)
13519 {
13520     char *p, line[MSG_SIZ];
13521     Board initial_position;
13522     int i, j, fenMode, pn;
13523
13524     if (gameMode == Training )
13525         SetTrainingModeOff();
13526
13527     if (gameMode != BeginningOfGame) {
13528         Reset(FALSE, TRUE);
13529     }
13530     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13531         fclose(lastLoadPositionFP);
13532     }
13533     if (positionNumber == 0) positionNumber = 1;
13534     lastLoadPositionFP = f;
13535     lastLoadPositionNumber = positionNumber;
13536     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13537     if (first.pr == NoProc && !appData.noChessProgram) {
13538       StartChessProgram(&first);
13539       InitChessProgram(&first, FALSE);
13540     }
13541     pn = positionNumber;
13542     if (positionNumber < 0) {
13543         /* Negative position number means to seek to that byte offset */
13544         if (fseek(f, -positionNumber, 0) == -1) {
13545             DisplayError(_("Can't seek on position file"), 0);
13546             return FALSE;
13547         };
13548         pn = 1;
13549     } else {
13550         if (fseek(f, 0, 0) == -1) {
13551             if (f == lastLoadPositionFP ?
13552                 positionNumber == lastLoadPositionNumber + 1 :
13553                 positionNumber == 1) {
13554                 pn = 1;
13555             } else {
13556                 DisplayError(_("Can't seek on position file"), 0);
13557                 return FALSE;
13558             }
13559         }
13560     }
13561     /* See if this file is FEN or old-style xboard */
13562     if (fgets(line, MSG_SIZ, f) == NULL) {
13563         DisplayError(_("Position not found in file"), 0);
13564         return FALSE;
13565     }
13566     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13567     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13568
13569     if (pn >= 2) {
13570         if (fenMode || line[0] == '#') pn--;
13571         while (pn > 0) {
13572             /* skip positions before number pn */
13573             if (fgets(line, MSG_SIZ, f) == NULL) {
13574                 Reset(TRUE, TRUE);
13575                 DisplayError(_("Position not found in file"), 0);
13576                 return FALSE;
13577             }
13578             if (fenMode || line[0] == '#') pn--;
13579         }
13580     }
13581
13582     if (fenMode) {
13583         char *p;
13584         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13585             DisplayError(_("Bad FEN position in file"), 0);
13586             return FALSE;
13587         }
13588         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13589             sscanf(p+4, "%[^;]", bestMove);
13590         } else *bestMove = NULLCHAR;
13591         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13592             sscanf(p+4, "%[^;]", avoidMove);
13593         } else *avoidMove = NULLCHAR;
13594     } else {
13595         (void) fgets(line, MSG_SIZ, f);
13596         (void) fgets(line, MSG_SIZ, f);
13597
13598         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13599             (void) fgets(line, MSG_SIZ, f);
13600             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13601                 if (*p == ' ')
13602                   continue;
13603                 initial_position[i][j++] = CharToPiece(*p);
13604             }
13605         }
13606
13607         blackPlaysFirst = FALSE;
13608         if (!feof(f)) {
13609             (void) fgets(line, MSG_SIZ, f);
13610             if (strncmp(line, "black", strlen("black"))==0)
13611               blackPlaysFirst = TRUE;
13612         }
13613     }
13614     startedFromSetupPosition = TRUE;
13615
13616     CopyBoard(boards[0], initial_position);
13617     if (blackPlaysFirst) {
13618         currentMove = forwardMostMove = backwardMostMove = 1;
13619         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13620         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13621         CopyBoard(boards[1], initial_position);
13622         DisplayMessage("", _("Black to play"));
13623     } else {
13624         currentMove = forwardMostMove = backwardMostMove = 0;
13625         DisplayMessage("", _("White to play"));
13626     }
13627     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13628     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13629         SendToProgram("force\n", &first);
13630         SendBoard(&first, forwardMostMove);
13631     }
13632     if (appData.debugMode) {
13633 int i, j;
13634   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13635   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13636         fprintf(debugFP, "Load Position\n");
13637     }
13638
13639     if (positionNumber > 1) {
13640       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13641         DisplayTitle(line);
13642     } else {
13643         DisplayTitle(title);
13644     }
13645     gameMode = EditGame;
13646     ModeHighlight();
13647     ResetClocks();
13648     timeRemaining[0][1] = whiteTimeRemaining;
13649     timeRemaining[1][1] = blackTimeRemaining;
13650     DrawPosition(FALSE, boards[currentMove]);
13651
13652     return TRUE;
13653 }
13654
13655
13656 void
13657 CopyPlayerNameIntoFileName (char **dest, char *src)
13658 {
13659     while (*src != NULLCHAR && *src != ',') {
13660         if (*src == ' ') {
13661             *(*dest)++ = '_';
13662             src++;
13663         } else {
13664             *(*dest)++ = *src++;
13665         }
13666     }
13667 }
13668
13669 char *
13670 DefaultFileName (char *ext)
13671 {
13672     static char def[MSG_SIZ];
13673     char *p;
13674
13675     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13676         p = def;
13677         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13678         *p++ = '-';
13679         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13680         *p++ = '.';
13681         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13682     } else {
13683         def[0] = NULLCHAR;
13684     }
13685     return def;
13686 }
13687
13688 /* Save the current game to the given file */
13689 int
13690 SaveGameToFile (char *filename, int append)
13691 {
13692     FILE *f;
13693     char buf[MSG_SIZ];
13694     int result, i, t,tot=0;
13695
13696     if (strcmp(filename, "-") == 0) {
13697         return SaveGame(stdout, 0, NULL);
13698     } else {
13699         for(i=0; i<10; i++) { // upto 10 tries
13700              f = fopen(filename, append ? "a" : "w");
13701              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13702              if(f || errno != 13) break;
13703              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13704              tot += t;
13705         }
13706         if (f == NULL) {
13707             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13708             DisplayError(buf, errno);
13709             return FALSE;
13710         } else {
13711             safeStrCpy(buf, lastMsg, MSG_SIZ);
13712             DisplayMessage(_("Waiting for access to save file"), "");
13713             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13714             DisplayMessage(_("Saving game"), "");
13715             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13716             result = SaveGame(f, 0, NULL);
13717             DisplayMessage(buf, "");
13718             return result;
13719         }
13720     }
13721 }
13722
13723 char *
13724 SavePart (char *str)
13725 {
13726     static char buf[MSG_SIZ];
13727     char *p;
13728
13729     p = strchr(str, ' ');
13730     if (p == NULL) return str;
13731     strncpy(buf, str, p - str);
13732     buf[p - str] = NULLCHAR;
13733     return buf;
13734 }
13735
13736 #define PGN_MAX_LINE 75
13737
13738 #define PGN_SIDE_WHITE  0
13739 #define PGN_SIDE_BLACK  1
13740
13741 static int
13742 FindFirstMoveOutOfBook (int side)
13743 {
13744     int result = -1;
13745
13746     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13747         int index = backwardMostMove;
13748         int has_book_hit = 0;
13749
13750         if( (index % 2) != side ) {
13751             index++;
13752         }
13753
13754         while( index < forwardMostMove ) {
13755             /* Check to see if engine is in book */
13756             int depth = pvInfoList[index].depth;
13757             int score = pvInfoList[index].score;
13758             int in_book = 0;
13759
13760             if( depth <= 2 ) {
13761                 in_book = 1;
13762             }
13763             else if( score == 0 && depth == 63 ) {
13764                 in_book = 1; /* Zappa */
13765             }
13766             else if( score == 2 && depth == 99 ) {
13767                 in_book = 1; /* Abrok */
13768             }
13769
13770             has_book_hit += in_book;
13771
13772             if( ! in_book ) {
13773                 result = index;
13774
13775                 break;
13776             }
13777
13778             index += 2;
13779         }
13780     }
13781
13782     return result;
13783 }
13784
13785 void
13786 GetOutOfBookInfo (char * buf)
13787 {
13788     int oob[2];
13789     int i;
13790     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13791
13792     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13793     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13794
13795     *buf = '\0';
13796
13797     if( oob[0] >= 0 || oob[1] >= 0 ) {
13798         for( i=0; i<2; i++ ) {
13799             int idx = oob[i];
13800
13801             if( idx >= 0 ) {
13802                 if( i > 0 && oob[0] >= 0 ) {
13803                     strcat( buf, "   " );
13804                 }
13805
13806                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13807                 sprintf( buf+strlen(buf), "%s%.2f",
13808                     pvInfoList[idx].score >= 0 ? "+" : "",
13809                     pvInfoList[idx].score / 100.0 );
13810             }
13811         }
13812     }
13813 }
13814
13815 /* Save game in PGN style */
13816 static void
13817 SaveGamePGN2 (FILE *f)
13818 {
13819     int i, offset, linelen, newblock;
13820 //    char *movetext;
13821     char numtext[32];
13822     int movelen, numlen, blank;
13823     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13824
13825     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13826
13827     PrintPGNTags(f, &gameInfo);
13828
13829     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13830
13831     if (backwardMostMove > 0 || startedFromSetupPosition) {
13832         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13833         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13834         fprintf(f, "\n{--------------\n");
13835         PrintPosition(f, backwardMostMove);
13836         fprintf(f, "--------------}\n");
13837         free(fen);
13838     }
13839     else {
13840         /* [AS] Out of book annotation */
13841         if( appData.saveOutOfBookInfo ) {
13842             char buf[64];
13843
13844             GetOutOfBookInfo( buf );
13845
13846             if( buf[0] != '\0' ) {
13847                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13848             }
13849         }
13850
13851         fprintf(f, "\n");
13852     }
13853
13854     i = backwardMostMove;
13855     linelen = 0;
13856     newblock = TRUE;
13857
13858     while (i < forwardMostMove) {
13859         /* Print comments preceding this move */
13860         if (commentList[i] != NULL) {
13861             if (linelen > 0) fprintf(f, "\n");
13862             fprintf(f, "%s", commentList[i]);
13863             linelen = 0;
13864             newblock = TRUE;
13865         }
13866
13867         /* Format move number */
13868         if ((i % 2) == 0)
13869           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13870         else
13871           if (newblock)
13872             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13873           else
13874             numtext[0] = NULLCHAR;
13875
13876         numlen = strlen(numtext);
13877         newblock = FALSE;
13878
13879         /* Print move number */
13880         blank = linelen > 0 && numlen > 0;
13881         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13882             fprintf(f, "\n");
13883             linelen = 0;
13884             blank = 0;
13885         }
13886         if (blank) {
13887             fprintf(f, " ");
13888             linelen++;
13889         }
13890         fprintf(f, "%s", numtext);
13891         linelen += numlen;
13892
13893         /* Get move */
13894         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13895         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13896
13897         /* Print move */
13898         blank = linelen > 0 && movelen > 0;
13899         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13900             fprintf(f, "\n");
13901             linelen = 0;
13902             blank = 0;
13903         }
13904         if (blank) {
13905             fprintf(f, " ");
13906             linelen++;
13907         }
13908         fprintf(f, "%s", move_buffer);
13909         linelen += movelen;
13910
13911         /* [AS] Add PV info if present */
13912         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13913             /* [HGM] add time */
13914             char buf[MSG_SIZ]; int seconds;
13915
13916             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13917
13918             if( seconds <= 0)
13919               buf[0] = 0;
13920             else
13921               if( seconds < 30 )
13922                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13923               else
13924                 {
13925                   seconds = (seconds + 4)/10; // round to full seconds
13926                   if( seconds < 60 )
13927                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13928                   else
13929                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13930                 }
13931
13932             if(appData.cumulativeTimePGN) {
13933                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13934             }
13935
13936             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13937                       pvInfoList[i].score >= 0 ? "+" : "",
13938                       pvInfoList[i].score / 100.0,
13939                       pvInfoList[i].depth,
13940                       buf );
13941
13942             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13943
13944             /* Print score/depth */
13945             blank = linelen > 0 && movelen > 0;
13946             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13947                 fprintf(f, "\n");
13948                 linelen = 0;
13949                 blank = 0;
13950             }
13951             if (blank) {
13952                 fprintf(f, " ");
13953                 linelen++;
13954             }
13955             fprintf(f, "%s", move_buffer);
13956             linelen += movelen;
13957         }
13958
13959         i++;
13960     }
13961
13962     /* Start a new line */
13963     if (linelen > 0) fprintf(f, "\n");
13964
13965     /* Print comments after last move */
13966     if (commentList[i] != NULL) {
13967         fprintf(f, "%s\n", commentList[i]);
13968     }
13969
13970     /* Print result */
13971     if (gameInfo.resultDetails != NULL &&
13972         gameInfo.resultDetails[0] != NULLCHAR) {
13973         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13974         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13975            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13976             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13977         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13978     } else {
13979         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13980     }
13981 }
13982
13983 /* Save game in PGN style and close the file */
13984 int
13985 SaveGamePGN (FILE *f)
13986 {
13987     SaveGamePGN2(f);
13988     fclose(f);
13989     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13990     return TRUE;
13991 }
13992
13993 /* Save game in old style and close the file */
13994 int
13995 SaveGameOldStyle (FILE *f)
13996 {
13997     int i, offset;
13998     time_t tm;
13999
14000     tm = time((time_t *) NULL);
14001
14002     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14003     PrintOpponents(f);
14004
14005     if (backwardMostMove > 0 || startedFromSetupPosition) {
14006         fprintf(f, "\n[--------------\n");
14007         PrintPosition(f, backwardMostMove);
14008         fprintf(f, "--------------]\n");
14009     } else {
14010         fprintf(f, "\n");
14011     }
14012
14013     i = backwardMostMove;
14014     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14015
14016     while (i < forwardMostMove) {
14017         if (commentList[i] != NULL) {
14018             fprintf(f, "[%s]\n", commentList[i]);
14019         }
14020
14021         if ((i % 2) == 1) {
14022             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14023             i++;
14024         } else {
14025             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14026             i++;
14027             if (commentList[i] != NULL) {
14028                 fprintf(f, "\n");
14029                 continue;
14030             }
14031             if (i >= forwardMostMove) {
14032                 fprintf(f, "\n");
14033                 break;
14034             }
14035             fprintf(f, "%s\n", parseList[i]);
14036             i++;
14037         }
14038     }
14039
14040     if (commentList[i] != NULL) {
14041         fprintf(f, "[%s]\n", commentList[i]);
14042     }
14043
14044     /* This isn't really the old style, but it's close enough */
14045     if (gameInfo.resultDetails != NULL &&
14046         gameInfo.resultDetails[0] != NULLCHAR) {
14047         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14048                 gameInfo.resultDetails);
14049     } else {
14050         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14051     }
14052
14053     fclose(f);
14054     return TRUE;
14055 }
14056
14057 /* Save the current game to open file f and close the file */
14058 int
14059 SaveGame (FILE *f, int dummy, char *dummy2)
14060 {
14061     if (gameMode == EditPosition) EditPositionDone(TRUE);
14062     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14063     if (appData.oldSaveStyle)
14064       return SaveGameOldStyle(f);
14065     else
14066       return SaveGamePGN(f);
14067 }
14068
14069 /* Save the current position to the given file */
14070 int
14071 SavePositionToFile (char *filename)
14072 {
14073     FILE *f;
14074     char buf[MSG_SIZ];
14075
14076     if (strcmp(filename, "-") == 0) {
14077         return SavePosition(stdout, 0, NULL);
14078     } else {
14079         f = fopen(filename, "a");
14080         if (f == NULL) {
14081             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14082             DisplayError(buf, errno);
14083             return FALSE;
14084         } else {
14085             safeStrCpy(buf, lastMsg, MSG_SIZ);
14086             DisplayMessage(_("Waiting for access to save file"), "");
14087             flock(fileno(f), LOCK_EX); // [HGM] lock
14088             DisplayMessage(_("Saving position"), "");
14089             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14090             SavePosition(f, 0, NULL);
14091             DisplayMessage(buf, "");
14092             return TRUE;
14093         }
14094     }
14095 }
14096
14097 /* Save the current position to the given open file and close the file */
14098 int
14099 SavePosition (FILE *f, int dummy, char *dummy2)
14100 {
14101     time_t tm;
14102     char *fen;
14103
14104     if (gameMode == EditPosition) EditPositionDone(TRUE);
14105     if (appData.oldSaveStyle) {
14106         tm = time((time_t *) NULL);
14107
14108         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14109         PrintOpponents(f);
14110         fprintf(f, "[--------------\n");
14111         PrintPosition(f, currentMove);
14112         fprintf(f, "--------------]\n");
14113     } else {
14114         fen = PositionToFEN(currentMove, NULL, 1);
14115         fprintf(f, "%s\n", fen);
14116         free(fen);
14117     }
14118     fclose(f);
14119     return TRUE;
14120 }
14121
14122 void
14123 ReloadCmailMsgEvent (int unregister)
14124 {
14125 #if !WIN32
14126     static char *inFilename = NULL;
14127     static char *outFilename;
14128     int i;
14129     struct stat inbuf, outbuf;
14130     int status;
14131
14132     /* Any registered moves are unregistered if unregister is set, */
14133     /* i.e. invoked by the signal handler */
14134     if (unregister) {
14135         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14136             cmailMoveRegistered[i] = FALSE;
14137             if (cmailCommentList[i] != NULL) {
14138                 free(cmailCommentList[i]);
14139                 cmailCommentList[i] = NULL;
14140             }
14141         }
14142         nCmailMovesRegistered = 0;
14143     }
14144
14145     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14146         cmailResult[i] = CMAIL_NOT_RESULT;
14147     }
14148     nCmailResults = 0;
14149
14150     if (inFilename == NULL) {
14151         /* Because the filenames are static they only get malloced once  */
14152         /* and they never get freed                                      */
14153         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14154         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14155
14156         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14157         sprintf(outFilename, "%s.out", appData.cmailGameName);
14158     }
14159
14160     status = stat(outFilename, &outbuf);
14161     if (status < 0) {
14162         cmailMailedMove = FALSE;
14163     } else {
14164         status = stat(inFilename, &inbuf);
14165         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14166     }
14167
14168     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14169        counts the games, notes how each one terminated, etc.
14170
14171        It would be nice to remove this kludge and instead gather all
14172        the information while building the game list.  (And to keep it
14173        in the game list nodes instead of having a bunch of fixed-size
14174        parallel arrays.)  Note this will require getting each game's
14175        termination from the PGN tags, as the game list builder does
14176        not process the game moves.  --mann
14177        */
14178     cmailMsgLoaded = TRUE;
14179     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14180
14181     /* Load first game in the file or popup game menu */
14182     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14183
14184 #endif /* !WIN32 */
14185     return;
14186 }
14187
14188 int
14189 RegisterMove ()
14190 {
14191     FILE *f;
14192     char string[MSG_SIZ];
14193
14194     if (   cmailMailedMove
14195         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14196         return TRUE;            /* Allow free viewing  */
14197     }
14198
14199     /* Unregister move to ensure that we don't leave RegisterMove        */
14200     /* with the move registered when the conditions for registering no   */
14201     /* longer hold                                                       */
14202     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14203         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14204         nCmailMovesRegistered --;
14205
14206         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14207           {
14208               free(cmailCommentList[lastLoadGameNumber - 1]);
14209               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14210           }
14211     }
14212
14213     if (cmailOldMove == -1) {
14214         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14215         return FALSE;
14216     }
14217
14218     if (currentMove > cmailOldMove + 1) {
14219         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14220         return FALSE;
14221     }
14222
14223     if (currentMove < cmailOldMove) {
14224         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14225         return FALSE;
14226     }
14227
14228     if (forwardMostMove > currentMove) {
14229         /* Silently truncate extra moves */
14230         TruncateGame();
14231     }
14232
14233     if (   (currentMove == cmailOldMove + 1)
14234         || (   (currentMove == cmailOldMove)
14235             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14236                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14237         if (gameInfo.result != GameUnfinished) {
14238             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14239         }
14240
14241         if (commentList[currentMove] != NULL) {
14242             cmailCommentList[lastLoadGameNumber - 1]
14243               = StrSave(commentList[currentMove]);
14244         }
14245         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14246
14247         if (appData.debugMode)
14248           fprintf(debugFP, "Saving %s for game %d\n",
14249                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14250
14251         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14252
14253         f = fopen(string, "w");
14254         if (appData.oldSaveStyle) {
14255             SaveGameOldStyle(f); /* also closes the file */
14256
14257             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14258             f = fopen(string, "w");
14259             SavePosition(f, 0, NULL); /* also closes the file */
14260         } else {
14261             fprintf(f, "{--------------\n");
14262             PrintPosition(f, currentMove);
14263             fprintf(f, "--------------}\n\n");
14264
14265             SaveGame(f, 0, NULL); /* also closes the file*/
14266         }
14267
14268         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14269         nCmailMovesRegistered ++;
14270     } else if (nCmailGames == 1) {
14271         DisplayError(_("You have not made a move yet"), 0);
14272         return FALSE;
14273     }
14274
14275     return TRUE;
14276 }
14277
14278 void
14279 MailMoveEvent ()
14280 {
14281 #if !WIN32
14282     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14283     FILE *commandOutput;
14284     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14285     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14286     int nBuffers;
14287     int i;
14288     int archived;
14289     char *arcDir;
14290
14291     if (! cmailMsgLoaded) {
14292         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14293         return;
14294     }
14295
14296     if (nCmailGames == nCmailResults) {
14297         DisplayError(_("No unfinished games"), 0);
14298         return;
14299     }
14300
14301 #if CMAIL_PROHIBIT_REMAIL
14302     if (cmailMailedMove) {
14303       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);
14304         DisplayError(msg, 0);
14305         return;
14306     }
14307 #endif
14308
14309     if (! (cmailMailedMove || RegisterMove())) return;
14310
14311     if (   cmailMailedMove
14312         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14313       snprintf(string, MSG_SIZ, partCommandString,
14314                appData.debugMode ? " -v" : "", appData.cmailGameName);
14315         commandOutput = popen(string, "r");
14316
14317         if (commandOutput == NULL) {
14318             DisplayError(_("Failed to invoke cmail"), 0);
14319         } else {
14320             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14321                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14322             }
14323             if (nBuffers > 1) {
14324                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14325                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14326                 nBytes = MSG_SIZ - 1;
14327             } else {
14328                 (void) memcpy(msg, buffer, nBytes);
14329             }
14330             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14331
14332             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14333                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14334
14335                 archived = TRUE;
14336                 for (i = 0; i < nCmailGames; i ++) {
14337                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14338                         archived = FALSE;
14339                     }
14340                 }
14341                 if (   archived
14342                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14343                         != NULL)) {
14344                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14345                            arcDir,
14346                            appData.cmailGameName,
14347                            gameInfo.date);
14348                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14349                     cmailMsgLoaded = FALSE;
14350                 }
14351             }
14352
14353             DisplayInformation(msg);
14354             pclose(commandOutput);
14355         }
14356     } else {
14357         if ((*cmailMsg) != '\0') {
14358             DisplayInformation(cmailMsg);
14359         }
14360     }
14361
14362     return;
14363 #endif /* !WIN32 */
14364 }
14365
14366 char *
14367 CmailMsg ()
14368 {
14369 #if WIN32
14370     return NULL;
14371 #else
14372     int  prependComma = 0;
14373     char number[5];
14374     char string[MSG_SIZ];       /* Space for game-list */
14375     int  i;
14376
14377     if (!cmailMsgLoaded) return "";
14378
14379     if (cmailMailedMove) {
14380       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14381     } else {
14382         /* Create a list of games left */
14383       snprintf(string, MSG_SIZ, "[");
14384         for (i = 0; i < nCmailGames; i ++) {
14385             if (! (   cmailMoveRegistered[i]
14386                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14387                 if (prependComma) {
14388                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14389                 } else {
14390                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14391                     prependComma = 1;
14392                 }
14393
14394                 strcat(string, number);
14395             }
14396         }
14397         strcat(string, "]");
14398
14399         if (nCmailMovesRegistered + nCmailResults == 0) {
14400             switch (nCmailGames) {
14401               case 1:
14402                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14403                 break;
14404
14405               case 2:
14406                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14407                 break;
14408
14409               default:
14410                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14411                          nCmailGames);
14412                 break;
14413             }
14414         } else {
14415             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14416               case 1:
14417                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14418                          string);
14419                 break;
14420
14421               case 0:
14422                 if (nCmailResults == nCmailGames) {
14423                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14424                 } else {
14425                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14426                 }
14427                 break;
14428
14429               default:
14430                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14431                          string);
14432             }
14433         }
14434     }
14435     return cmailMsg;
14436 #endif /* WIN32 */
14437 }
14438
14439 void
14440 ResetGameEvent ()
14441 {
14442     if (gameMode == Training)
14443       SetTrainingModeOff();
14444
14445     Reset(TRUE, TRUE);
14446     cmailMsgLoaded = FALSE;
14447     if (appData.icsActive) {
14448       SendToICS(ics_prefix);
14449       SendToICS("refresh\n");
14450     }
14451 }
14452
14453 void
14454 ExitEvent (int status)
14455 {
14456     exiting++;
14457     if (exiting > 2) {
14458       /* Give up on clean exit */
14459       exit(status);
14460     }
14461     if (exiting > 1) {
14462       /* Keep trying for clean exit */
14463       return;
14464     }
14465
14466     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14467     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14468
14469     if (telnetISR != NULL) {
14470       RemoveInputSource(telnetISR);
14471     }
14472     if (icsPR != NoProc) {
14473       DestroyChildProcess(icsPR, TRUE);
14474     }
14475
14476     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14477     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14478
14479     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14480     /* make sure this other one finishes before killing it!                  */
14481     if(endingGame) { int count = 0;
14482         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14483         while(endingGame && count++ < 10) DoSleep(1);
14484         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14485     }
14486
14487     /* Kill off chess programs */
14488     if (first.pr != NoProc) {
14489         ExitAnalyzeMode();
14490
14491         DoSleep( appData.delayBeforeQuit );
14492         SendToProgram("quit\n", &first);
14493         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14494     }
14495     if (second.pr != NoProc) {
14496         DoSleep( appData.delayBeforeQuit );
14497         SendToProgram("quit\n", &second);
14498         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14499     }
14500     if (first.isr != NULL) {
14501         RemoveInputSource(first.isr);
14502     }
14503     if (second.isr != NULL) {
14504         RemoveInputSource(second.isr);
14505     }
14506
14507     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14508     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14509
14510     ShutDownFrontEnd();
14511     exit(status);
14512 }
14513
14514 void
14515 PauseEngine (ChessProgramState *cps)
14516 {
14517     SendToProgram("pause\n", cps);
14518     cps->pause = 2;
14519 }
14520
14521 void
14522 UnPauseEngine (ChessProgramState *cps)
14523 {
14524     SendToProgram("resume\n", cps);
14525     cps->pause = 1;
14526 }
14527
14528 void
14529 PauseEvent ()
14530 {
14531     if (appData.debugMode)
14532         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14533     if (pausing) {
14534         pausing = FALSE;
14535         ModeHighlight();
14536         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14537             StartClocks();
14538             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14539                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14540                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14541             }
14542             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14543             HandleMachineMove(stashedInputMove, stalledEngine);
14544             stalledEngine = NULL;
14545             return;
14546         }
14547         if (gameMode == MachinePlaysWhite ||
14548             gameMode == TwoMachinesPlay   ||
14549             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14550             if(first.pause)  UnPauseEngine(&first);
14551             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14552             if(second.pause) UnPauseEngine(&second);
14553             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14554             StartClocks();
14555         } else {
14556             DisplayBothClocks();
14557         }
14558         if (gameMode == PlayFromGameFile) {
14559             if (appData.timeDelay >= 0)
14560                 AutoPlayGameLoop();
14561         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14562             Reset(FALSE, TRUE);
14563             SendToICS(ics_prefix);
14564             SendToICS("refresh\n");
14565         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14566             ForwardInner(forwardMostMove);
14567         }
14568         pauseExamInvalid = FALSE;
14569     } else {
14570         switch (gameMode) {
14571           default:
14572             return;
14573           case IcsExamining:
14574             pauseExamForwardMostMove = forwardMostMove;
14575             pauseExamInvalid = FALSE;
14576             /* fall through */
14577           case IcsObserving:
14578           case IcsPlayingWhite:
14579           case IcsPlayingBlack:
14580             pausing = TRUE;
14581             ModeHighlight();
14582             return;
14583           case PlayFromGameFile:
14584             (void) StopLoadGameTimer();
14585             pausing = TRUE;
14586             ModeHighlight();
14587             break;
14588           case BeginningOfGame:
14589             if (appData.icsActive) return;
14590             /* else fall through */
14591           case MachinePlaysWhite:
14592           case MachinePlaysBlack:
14593           case TwoMachinesPlay:
14594             if (forwardMostMove == 0)
14595               return;           /* don't pause if no one has moved */
14596             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14597                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14598                 if(onMove->pause) {           // thinking engine can be paused
14599                     PauseEngine(onMove);      // do it
14600                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14601                         PauseEngine(onMove->other);
14602                     else
14603                         SendToProgram("easy\n", onMove->other);
14604                     StopClocks();
14605                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14606             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14607                 if(first.pause) {
14608                     PauseEngine(&first);
14609                     StopClocks();
14610                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14611             } else { // human on move, pause pondering by either method
14612                 if(first.pause)
14613                     PauseEngine(&first);
14614                 else if(appData.ponderNextMove)
14615                     SendToProgram("easy\n", &first);
14616                 StopClocks();
14617             }
14618             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14619           case AnalyzeMode:
14620             pausing = TRUE;
14621             ModeHighlight();
14622             break;
14623         }
14624     }
14625 }
14626
14627 void
14628 EditCommentEvent ()
14629 {
14630     char title[MSG_SIZ];
14631
14632     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14633       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14634     } else {
14635       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14636                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14637                parseList[currentMove - 1]);
14638     }
14639
14640     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14641 }
14642
14643
14644 void
14645 EditTagsEvent ()
14646 {
14647     char *tags = PGNTags(&gameInfo);
14648     bookUp = FALSE;
14649     EditTagsPopUp(tags, NULL);
14650     free(tags);
14651 }
14652
14653 void
14654 ToggleSecond ()
14655 {
14656   if(second.analyzing) {
14657     SendToProgram("exit\n", &second);
14658     second.analyzing = FALSE;
14659   } else {
14660     if (second.pr == NoProc) StartChessProgram(&second);
14661     InitChessProgram(&second, FALSE);
14662     FeedMovesToProgram(&second, currentMove);
14663
14664     SendToProgram("analyze\n", &second);
14665     second.analyzing = TRUE;
14666   }
14667 }
14668
14669 /* Toggle ShowThinking */
14670 void
14671 ToggleShowThinking()
14672 {
14673   appData.showThinking = !appData.showThinking;
14674   ShowThinkingEvent();
14675 }
14676
14677 int
14678 AnalyzeModeEvent ()
14679 {
14680     char buf[MSG_SIZ];
14681
14682     if (!first.analysisSupport) {
14683       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14684       DisplayError(buf, 0);
14685       return 0;
14686     }
14687     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14688     if (appData.icsActive) {
14689         if (gameMode != IcsObserving) {
14690           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14691             DisplayError(buf, 0);
14692             /* secure check */
14693             if (appData.icsEngineAnalyze) {
14694                 if (appData.debugMode)
14695                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14696                 ExitAnalyzeMode();
14697                 ModeHighlight();
14698             }
14699             return 0;
14700         }
14701         /* if enable, user wants to disable icsEngineAnalyze */
14702         if (appData.icsEngineAnalyze) {
14703                 ExitAnalyzeMode();
14704                 ModeHighlight();
14705                 return 0;
14706         }
14707         appData.icsEngineAnalyze = TRUE;
14708         if (appData.debugMode)
14709             fprintf(debugFP, "ICS engine analyze starting... \n");
14710     }
14711
14712     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14713     if (appData.noChessProgram || gameMode == AnalyzeMode)
14714       return 0;
14715
14716     if (gameMode != AnalyzeFile) {
14717         if (!appData.icsEngineAnalyze) {
14718                EditGameEvent();
14719                if (gameMode != EditGame) return 0;
14720         }
14721         if (!appData.showThinking) ToggleShowThinking();
14722         ResurrectChessProgram();
14723         SendToProgram("analyze\n", &first);
14724         first.analyzing = TRUE;
14725         /*first.maybeThinking = TRUE;*/
14726         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14727         EngineOutputPopUp();
14728     }
14729     if (!appData.icsEngineAnalyze) {
14730         gameMode = AnalyzeMode;
14731         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14732     }
14733     pausing = FALSE;
14734     ModeHighlight();
14735     SetGameInfo();
14736
14737     StartAnalysisClock();
14738     GetTimeMark(&lastNodeCountTime);
14739     lastNodeCount = 0;
14740     return 1;
14741 }
14742
14743 void
14744 AnalyzeFileEvent ()
14745 {
14746     if (appData.noChessProgram || gameMode == AnalyzeFile)
14747       return;
14748
14749     if (!first.analysisSupport) {
14750       char buf[MSG_SIZ];
14751       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14752       DisplayError(buf, 0);
14753       return;
14754     }
14755
14756     if (gameMode != AnalyzeMode) {
14757         keepInfo = 1; // mere annotating should not alter PGN tags
14758         EditGameEvent();
14759         keepInfo = 0;
14760         if (gameMode != EditGame) return;
14761         if (!appData.showThinking) ToggleShowThinking();
14762         ResurrectChessProgram();
14763         SendToProgram("analyze\n", &first);
14764         first.analyzing = TRUE;
14765         /*first.maybeThinking = TRUE;*/
14766         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14767         EngineOutputPopUp();
14768     }
14769     gameMode = AnalyzeFile;
14770     pausing = FALSE;
14771     ModeHighlight();
14772
14773     StartAnalysisClock();
14774     GetTimeMark(&lastNodeCountTime);
14775     lastNodeCount = 0;
14776     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14777     AnalysisPeriodicEvent(1);
14778 }
14779
14780 void
14781 MachineWhiteEvent ()
14782 {
14783     char buf[MSG_SIZ];
14784     char *bookHit = NULL;
14785
14786     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14787       return;
14788
14789
14790     if (gameMode == PlayFromGameFile ||
14791         gameMode == TwoMachinesPlay  ||
14792         gameMode == Training         ||
14793         gameMode == AnalyzeMode      ||
14794         gameMode == EndOfGame)
14795         EditGameEvent();
14796
14797     if (gameMode == EditPosition)
14798         EditPositionDone(TRUE);
14799
14800     if (!WhiteOnMove(currentMove)) {
14801         DisplayError(_("It is not White's turn"), 0);
14802         return;
14803     }
14804
14805     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14806       ExitAnalyzeMode();
14807
14808     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14809         gameMode == AnalyzeFile)
14810         TruncateGame();
14811
14812     ResurrectChessProgram();    /* in case it isn't running */
14813     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14814         gameMode = MachinePlaysWhite;
14815         ResetClocks();
14816     } else
14817     gameMode = MachinePlaysWhite;
14818     pausing = FALSE;
14819     ModeHighlight();
14820     SetGameInfo();
14821     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14822     DisplayTitle(buf);
14823     if (first.sendName) {
14824       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14825       SendToProgram(buf, &first);
14826     }
14827     if (first.sendTime) {
14828       if (first.useColors) {
14829         SendToProgram("black\n", &first); /*gnu kludge*/
14830       }
14831       SendTimeRemaining(&first, TRUE);
14832     }
14833     if (first.useColors) {
14834       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14835     }
14836     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14837     SetMachineThinkingEnables();
14838     first.maybeThinking = TRUE;
14839     StartClocks();
14840     firstMove = FALSE;
14841
14842     if (appData.autoFlipView && !flipView) {
14843       flipView = !flipView;
14844       DrawPosition(FALSE, NULL);
14845       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14846     }
14847
14848     if(bookHit) { // [HGM] book: simulate book reply
14849         static char bookMove[MSG_SIZ]; // a bit generous?
14850
14851         programStats.nodes = programStats.depth = programStats.time =
14852         programStats.score = programStats.got_only_move = 0;
14853         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14854
14855         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14856         strcat(bookMove, bookHit);
14857         HandleMachineMove(bookMove, &first);
14858     }
14859 }
14860
14861 void
14862 MachineBlackEvent ()
14863 {
14864   char buf[MSG_SIZ];
14865   char *bookHit = NULL;
14866
14867     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14868         return;
14869
14870
14871     if (gameMode == PlayFromGameFile ||
14872         gameMode == TwoMachinesPlay  ||
14873         gameMode == Training         ||
14874         gameMode == AnalyzeMode      ||
14875         gameMode == EndOfGame)
14876         EditGameEvent();
14877
14878     if (gameMode == EditPosition)
14879         EditPositionDone(TRUE);
14880
14881     if (WhiteOnMove(currentMove)) {
14882         DisplayError(_("It is not Black's turn"), 0);
14883         return;
14884     }
14885
14886     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14887       ExitAnalyzeMode();
14888
14889     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14890         gameMode == AnalyzeFile)
14891         TruncateGame();
14892
14893     ResurrectChessProgram();    /* in case it isn't running */
14894     gameMode = MachinePlaysBlack;
14895     pausing = FALSE;
14896     ModeHighlight();
14897     SetGameInfo();
14898     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14899     DisplayTitle(buf);
14900     if (first.sendName) {
14901       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14902       SendToProgram(buf, &first);
14903     }
14904     if (first.sendTime) {
14905       if (first.useColors) {
14906         SendToProgram("white\n", &first); /*gnu kludge*/
14907       }
14908       SendTimeRemaining(&first, FALSE);
14909     }
14910     if (first.useColors) {
14911       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14912     }
14913     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14914     SetMachineThinkingEnables();
14915     first.maybeThinking = TRUE;
14916     StartClocks();
14917
14918     if (appData.autoFlipView && flipView) {
14919       flipView = !flipView;
14920       DrawPosition(FALSE, NULL);
14921       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14922     }
14923     if(bookHit) { // [HGM] book: simulate book reply
14924         static char bookMove[MSG_SIZ]; // a bit generous?
14925
14926         programStats.nodes = programStats.depth = programStats.time =
14927         programStats.score = programStats.got_only_move = 0;
14928         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14929
14930         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14931         strcat(bookMove, bookHit);
14932         HandleMachineMove(bookMove, &first);
14933     }
14934 }
14935
14936
14937 void
14938 DisplayTwoMachinesTitle ()
14939 {
14940     char buf[MSG_SIZ];
14941     if (appData.matchGames > 0) {
14942         if(appData.tourneyFile[0]) {
14943           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14944                    gameInfo.white, _("vs."), gameInfo.black,
14945                    nextGame+1, appData.matchGames+1,
14946                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14947         } else
14948         if (first.twoMachinesColor[0] == 'w') {
14949           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14950                    gameInfo.white, _("vs."),  gameInfo.black,
14951                    first.matchWins, second.matchWins,
14952                    matchGame - 1 - (first.matchWins + second.matchWins));
14953         } else {
14954           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14955                    gameInfo.white, _("vs."), gameInfo.black,
14956                    second.matchWins, first.matchWins,
14957                    matchGame - 1 - (first.matchWins + second.matchWins));
14958         }
14959     } else {
14960       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14961     }
14962     DisplayTitle(buf);
14963 }
14964
14965 void
14966 SettingsMenuIfReady ()
14967 {
14968   if (second.lastPing != second.lastPong) {
14969     DisplayMessage("", _("Waiting for second chess program"));
14970     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14971     return;
14972   }
14973   ThawUI();
14974   DisplayMessage("", "");
14975   SettingsPopUp(&second);
14976 }
14977
14978 int
14979 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14980 {
14981     char buf[MSG_SIZ];
14982     if (cps->pr == NoProc) {
14983         StartChessProgram(cps);
14984         if (cps->protocolVersion == 1) {
14985           retry();
14986           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14987         } else {
14988           /* kludge: allow timeout for initial "feature" command */
14989           if(retry != TwoMachinesEventIfReady) FreezeUI();
14990           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14991           DisplayMessage("", buf);
14992           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14993         }
14994         return 1;
14995     }
14996     return 0;
14997 }
14998
14999 void
15000 TwoMachinesEvent P((void))
15001 {
15002     int i;
15003     char buf[MSG_SIZ];
15004     ChessProgramState *onmove;
15005     char *bookHit = NULL;
15006     static int stalling = 0;
15007     TimeMark now;
15008     long wait;
15009
15010     if (appData.noChessProgram) return;
15011
15012     switch (gameMode) {
15013       case TwoMachinesPlay:
15014         return;
15015       case MachinePlaysWhite:
15016       case MachinePlaysBlack:
15017         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15018             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15019             return;
15020         }
15021         /* fall through */
15022       case BeginningOfGame:
15023       case PlayFromGameFile:
15024       case EndOfGame:
15025         EditGameEvent();
15026         if (gameMode != EditGame) return;
15027         break;
15028       case EditPosition:
15029         EditPositionDone(TRUE);
15030         break;
15031       case AnalyzeMode:
15032       case AnalyzeFile:
15033         ExitAnalyzeMode();
15034         break;
15035       case EditGame:
15036       default:
15037         break;
15038     }
15039
15040 //    forwardMostMove = currentMove;
15041     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15042     startingEngine = TRUE;
15043
15044     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15045
15046     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15047     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15048       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15049       return;
15050     }
15051   if(!appData.epd) {
15052     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15053
15054     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15055                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15056         startingEngine = matchMode = FALSE;
15057         DisplayError("second engine does not play this", 0);
15058         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15059         EditGameEvent(); // switch back to EditGame mode
15060         return;
15061     }
15062
15063     if(!stalling) {
15064       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15065       SendToProgram("force\n", &second);
15066       stalling = 1;
15067       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15068       return;
15069     }
15070   }
15071     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15072     if(appData.matchPause>10000 || appData.matchPause<10)
15073                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15074     wait = SubtractTimeMarks(&now, &pauseStart);
15075     if(wait < appData.matchPause) {
15076         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15077         return;
15078     }
15079     // we are now committed to starting the game
15080     stalling = 0;
15081     DisplayMessage("", "");
15082   if(!appData.epd) {
15083     if (startedFromSetupPosition) {
15084         SendBoard(&second, backwardMostMove);
15085     if (appData.debugMode) {
15086         fprintf(debugFP, "Two Machines\n");
15087     }
15088     }
15089     for (i = backwardMostMove; i < forwardMostMove; i++) {
15090         SendMoveToProgram(i, &second);
15091     }
15092   }
15093
15094     gameMode = TwoMachinesPlay;
15095     pausing = startingEngine = FALSE;
15096     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15097     SetGameInfo();
15098     DisplayTwoMachinesTitle();
15099     firstMove = TRUE;
15100     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15101         onmove = &first;
15102     } else {
15103         onmove = &second;
15104     }
15105     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15106     SendToProgram(first.computerString, &first);
15107     if (first.sendName) {
15108       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15109       SendToProgram(buf, &first);
15110     }
15111   if(!appData.epd) {
15112     SendToProgram(second.computerString, &second);
15113     if (second.sendName) {
15114       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15115       SendToProgram(buf, &second);
15116     }
15117   }
15118
15119     ResetClocks();
15120     if (!first.sendTime || !second.sendTime) {
15121         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15122         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15123     }
15124     if (onmove->sendTime) {
15125       if (onmove->useColors) {
15126         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15127       }
15128       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15129     }
15130     if (onmove->useColors) {
15131       SendToProgram(onmove->twoMachinesColor, onmove);
15132     }
15133     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15134 //    SendToProgram("go\n", onmove);
15135     onmove->maybeThinking = TRUE;
15136     SetMachineThinkingEnables();
15137
15138     StartClocks();
15139
15140     if(bookHit) { // [HGM] book: simulate book reply
15141         static char bookMove[MSG_SIZ]; // a bit generous?
15142
15143         programStats.nodes = programStats.depth = programStats.time =
15144         programStats.score = programStats.got_only_move = 0;
15145         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15146
15147         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15148         strcat(bookMove, bookHit);
15149         savedMessage = bookMove; // args for deferred call
15150         savedState = onmove;
15151         ScheduleDelayedEvent(DeferredBookMove, 1);
15152     }
15153 }
15154
15155 void
15156 TrainingEvent ()
15157 {
15158     if (gameMode == Training) {
15159       SetTrainingModeOff();
15160       gameMode = PlayFromGameFile;
15161       DisplayMessage("", _("Training mode off"));
15162     } else {
15163       gameMode = Training;
15164       animateTraining = appData.animate;
15165
15166       /* make sure we are not already at the end of the game */
15167       if (currentMove < forwardMostMove) {
15168         SetTrainingModeOn();
15169         DisplayMessage("", _("Training mode on"));
15170       } else {
15171         gameMode = PlayFromGameFile;
15172         DisplayError(_("Already at end of game"), 0);
15173       }
15174     }
15175     ModeHighlight();
15176 }
15177
15178 void
15179 IcsClientEvent ()
15180 {
15181     if (!appData.icsActive) return;
15182     switch (gameMode) {
15183       case IcsPlayingWhite:
15184       case IcsPlayingBlack:
15185       case IcsObserving:
15186       case IcsIdle:
15187       case BeginningOfGame:
15188       case IcsExamining:
15189         return;
15190
15191       case EditGame:
15192         break;
15193
15194       case EditPosition:
15195         EditPositionDone(TRUE);
15196         break;
15197
15198       case AnalyzeMode:
15199       case AnalyzeFile:
15200         ExitAnalyzeMode();
15201         break;
15202
15203       default:
15204         EditGameEvent();
15205         break;
15206     }
15207
15208     gameMode = IcsIdle;
15209     ModeHighlight();
15210     return;
15211 }
15212
15213 void
15214 EditGameEvent ()
15215 {
15216     int i;
15217
15218     switch (gameMode) {
15219       case Training:
15220         SetTrainingModeOff();
15221         break;
15222       case MachinePlaysWhite:
15223       case MachinePlaysBlack:
15224       case BeginningOfGame:
15225         SendToProgram("force\n", &first);
15226         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15227             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15228                 char buf[MSG_SIZ];
15229                 abortEngineThink = TRUE;
15230                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15231                 SendToProgram(buf, &first);
15232                 DisplayMessage("Aborting engine think", "");
15233                 FreezeUI();
15234             }
15235         }
15236         SetUserThinkingEnables();
15237         break;
15238       case PlayFromGameFile:
15239         (void) StopLoadGameTimer();
15240         if (gameFileFP != NULL) {
15241             gameFileFP = NULL;
15242         }
15243         break;
15244       case EditPosition:
15245         EditPositionDone(TRUE);
15246         break;
15247       case AnalyzeMode:
15248       case AnalyzeFile:
15249         ExitAnalyzeMode();
15250         SendToProgram("force\n", &first);
15251         break;
15252       case TwoMachinesPlay:
15253         GameEnds(EndOfFile, NULL, GE_PLAYER);
15254         ResurrectChessProgram();
15255         SetUserThinkingEnables();
15256         break;
15257       case EndOfGame:
15258         ResurrectChessProgram();
15259         break;
15260       case IcsPlayingBlack:
15261       case IcsPlayingWhite:
15262         DisplayError(_("Warning: You are still playing a game"), 0);
15263         break;
15264       case IcsObserving:
15265         DisplayError(_("Warning: You are still observing a game"), 0);
15266         break;
15267       case IcsExamining:
15268         DisplayError(_("Warning: You are still examining a game"), 0);
15269         break;
15270       case IcsIdle:
15271         break;
15272       case EditGame:
15273       default:
15274         return;
15275     }
15276
15277     pausing = FALSE;
15278     StopClocks();
15279     first.offeredDraw = second.offeredDraw = 0;
15280
15281     if (gameMode == PlayFromGameFile) {
15282         whiteTimeRemaining = timeRemaining[0][currentMove];
15283         blackTimeRemaining = timeRemaining[1][currentMove];
15284         DisplayTitle("");
15285     }
15286
15287     if (gameMode == MachinePlaysWhite ||
15288         gameMode == MachinePlaysBlack ||
15289         gameMode == TwoMachinesPlay ||
15290         gameMode == EndOfGame) {
15291         i = forwardMostMove;
15292         while (i > currentMove) {
15293             SendToProgram("undo\n", &first);
15294             i--;
15295         }
15296         if(!adjustedClock) {
15297         whiteTimeRemaining = timeRemaining[0][currentMove];
15298         blackTimeRemaining = timeRemaining[1][currentMove];
15299         DisplayBothClocks();
15300         }
15301         if (whiteFlag || blackFlag) {
15302             whiteFlag = blackFlag = 0;
15303         }
15304         DisplayTitle("");
15305     }
15306
15307     gameMode = EditGame;
15308     ModeHighlight();
15309     SetGameInfo();
15310 }
15311
15312 void
15313 EditPositionEvent ()
15314 {
15315     int i;
15316     if (gameMode == EditPosition) {
15317         EditGameEvent();
15318         return;
15319     }
15320
15321     EditGameEvent();
15322     if (gameMode != EditGame) return;
15323
15324     gameMode = EditPosition;
15325     ModeHighlight();
15326     SetGameInfo();
15327     CopyBoard(rightsBoard, nullBoard);
15328     if (currentMove > 0)
15329       CopyBoard(boards[0], boards[currentMove]);
15330     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15331       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15332
15333     blackPlaysFirst = !WhiteOnMove(currentMove);
15334     ResetClocks();
15335     currentMove = forwardMostMove = backwardMostMove = 0;
15336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15337     DisplayMove(-1);
15338     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15339 }
15340
15341 void
15342 ExitAnalyzeMode ()
15343 {
15344     /* [DM] icsEngineAnalyze - possible call from other functions */
15345     if (appData.icsEngineAnalyze) {
15346         appData.icsEngineAnalyze = FALSE;
15347
15348         DisplayMessage("",_("Close ICS engine analyze..."));
15349     }
15350     if (first.analysisSupport && first.analyzing) {
15351       SendToBoth("exit\n");
15352       first.analyzing = second.analyzing = FALSE;
15353     }
15354     thinkOutput[0] = NULLCHAR;
15355 }
15356
15357 void
15358 EditPositionDone (Boolean fakeRights)
15359 {
15360     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15361
15362     startedFromSetupPosition = TRUE;
15363     InitChessProgram(&first, FALSE);
15364     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15365       int r, f;
15366       boards[0][EP_STATUS] = EP_NONE;
15367       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15368       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15369         if(rightsBoard[r][f]) {
15370           ChessSquare p = boards[0][r][f];
15371           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15372           else if(p == king) boards[0][CASTLING][2] = f;
15373           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15374           else rightsBoard[r][f] = 2; // mark for second pass
15375         }
15376       }
15377       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15378         if(rightsBoard[r][f] == 2) {
15379           ChessSquare p = boards[0][r][f];
15380           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15381           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15382         }
15383       }
15384     }
15385     SendToProgram("force\n", &first);
15386     if (blackPlaysFirst) {
15387         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15388         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15389         currentMove = forwardMostMove = backwardMostMove = 1;
15390         CopyBoard(boards[1], boards[0]);
15391     } else {
15392         currentMove = forwardMostMove = backwardMostMove = 0;
15393     }
15394     SendBoard(&first, forwardMostMove);
15395     if (appData.debugMode) {
15396         fprintf(debugFP, "EditPosDone\n");
15397     }
15398     DisplayTitle("");
15399     DisplayMessage("", "");
15400     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15401     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15402     gameMode = EditGame;
15403     ModeHighlight();
15404     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15405     ClearHighlights(); /* [AS] */
15406 }
15407
15408 /* Pause for `ms' milliseconds */
15409 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15410 void
15411 TimeDelay (long ms)
15412 {
15413     TimeMark m1, m2;
15414
15415     GetTimeMark(&m1);
15416     do {
15417         GetTimeMark(&m2);
15418     } while (SubtractTimeMarks(&m2, &m1) < ms);
15419 }
15420
15421 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15422 void
15423 SendMultiLineToICS (char *buf)
15424 {
15425     char temp[MSG_SIZ+1], *p;
15426     int len;
15427
15428     len = strlen(buf);
15429     if (len > MSG_SIZ)
15430       len = MSG_SIZ;
15431
15432     strncpy(temp, buf, len);
15433     temp[len] = 0;
15434
15435     p = temp;
15436     while (*p) {
15437         if (*p == '\n' || *p == '\r')
15438           *p = ' ';
15439         ++p;
15440     }
15441
15442     strcat(temp, "\n");
15443     SendToICS(temp);
15444     SendToPlayer(temp, strlen(temp));
15445 }
15446
15447 void
15448 SetWhiteToPlayEvent ()
15449 {
15450     if (gameMode == EditPosition) {
15451         blackPlaysFirst = FALSE;
15452         DisplayBothClocks();    /* works because currentMove is 0 */
15453     } else if (gameMode == IcsExamining) {
15454         SendToICS(ics_prefix);
15455         SendToICS("tomove white\n");
15456     }
15457 }
15458
15459 void
15460 SetBlackToPlayEvent ()
15461 {
15462     if (gameMode == EditPosition) {
15463         blackPlaysFirst = TRUE;
15464         currentMove = 1;        /* kludge */
15465         DisplayBothClocks();
15466         currentMove = 0;
15467     } else if (gameMode == IcsExamining) {
15468         SendToICS(ics_prefix);
15469         SendToICS("tomove black\n");
15470     }
15471 }
15472
15473 void
15474 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15475 {
15476     char buf[MSG_SIZ];
15477     ChessSquare piece = boards[0][y][x];
15478     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15479     static int lastVariant;
15480     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15481
15482     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15483
15484     switch (selection) {
15485       case ClearBoard:
15486         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15487         MarkTargetSquares(1);
15488         CopyBoard(currentBoard, boards[0]);
15489         CopyBoard(menuBoard, initialPosition);
15490         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15491             SendToICS(ics_prefix);
15492             SendToICS("bsetup clear\n");
15493         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15494             SendToICS(ics_prefix);
15495             SendToICS("clearboard\n");
15496         } else {
15497             int nonEmpty = 0;
15498             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15499                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15500                 for (y = 0; y < BOARD_HEIGHT; y++) {
15501                     if (gameMode == IcsExamining) {
15502                         if (boards[currentMove][y][x] != EmptySquare) {
15503                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15504                                     AAA + x, ONE + y);
15505                             SendToICS(buf);
15506                         }
15507                     } else if(boards[0][y][x] != DarkSquare) {
15508                         if(boards[0][y][x] != p) nonEmpty++;
15509                         boards[0][y][x] = p;
15510                     }
15511                 }
15512             }
15513             CopyBoard(rightsBoard, nullBoard);
15514             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15515                 int r, i;
15516                 for(r = 0; r < BOARD_HEIGHT; r++) {
15517                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15518                     ChessSquare p = menuBoard[r][x];
15519                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15520                   }
15521                 }
15522                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15523                 DisplayMessage("Clicking clock again restores position", "");
15524                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15525                 if(!nonEmpty) { // asked to clear an empty board
15526                     CopyBoard(boards[0], menuBoard);
15527                 } else
15528                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15529                     CopyBoard(boards[0], initialPosition);
15530                 } else
15531                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15532                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15533                     CopyBoard(boards[0], erasedBoard);
15534                 } else
15535                     CopyBoard(erasedBoard, currentBoard);
15536
15537                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15538                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15539             }
15540         }
15541         if (gameMode == EditPosition) {
15542             DrawPosition(FALSE, boards[0]);
15543         }
15544         break;
15545
15546       case WhitePlay:
15547         SetWhiteToPlayEvent();
15548         break;
15549
15550       case BlackPlay:
15551         SetBlackToPlayEvent();
15552         break;
15553
15554       case EmptySquare:
15555         if (gameMode == IcsExamining) {
15556             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15557             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15558             SendToICS(buf);
15559         } else {
15560             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15561                 if(x == BOARD_LEFT-2) {
15562                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15563                     boards[0][y][1] = 0;
15564                 } else
15565                 if(x == BOARD_RGHT+1) {
15566                     if(y >= gameInfo.holdingsSize) break;
15567                     boards[0][y][BOARD_WIDTH-2] = 0;
15568                 } else break;
15569             }
15570             boards[0][y][x] = EmptySquare;
15571             DrawPosition(FALSE, boards[0]);
15572         }
15573         break;
15574
15575       case PromotePiece:
15576         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15577            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15578             selection = (ChessSquare) (PROMOTED(piece));
15579         } else if(piece == EmptySquare) selection = WhiteSilver;
15580         else selection = (ChessSquare)((int)piece - 1);
15581         goto defaultlabel;
15582
15583       case DemotePiece:
15584         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15585            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15586             selection = (ChessSquare) (DEMOTED(piece));
15587         } else if(piece == EmptySquare) selection = BlackSilver;
15588         else selection = (ChessSquare)((int)piece + 1);
15589         goto defaultlabel;
15590
15591       case WhiteQueen:
15592       case BlackQueen:
15593         if(gameInfo.variant == VariantShatranj ||
15594            gameInfo.variant == VariantXiangqi  ||
15595            gameInfo.variant == VariantCourier  ||
15596            gameInfo.variant == VariantASEAN    ||
15597            gameInfo.variant == VariantMakruk     )
15598             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15599         goto defaultlabel;
15600
15601       case WhiteRook:
15602         baseRank = 0;
15603       case BlackRook:
15604         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15605         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15606         goto defaultlabel;
15607
15608       case WhiteKing:
15609         baseRank = 0;
15610       case BlackKing:
15611         if(gameInfo.variant == VariantXiangqi)
15612             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15613         if(gameInfo.variant == VariantKnightmate)
15614             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15615         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15616       default:
15617         defaultlabel:
15618         if (gameMode == IcsExamining) {
15619             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15620             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15621                      PieceToChar(selection), AAA + x, ONE + y);
15622             SendToICS(buf);
15623         } else {
15624             rightsBoard[y][x] = hasRights;
15625             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15626                 int n;
15627                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15628                     n = PieceToNumber(selection - BlackPawn);
15629                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15630                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15631                     boards[0][BOARD_HEIGHT-1-n][1]++;
15632                 } else
15633                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15634                     n = PieceToNumber(selection);
15635                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15636                     boards[0][n][BOARD_WIDTH-1] = selection;
15637                     boards[0][n][BOARD_WIDTH-2]++;
15638                 }
15639             } else
15640             boards[0][y][x] = selection;
15641             DrawPosition(TRUE, boards[0]);
15642             ClearHighlights();
15643             fromX = fromY = -1;
15644         }
15645         break;
15646     }
15647 }
15648
15649
15650 void
15651 DropMenuEvent (ChessSquare selection, int x, int y)
15652 {
15653     ChessMove moveType;
15654
15655     switch (gameMode) {
15656       case IcsPlayingWhite:
15657       case MachinePlaysBlack:
15658         if (!WhiteOnMove(currentMove)) {
15659             DisplayMoveError(_("It is Black's turn"));
15660             return;
15661         }
15662         moveType = WhiteDrop;
15663         break;
15664       case IcsPlayingBlack:
15665       case MachinePlaysWhite:
15666         if (WhiteOnMove(currentMove)) {
15667             DisplayMoveError(_("It is White's turn"));
15668             return;
15669         }
15670         moveType = BlackDrop;
15671         break;
15672       case EditGame:
15673         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15674         break;
15675       default:
15676         return;
15677     }
15678
15679     if (moveType == BlackDrop && selection < BlackPawn) {
15680       selection = (ChessSquare) ((int) selection
15681                                  + (int) BlackPawn - (int) WhitePawn);
15682     }
15683     if (boards[currentMove][y][x] != EmptySquare) {
15684         DisplayMoveError(_("That square is occupied"));
15685         return;
15686     }
15687
15688     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15689 }
15690
15691 void
15692 AcceptEvent ()
15693 {
15694     /* Accept a pending offer of any kind from opponent */
15695
15696     if (appData.icsActive) {
15697         SendToICS(ics_prefix);
15698         SendToICS("accept\n");
15699     } else if (cmailMsgLoaded) {
15700         if (currentMove == cmailOldMove &&
15701             commentList[cmailOldMove] != NULL &&
15702             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15703                    "Black offers a draw" : "White offers a draw")) {
15704             TruncateGame();
15705             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15706             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15707         } else {
15708             DisplayError(_("There is no pending offer on this move"), 0);
15709             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15710         }
15711     } else {
15712         /* Not used for offers from chess program */
15713     }
15714 }
15715
15716 void
15717 DeclineEvent ()
15718 {
15719     /* Decline a pending offer of any kind from opponent */
15720
15721     if (appData.icsActive) {
15722         SendToICS(ics_prefix);
15723         SendToICS("decline\n");
15724     } else if (cmailMsgLoaded) {
15725         if (currentMove == cmailOldMove &&
15726             commentList[cmailOldMove] != NULL &&
15727             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15728                    "Black offers a draw" : "White offers a draw")) {
15729 #ifdef NOTDEF
15730             AppendComment(cmailOldMove, "Draw declined", TRUE);
15731             DisplayComment(cmailOldMove - 1, "Draw declined");
15732 #endif /*NOTDEF*/
15733         } else {
15734             DisplayError(_("There is no pending offer on this move"), 0);
15735         }
15736     } else {
15737         /* Not used for offers from chess program */
15738     }
15739 }
15740
15741 void
15742 RematchEvent ()
15743 {
15744     /* Issue ICS rematch command */
15745     if (appData.icsActive) {
15746         SendToICS(ics_prefix);
15747         SendToICS("rematch\n");
15748     }
15749 }
15750
15751 void
15752 CallFlagEvent ()
15753 {
15754     /* Call your opponent's flag (claim a win on time) */
15755     if (appData.icsActive) {
15756         SendToICS(ics_prefix);
15757         SendToICS("flag\n");
15758     } else {
15759         switch (gameMode) {
15760           default:
15761             return;
15762           case MachinePlaysWhite:
15763             if (whiteFlag) {
15764                 if (blackFlag)
15765                   GameEnds(GameIsDrawn, "Both players ran out of time",
15766                            GE_PLAYER);
15767                 else
15768                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15769             } else {
15770                 DisplayError(_("Your opponent is not out of time"), 0);
15771             }
15772             break;
15773           case MachinePlaysBlack:
15774             if (blackFlag) {
15775                 if (whiteFlag)
15776                   GameEnds(GameIsDrawn, "Both players ran out of time",
15777                            GE_PLAYER);
15778                 else
15779                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15780             } else {
15781                 DisplayError(_("Your opponent is not out of time"), 0);
15782             }
15783             break;
15784         }
15785     }
15786 }
15787
15788 void
15789 ClockClick (int which)
15790 {       // [HGM] code moved to back-end from winboard.c
15791         if(which) { // black clock
15792           if (gameMode == EditPosition || gameMode == IcsExamining) {
15793             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15794             SetBlackToPlayEvent();
15795           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15796                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15797           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15798           } else if (shiftKey) {
15799             AdjustClock(which, -1);
15800           } else if (gameMode == IcsPlayingWhite ||
15801                      gameMode == MachinePlaysBlack) {
15802             CallFlagEvent();
15803           }
15804         } else { // white clock
15805           if (gameMode == EditPosition || gameMode == IcsExamining) {
15806             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15807             SetWhiteToPlayEvent();
15808           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15809                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15810           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15811           } else if (shiftKey) {
15812             AdjustClock(which, -1);
15813           } else if (gameMode == IcsPlayingBlack ||
15814                    gameMode == MachinePlaysWhite) {
15815             CallFlagEvent();
15816           }
15817         }
15818 }
15819
15820 void
15821 DrawEvent ()
15822 {
15823     /* Offer draw or accept pending draw offer from opponent */
15824
15825     if (appData.icsActive) {
15826         /* Note: tournament rules require draw offers to be
15827            made after you make your move but before you punch
15828            your clock.  Currently ICS doesn't let you do that;
15829            instead, you immediately punch your clock after making
15830            a move, but you can offer a draw at any time. */
15831
15832         SendToICS(ics_prefix);
15833         SendToICS("draw\n");
15834         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15835     } else if (cmailMsgLoaded) {
15836         if (currentMove == cmailOldMove &&
15837             commentList[cmailOldMove] != NULL &&
15838             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15839                    "Black offers a draw" : "White offers a draw")) {
15840             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15841             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15842         } else if (currentMove == cmailOldMove + 1) {
15843             char *offer = WhiteOnMove(cmailOldMove) ?
15844               "White offers a draw" : "Black offers a draw";
15845             AppendComment(currentMove, offer, TRUE);
15846             DisplayComment(currentMove - 1, offer);
15847             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15848         } else {
15849             DisplayError(_("You must make your move before offering a draw"), 0);
15850             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15851         }
15852     } else if (first.offeredDraw) {
15853         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15854     } else {
15855         if (first.sendDrawOffers) {
15856             SendToProgram("draw\n", &first);
15857             userOfferedDraw = TRUE;
15858         }
15859     }
15860 }
15861
15862 void
15863 AdjournEvent ()
15864 {
15865     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15866
15867     if (appData.icsActive) {
15868         SendToICS(ics_prefix);
15869         SendToICS("adjourn\n");
15870     } else {
15871         /* Currently GNU Chess doesn't offer or accept Adjourns */
15872     }
15873 }
15874
15875
15876 void
15877 AbortEvent ()
15878 {
15879     /* Offer Abort or accept pending Abort offer from opponent */
15880
15881     if (appData.icsActive) {
15882         SendToICS(ics_prefix);
15883         SendToICS("abort\n");
15884     } else {
15885         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15886     }
15887 }
15888
15889 void
15890 ResignEvent ()
15891 {
15892     /* Resign.  You can do this even if it's not your turn. */
15893
15894     if (appData.icsActive) {
15895         SendToICS(ics_prefix);
15896         SendToICS("resign\n");
15897     } else {
15898         switch (gameMode) {
15899           case MachinePlaysWhite:
15900             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15901             break;
15902           case MachinePlaysBlack:
15903             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15904             break;
15905           case EditGame:
15906             if (cmailMsgLoaded) {
15907                 TruncateGame();
15908                 if (WhiteOnMove(cmailOldMove)) {
15909                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15910                 } else {
15911                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15912                 }
15913                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15914             }
15915             break;
15916           default:
15917             break;
15918         }
15919     }
15920 }
15921
15922
15923 void
15924 StopObservingEvent ()
15925 {
15926     /* Stop observing current games */
15927     SendToICS(ics_prefix);
15928     SendToICS("unobserve\n");
15929 }
15930
15931 void
15932 StopExaminingEvent ()
15933 {
15934     /* Stop observing current game */
15935     SendToICS(ics_prefix);
15936     SendToICS("unexamine\n");
15937 }
15938
15939 void
15940 ForwardInner (int target)
15941 {
15942     int limit; int oldSeekGraphUp = seekGraphUp;
15943
15944     if (appData.debugMode)
15945         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15946                 target, currentMove, forwardMostMove);
15947
15948     if (gameMode == EditPosition)
15949       return;
15950
15951     seekGraphUp = FALSE;
15952     MarkTargetSquares(1);
15953     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15954
15955     if (gameMode == PlayFromGameFile && !pausing)
15956       PauseEvent();
15957
15958     if (gameMode == IcsExamining && pausing)
15959       limit = pauseExamForwardMostMove;
15960     else
15961       limit = forwardMostMove;
15962
15963     if (target > limit) target = limit;
15964
15965     if (target > 0 && moveList[target - 1][0]) {
15966         int fromX, fromY, toX, toY;
15967         toX = moveList[target - 1][2] - AAA;
15968         toY = moveList[target - 1][3] - ONE;
15969         if (moveList[target - 1][1] == '@') {
15970             if (appData.highlightLastMove) {
15971                 SetHighlights(-1, -1, toX, toY);
15972             }
15973         } else {
15974             fromX = moveList[target - 1][0] - AAA;
15975             fromY = moveList[target - 1][1] - ONE;
15976             if (target == currentMove + 1) {
15977                 if(moveList[target - 1][4] == ';') { // multi-leg
15978                     killX = moveList[target - 1][5] - AAA;
15979                     killY = moveList[target - 1][6] - ONE;
15980                 }
15981                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15982                 killX = killY = -1;
15983             }
15984             if (appData.highlightLastMove) {
15985                 SetHighlights(fromX, fromY, toX, toY);
15986             }
15987         }
15988     }
15989     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15990         gameMode == Training || gameMode == PlayFromGameFile ||
15991         gameMode == AnalyzeFile) {
15992         while (currentMove < target) {
15993             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15994             SendMoveToProgram(currentMove++, &first);
15995         }
15996     } else {
15997         currentMove = target;
15998     }
15999
16000     if (gameMode == EditGame || gameMode == EndOfGame) {
16001         whiteTimeRemaining = timeRemaining[0][currentMove];
16002         blackTimeRemaining = timeRemaining[1][currentMove];
16003     }
16004     DisplayBothClocks();
16005     DisplayMove(currentMove - 1);
16006     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16007     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16008     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16009         DisplayComment(currentMove - 1, commentList[currentMove]);
16010     }
16011     ClearMap(); // [HGM] exclude: invalidate map
16012 }
16013
16014
16015 void
16016 ForwardEvent ()
16017 {
16018     if (gameMode == IcsExamining && !pausing) {
16019         SendToICS(ics_prefix);
16020         SendToICS("forward\n");
16021     } else {
16022         ForwardInner(currentMove + 1);
16023     }
16024 }
16025
16026 void
16027 ToEndEvent ()
16028 {
16029     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16030         /* to optimze, we temporarily turn off analysis mode while we feed
16031          * the remaining moves to the engine. Otherwise we get analysis output
16032          * after each move.
16033          */
16034         if (first.analysisSupport) {
16035           SendToProgram("exit\nforce\n", &first);
16036           first.analyzing = FALSE;
16037         }
16038     }
16039
16040     if (gameMode == IcsExamining && !pausing) {
16041         SendToICS(ics_prefix);
16042         SendToICS("forward 999999\n");
16043     } else {
16044         ForwardInner(forwardMostMove);
16045     }
16046
16047     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16048         /* we have fed all the moves, so reactivate analysis mode */
16049         SendToProgram("analyze\n", &first);
16050         first.analyzing = TRUE;
16051         /*first.maybeThinking = TRUE;*/
16052         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16053     }
16054 }
16055
16056 void
16057 BackwardInner (int target)
16058 {
16059     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16060
16061     if (appData.debugMode)
16062         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16063                 target, currentMove, forwardMostMove);
16064
16065     if (gameMode == EditPosition) return;
16066     seekGraphUp = FALSE;
16067     MarkTargetSquares(1);
16068     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16069     if (currentMove <= backwardMostMove) {
16070         ClearHighlights();
16071         DrawPosition(full_redraw, boards[currentMove]);
16072         return;
16073     }
16074     if (gameMode == PlayFromGameFile && !pausing)
16075       PauseEvent();
16076
16077     if (moveList[target][0]) {
16078         int fromX, fromY, toX, toY;
16079         toX = moveList[target][2] - AAA;
16080         toY = moveList[target][3] - ONE;
16081         if (moveList[target][1] == '@') {
16082             if (appData.highlightLastMove) {
16083                 SetHighlights(-1, -1, toX, toY);
16084             }
16085         } else {
16086             fromX = moveList[target][0] - AAA;
16087             fromY = moveList[target][1] - ONE;
16088             if (target == currentMove - 1) {
16089                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16090             }
16091             if (appData.highlightLastMove) {
16092                 SetHighlights(fromX, fromY, toX, toY);
16093             }
16094         }
16095     }
16096     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16097         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16098         while (currentMove > target) {
16099             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16100                 // null move cannot be undone. Reload program with move history before it.
16101                 int i;
16102                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16103                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16104                 }
16105                 SendBoard(&first, i);
16106               if(second.analyzing) SendBoard(&second, i);
16107                 for(currentMove=i; currentMove<target; currentMove++) {
16108                     SendMoveToProgram(currentMove, &first);
16109                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16110                 }
16111                 break;
16112             }
16113             SendToBoth("undo\n");
16114             currentMove--;
16115         }
16116     } else {
16117         currentMove = target;
16118     }
16119
16120     if (gameMode == EditGame || gameMode == EndOfGame) {
16121         whiteTimeRemaining = timeRemaining[0][currentMove];
16122         blackTimeRemaining = timeRemaining[1][currentMove];
16123     }
16124     DisplayBothClocks();
16125     DisplayMove(currentMove - 1);
16126     DrawPosition(full_redraw, boards[currentMove]);
16127     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16128     // [HGM] PV info: routine tests if comment empty
16129     DisplayComment(currentMove - 1, commentList[currentMove]);
16130     ClearMap(); // [HGM] exclude: invalidate map
16131 }
16132
16133 void
16134 BackwardEvent ()
16135 {
16136     if (gameMode == IcsExamining && !pausing) {
16137         SendToICS(ics_prefix);
16138         SendToICS("backward\n");
16139     } else {
16140         BackwardInner(currentMove - 1);
16141     }
16142 }
16143
16144 void
16145 ToStartEvent ()
16146 {
16147     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16148         /* to optimize, we temporarily turn off analysis mode while we undo
16149          * all the moves. Otherwise we get analysis output after each undo.
16150          */
16151         if (first.analysisSupport) {
16152           SendToProgram("exit\nforce\n", &first);
16153           first.analyzing = FALSE;
16154         }
16155     }
16156
16157     if (gameMode == IcsExamining && !pausing) {
16158         SendToICS(ics_prefix);
16159         SendToICS("backward 999999\n");
16160     } else {
16161         BackwardInner(backwardMostMove);
16162     }
16163
16164     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16165         /* we have fed all the moves, so reactivate analysis mode */
16166         SendToProgram("analyze\n", &first);
16167         first.analyzing = TRUE;
16168         /*first.maybeThinking = TRUE;*/
16169         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16170     }
16171 }
16172
16173 void
16174 ToNrEvent (int to)
16175 {
16176   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16177   if (to >= forwardMostMove) to = forwardMostMove;
16178   if (to <= backwardMostMove) to = backwardMostMove;
16179   if (to < currentMove) {
16180     BackwardInner(to);
16181   } else {
16182     ForwardInner(to);
16183   }
16184 }
16185
16186 void
16187 RevertEvent (Boolean annotate)
16188 {
16189     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16190         return;
16191     }
16192     if (gameMode != IcsExamining) {
16193         DisplayError(_("You are not examining a game"), 0);
16194         return;
16195     }
16196     if (pausing) {
16197         DisplayError(_("You can't revert while pausing"), 0);
16198         return;
16199     }
16200     SendToICS(ics_prefix);
16201     SendToICS("revert\n");
16202 }
16203
16204 void
16205 RetractMoveEvent ()
16206 {
16207     switch (gameMode) {
16208       case MachinePlaysWhite:
16209       case MachinePlaysBlack:
16210         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16211             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16212             return;
16213         }
16214         if (forwardMostMove < 2) return;
16215         currentMove = forwardMostMove = forwardMostMove - 2;
16216         whiteTimeRemaining = timeRemaining[0][currentMove];
16217         blackTimeRemaining = timeRemaining[1][currentMove];
16218         DisplayBothClocks();
16219         DisplayMove(currentMove - 1);
16220         ClearHighlights();/*!! could figure this out*/
16221         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16222         SendToProgram("remove\n", &first);
16223         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16224         break;
16225
16226       case BeginningOfGame:
16227       default:
16228         break;
16229
16230       case IcsPlayingWhite:
16231       case IcsPlayingBlack:
16232         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16233             SendToICS(ics_prefix);
16234             SendToICS("takeback 2\n");
16235         } else {
16236             SendToICS(ics_prefix);
16237             SendToICS("takeback 1\n");
16238         }
16239         break;
16240     }
16241 }
16242
16243 void
16244 MoveNowEvent ()
16245 {
16246     ChessProgramState *cps;
16247
16248     switch (gameMode) {
16249       case MachinePlaysWhite:
16250         if (!WhiteOnMove(forwardMostMove)) {
16251             DisplayError(_("It is your turn"), 0);
16252             return;
16253         }
16254         cps = &first;
16255         break;
16256       case MachinePlaysBlack:
16257         if (WhiteOnMove(forwardMostMove)) {
16258             DisplayError(_("It is your turn"), 0);
16259             return;
16260         }
16261         cps = &first;
16262         break;
16263       case TwoMachinesPlay:
16264         if (WhiteOnMove(forwardMostMove) ==
16265             (first.twoMachinesColor[0] == 'w')) {
16266             cps = &first;
16267         } else {
16268             cps = &second;
16269         }
16270         break;
16271       case BeginningOfGame:
16272       default:
16273         return;
16274     }
16275     SendToProgram("?\n", cps);
16276 }
16277
16278 void
16279 TruncateGameEvent ()
16280 {
16281     EditGameEvent();
16282     if (gameMode != EditGame) return;
16283     TruncateGame();
16284 }
16285
16286 void
16287 TruncateGame ()
16288 {
16289     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16290     if (forwardMostMove > currentMove) {
16291         if (gameInfo.resultDetails != NULL) {
16292             free(gameInfo.resultDetails);
16293             gameInfo.resultDetails = NULL;
16294             gameInfo.result = GameUnfinished;
16295         }
16296         forwardMostMove = currentMove;
16297         HistorySet(parseList, backwardMostMove, forwardMostMove,
16298                    currentMove-1);
16299     }
16300 }
16301
16302 void
16303 HintEvent ()
16304 {
16305     if (appData.noChessProgram) return;
16306     switch (gameMode) {
16307       case MachinePlaysWhite:
16308         if (WhiteOnMove(forwardMostMove)) {
16309             DisplayError(_("Wait until your turn."), 0);
16310             return;
16311         }
16312         break;
16313       case BeginningOfGame:
16314       case MachinePlaysBlack:
16315         if (!WhiteOnMove(forwardMostMove)) {
16316             DisplayError(_("Wait until your turn."), 0);
16317             return;
16318         }
16319         break;
16320       default:
16321         DisplayError(_("No hint available"), 0);
16322         return;
16323     }
16324     SendToProgram("hint\n", &first);
16325     hintRequested = TRUE;
16326 }
16327
16328 int
16329 SaveSelected (FILE *g, int dummy, char *dummy2)
16330 {
16331     ListGame * lg = (ListGame *) gameList.head;
16332     int nItem, cnt=0;
16333     FILE *f;
16334
16335     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16336         DisplayError(_("Game list not loaded or empty"), 0);
16337         return 0;
16338     }
16339
16340     creatingBook = TRUE; // suppresses stuff during load game
16341
16342     /* Get list size */
16343     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16344         if(lg->position >= 0) { // selected?
16345             LoadGame(f, nItem, "", TRUE);
16346             SaveGamePGN2(g); // leaves g open
16347             cnt++; DoEvents();
16348         }
16349         lg = (ListGame *) lg->node.succ;
16350     }
16351
16352     fclose(g);
16353     creatingBook = FALSE;
16354
16355     return cnt;
16356 }
16357
16358 void
16359 CreateBookEvent ()
16360 {
16361     ListGame * lg = (ListGame *) gameList.head;
16362     FILE *f, *g;
16363     int nItem;
16364     static int secondTime = FALSE;
16365
16366     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16367         DisplayError(_("Game list not loaded or empty"), 0);
16368         return;
16369     }
16370
16371     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16372         fclose(g);
16373         secondTime++;
16374         DisplayNote(_("Book file exists! Try again for overwrite."));
16375         return;
16376     }
16377
16378     creatingBook = TRUE;
16379     secondTime = FALSE;
16380
16381     /* Get list size */
16382     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16383         if(lg->position >= 0) {
16384             LoadGame(f, nItem, "", TRUE);
16385             AddGameToBook(TRUE);
16386             DoEvents();
16387         }
16388         lg = (ListGame *) lg->node.succ;
16389     }
16390
16391     creatingBook = FALSE;
16392     FlushBook();
16393 }
16394
16395 void
16396 BookEvent ()
16397 {
16398     if (appData.noChessProgram) return;
16399     switch (gameMode) {
16400       case MachinePlaysWhite:
16401         if (WhiteOnMove(forwardMostMove)) {
16402             DisplayError(_("Wait until your turn."), 0);
16403             return;
16404         }
16405         break;
16406       case BeginningOfGame:
16407       case MachinePlaysBlack:
16408         if (!WhiteOnMove(forwardMostMove)) {
16409             DisplayError(_("Wait until your turn."), 0);
16410             return;
16411         }
16412         break;
16413       case EditPosition:
16414         EditPositionDone(TRUE);
16415         break;
16416       case TwoMachinesPlay:
16417         return;
16418       default:
16419         break;
16420     }
16421     SendToProgram("bk\n", &first);
16422     bookOutput[0] = NULLCHAR;
16423     bookRequested = TRUE;
16424 }
16425
16426 void
16427 AboutGameEvent ()
16428 {
16429     char *tags = PGNTags(&gameInfo);
16430     TagsPopUp(tags, CmailMsg());
16431     free(tags);
16432 }
16433
16434 /* end button procedures */
16435
16436 void
16437 PrintPosition (FILE *fp, int move)
16438 {
16439     int i, j;
16440
16441     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16442         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16443             char c = PieceToChar(boards[move][i][j]);
16444             fputc(c == '?' ? '.' : c, fp);
16445             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16446         }
16447     }
16448     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16449       fprintf(fp, "white to play\n");
16450     else
16451       fprintf(fp, "black to play\n");
16452 }
16453
16454 void
16455 PrintOpponents (FILE *fp)
16456 {
16457     if (gameInfo.white != NULL) {
16458         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16459     } else {
16460         fprintf(fp, "\n");
16461     }
16462 }
16463
16464 /* Find last component of program's own name, using some heuristics */
16465 void
16466 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16467 {
16468     char *p, *q, c;
16469     int local = (strcmp(host, "localhost") == 0);
16470     while (!local && (p = strchr(prog, ';')) != NULL) {
16471         p++;
16472         while (*p == ' ') p++;
16473         prog = p;
16474     }
16475     if (*prog == '"' || *prog == '\'') {
16476         q = strchr(prog + 1, *prog);
16477     } else {
16478         q = strchr(prog, ' ');
16479     }
16480     if (q == NULL) q = prog + strlen(prog);
16481     p = q;
16482     while (p >= prog && *p != '/' && *p != '\\') p--;
16483     p++;
16484     if(p == prog && *p == '"') p++;
16485     c = *q; *q = 0;
16486     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16487     memcpy(buf, p, q - p);
16488     buf[q - p] = NULLCHAR;
16489     if (!local) {
16490         strcat(buf, "@");
16491         strcat(buf, host);
16492     }
16493 }
16494
16495 char *
16496 TimeControlTagValue ()
16497 {
16498     char buf[MSG_SIZ];
16499     if (!appData.clockMode) {
16500       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16501     } else if (movesPerSession > 0) {
16502       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16503     } else if (timeIncrement == 0) {
16504       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16505     } else {
16506       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16507     }
16508     return StrSave(buf);
16509 }
16510
16511 void
16512 SetGameInfo ()
16513 {
16514     /* This routine is used only for certain modes */
16515     VariantClass v = gameInfo.variant;
16516     ChessMove r = GameUnfinished;
16517     char *p = NULL;
16518
16519     if(keepInfo) return;
16520
16521     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16522         r = gameInfo.result;
16523         p = gameInfo.resultDetails;
16524         gameInfo.resultDetails = NULL;
16525     }
16526     ClearGameInfo(&gameInfo);
16527     gameInfo.variant = v;
16528
16529     switch (gameMode) {
16530       case MachinePlaysWhite:
16531         gameInfo.event = StrSave( appData.pgnEventHeader );
16532         gameInfo.site = StrSave(HostName());
16533         gameInfo.date = PGNDate();
16534         gameInfo.round = StrSave("-");
16535         gameInfo.white = StrSave(first.tidy);
16536         gameInfo.black = StrSave(UserName());
16537         gameInfo.timeControl = TimeControlTagValue();
16538         break;
16539
16540       case MachinePlaysBlack:
16541         gameInfo.event = StrSave( appData.pgnEventHeader );
16542         gameInfo.site = StrSave(HostName());
16543         gameInfo.date = PGNDate();
16544         gameInfo.round = StrSave("-");
16545         gameInfo.white = StrSave(UserName());
16546         gameInfo.black = StrSave(first.tidy);
16547         gameInfo.timeControl = TimeControlTagValue();
16548         break;
16549
16550       case TwoMachinesPlay:
16551         gameInfo.event = StrSave( appData.pgnEventHeader );
16552         gameInfo.site = StrSave(HostName());
16553         gameInfo.date = PGNDate();
16554         if (roundNr > 0) {
16555             char buf[MSG_SIZ];
16556             snprintf(buf, MSG_SIZ, "%d", roundNr);
16557             gameInfo.round = StrSave(buf);
16558         } else {
16559             gameInfo.round = StrSave("-");
16560         }
16561         if (first.twoMachinesColor[0] == 'w') {
16562             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16563             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16564         } else {
16565             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16566             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16567         }
16568         gameInfo.timeControl = TimeControlTagValue();
16569         break;
16570
16571       case EditGame:
16572         gameInfo.event = StrSave("Edited game");
16573         gameInfo.site = StrSave(HostName());
16574         gameInfo.date = PGNDate();
16575         gameInfo.round = StrSave("-");
16576         gameInfo.white = StrSave("-");
16577         gameInfo.black = StrSave("-");
16578         gameInfo.result = r;
16579         gameInfo.resultDetails = p;
16580         break;
16581
16582       case EditPosition:
16583         gameInfo.event = StrSave("Edited position");
16584         gameInfo.site = StrSave(HostName());
16585         gameInfo.date = PGNDate();
16586         gameInfo.round = StrSave("-");
16587         gameInfo.white = StrSave("-");
16588         gameInfo.black = StrSave("-");
16589         break;
16590
16591       case IcsPlayingWhite:
16592       case IcsPlayingBlack:
16593       case IcsObserving:
16594       case IcsExamining:
16595         break;
16596
16597       case PlayFromGameFile:
16598         gameInfo.event = StrSave("Game from non-PGN file");
16599         gameInfo.site = StrSave(HostName());
16600         gameInfo.date = PGNDate();
16601         gameInfo.round = StrSave("-");
16602         gameInfo.white = StrSave("?");
16603         gameInfo.black = StrSave("?");
16604         break;
16605
16606       default:
16607         break;
16608     }
16609 }
16610
16611 void
16612 ReplaceComment (int index, char *text)
16613 {
16614     int len;
16615     char *p;
16616     float score;
16617
16618     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16619        pvInfoList[index-1].depth == len &&
16620        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16621        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16622     while (*text == '\n') text++;
16623     len = strlen(text);
16624     while (len > 0 && text[len - 1] == '\n') len--;
16625
16626     if (commentList[index] != NULL)
16627       free(commentList[index]);
16628
16629     if (len == 0) {
16630         commentList[index] = NULL;
16631         return;
16632     }
16633   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16634       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16635       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16636     commentList[index] = (char *) malloc(len + 2);
16637     strncpy(commentList[index], text, len);
16638     commentList[index][len] = '\n';
16639     commentList[index][len + 1] = NULLCHAR;
16640   } else {
16641     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16642     char *p;
16643     commentList[index] = (char *) malloc(len + 7);
16644     safeStrCpy(commentList[index], "{\n", 3);
16645     safeStrCpy(commentList[index]+2, text, len+1);
16646     commentList[index][len+2] = NULLCHAR;
16647     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16648     strcat(commentList[index], "\n}\n");
16649   }
16650 }
16651
16652 void
16653 CrushCRs (char *text)
16654 {
16655   char *p = text;
16656   char *q = text;
16657   char ch;
16658
16659   do {
16660     ch = *p++;
16661     if (ch == '\r') continue;
16662     *q++ = ch;
16663   } while (ch != '\0');
16664 }
16665
16666 void
16667 AppendComment (int index, char *text, Boolean addBraces)
16668 /* addBraces  tells if we should add {} */
16669 {
16670     int oldlen, len;
16671     char *old;
16672
16673 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16674     if(addBraces == 3) addBraces = 0; else // force appending literally
16675     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16676
16677     CrushCRs(text);
16678     while (*text == '\n') text++;
16679     len = strlen(text);
16680     while (len > 0 && text[len - 1] == '\n') len--;
16681     text[len] = NULLCHAR;
16682
16683     if (len == 0) return;
16684
16685     if (commentList[index] != NULL) {
16686       Boolean addClosingBrace = addBraces;
16687         old = commentList[index];
16688         oldlen = strlen(old);
16689         while(commentList[index][oldlen-1] ==  '\n')
16690           commentList[index][--oldlen] = NULLCHAR;
16691         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16692         safeStrCpy(commentList[index], old, oldlen + len + 6);
16693         free(old);
16694         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16695         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16696           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16697           while (*text == '\n') { text++; len--; }
16698           commentList[index][--oldlen] = NULLCHAR;
16699       }
16700         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16701         else          strcat(commentList[index], "\n");
16702         strcat(commentList[index], text);
16703         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16704         else          strcat(commentList[index], "\n");
16705     } else {
16706         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16707         if(addBraces)
16708           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16709         else commentList[index][0] = NULLCHAR;
16710         strcat(commentList[index], text);
16711         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16712         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16713     }
16714 }
16715
16716 static char *
16717 FindStr (char * text, char * sub_text)
16718 {
16719     char * result = strstr( text, sub_text );
16720
16721     if( result != NULL ) {
16722         result += strlen( sub_text );
16723     }
16724
16725     return result;
16726 }
16727
16728 /* [AS] Try to extract PV info from PGN comment */
16729 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16730 char *
16731 GetInfoFromComment (int index, char * text)
16732 {
16733     char * sep = text, *p;
16734
16735     if( text != NULL && index > 0 ) {
16736         int score = 0;
16737         int depth = 0;
16738         int time = -1, sec = 0, deci;
16739         char * s_eval = FindStr( text, "[%eval " );
16740         char * s_emt = FindStr( text, "[%emt " );
16741 #if 0
16742         if( s_eval != NULL || s_emt != NULL ) {
16743 #else
16744         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16745 #endif
16746             /* New style */
16747             char delim;
16748
16749             if( s_eval != NULL ) {
16750                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16751                     return text;
16752                 }
16753
16754                 if( delim != ']' ) {
16755                     return text;
16756                 }
16757             }
16758
16759             if( s_emt != NULL ) {
16760             }
16761                 return text;
16762         }
16763         else {
16764             /* We expect something like: [+|-]nnn.nn/dd */
16765             int score_lo = 0;
16766
16767             if(*text != '{') return text; // [HGM] braces: must be normal comment
16768
16769             sep = strchr( text, '/' );
16770             if( sep == NULL || sep < (text+4) ) {
16771                 return text;
16772             }
16773
16774             p = text;
16775             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16776             if(p[1] == '(') { // comment starts with PV
16777                p = strchr(p, ')'); // locate end of PV
16778                if(p == NULL || sep < p+5) return text;
16779                // at this point we have something like "{(.*) +0.23/6 ..."
16780                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16781                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16782                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16783             }
16784             time = -1; sec = -1; deci = -1;
16785             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16786                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16787                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16788                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16789                 return text;
16790             }
16791
16792             if( score_lo < 0 || score_lo >= 100 ) {
16793                 return text;
16794             }
16795
16796             if(sec >= 0) time = 600*time + 10*sec; else
16797             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16798
16799             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16800
16801             /* [HGM] PV time: now locate end of PV info */
16802             while( *++sep >= '0' && *sep <= '9'); // strip depth
16803             if(time >= 0)
16804             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16805             if(sec >= 0)
16806             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16807             if(deci >= 0)
16808             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16809             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16810         }
16811
16812         if( depth <= 0 ) {
16813             return text;
16814         }
16815
16816         if( time < 0 ) {
16817             time = -1;
16818         }
16819
16820         pvInfoList[index-1].depth = depth;
16821         pvInfoList[index-1].score = score;
16822         pvInfoList[index-1].time  = 10*time; // centi-sec
16823         if(*sep == '}') *sep = 0; else *--sep = '{';
16824         if(p != text) {
16825             while(*p++ = *sep++)
16826                                 ;
16827             sep = text;
16828         } // squeeze out space between PV and comment, and return both
16829     }
16830     return sep;
16831 }
16832
16833 void
16834 SendToProgram (char *message, ChessProgramState *cps)
16835 {
16836     int count, outCount, error;
16837     char buf[MSG_SIZ];
16838
16839     if (cps->pr == NoProc) return;
16840     Attention(cps);
16841
16842     if (appData.debugMode) {
16843         TimeMark now;
16844         GetTimeMark(&now);
16845         fprintf(debugFP, "%ld >%-6s: %s",
16846                 SubtractTimeMarks(&now, &programStartTime),
16847                 cps->which, message);
16848         if(serverFP)
16849             fprintf(serverFP, "%ld >%-6s: %s",
16850                 SubtractTimeMarks(&now, &programStartTime),
16851                 cps->which, message), fflush(serverFP);
16852     }
16853
16854     count = strlen(message);
16855     outCount = OutputToProcess(cps->pr, message, count, &error);
16856     if (outCount < count && !exiting
16857                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16858       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16859       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16860         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16861             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16862                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16863                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16864                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16865             } else {
16866                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16867                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16868                 gameInfo.result = res;
16869             }
16870             gameInfo.resultDetails = StrSave(buf);
16871         }
16872         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16873         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16874     }
16875 }
16876
16877 void
16878 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16879 {
16880     char *end_str;
16881     char buf[MSG_SIZ];
16882     ChessProgramState *cps = (ChessProgramState *)closure;
16883
16884     if (isr != cps->isr) return; /* Killed intentionally */
16885     if (count <= 0) {
16886         if (count == 0) {
16887             RemoveInputSource(cps->isr);
16888             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16889                     _(cps->which), cps->program);
16890             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16891             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16892                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16893                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16894                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16895                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16896                 } else {
16897                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16898                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16899                     gameInfo.result = res;
16900                 }
16901                 gameInfo.resultDetails = StrSave(buf);
16902             }
16903             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16904             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16905         } else {
16906             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16907                     _(cps->which), cps->program);
16908             RemoveInputSource(cps->isr);
16909
16910             /* [AS] Program is misbehaving badly... kill it */
16911             if( count == -2 ) {
16912                 DestroyChildProcess( cps->pr, 9 );
16913                 cps->pr = NoProc;
16914             }
16915
16916             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16917         }
16918         return;
16919     }
16920
16921     if ((end_str = strchr(message, '\r')) != NULL)
16922       *end_str = NULLCHAR;
16923     if ((end_str = strchr(message, '\n')) != NULL)
16924       *end_str = NULLCHAR;
16925
16926     if (appData.debugMode) {
16927         TimeMark now; int print = 1;
16928         char *quote = ""; char c; int i;
16929
16930         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16931                 char start = message[0];
16932                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16933                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16934                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16935                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16936                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16937                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16938                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16939                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16940                    sscanf(message, "hint: %c", &c)!=1 &&
16941                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16942                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16943                     print = (appData.engineComments >= 2);
16944                 }
16945                 message[0] = start; // restore original message
16946         }
16947         if(print) {
16948                 GetTimeMark(&now);
16949                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16950                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16951                         quote,
16952                         message);
16953                 if(serverFP)
16954                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16955                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16956                         quote,
16957                         message), fflush(serverFP);
16958         }
16959     }
16960
16961     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16962     if (appData.icsEngineAnalyze) {
16963         if (strstr(message, "whisper") != NULL ||
16964              strstr(message, "kibitz") != NULL ||
16965             strstr(message, "tellics") != NULL) return;
16966     }
16967
16968     HandleMachineMove(message, cps);
16969 }
16970
16971
16972 void
16973 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16974 {
16975     char buf[MSG_SIZ];
16976     int seconds;
16977
16978     if( timeControl_2 > 0 ) {
16979         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16980             tc = timeControl_2;
16981         }
16982     }
16983     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16984     inc /= cps->timeOdds;
16985     st  /= cps->timeOdds;
16986
16987     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16988
16989     if (st > 0) {
16990       /* Set exact time per move, normally using st command */
16991       if (cps->stKludge) {
16992         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16993         seconds = st % 60;
16994         if (seconds == 0) {
16995           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16996         } else {
16997           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16998         }
16999       } else {
17000         snprintf(buf, MSG_SIZ, "st %d\n", st);
17001       }
17002     } else {
17003       /* Set conventional or incremental time control, using level command */
17004       if (seconds == 0) {
17005         /* Note old gnuchess bug -- minutes:seconds used to not work.
17006            Fixed in later versions, but still avoid :seconds
17007            when seconds is 0. */
17008         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17009       } else {
17010         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17011                  seconds, inc/1000.);
17012       }
17013     }
17014     SendToProgram(buf, cps);
17015
17016     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17017     /* Orthogonally, limit search to given depth */
17018     if (sd > 0) {
17019       if (cps->sdKludge) {
17020         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17021       } else {
17022         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17023       }
17024       SendToProgram(buf, cps);
17025     }
17026
17027     if(cps->nps >= 0) { /* [HGM] nps */
17028         if(cps->supportsNPS == FALSE)
17029           cps->nps = -1; // don't use if engine explicitly says not supported!
17030         else {
17031           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17032           SendToProgram(buf, cps);
17033         }
17034     }
17035 }
17036
17037 ChessProgramState *
17038 WhitePlayer ()
17039 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17040 {
17041     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17042        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17043         return &second;
17044     return &first;
17045 }
17046
17047 void
17048 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17049 {
17050     char message[MSG_SIZ];
17051     long time, otime;
17052
17053     /* Note: this routine must be called when the clocks are stopped
17054        or when they have *just* been set or switched; otherwise
17055        it will be off by the time since the current tick started.
17056     */
17057     if (machineWhite) {
17058         time = whiteTimeRemaining / 10;
17059         otime = blackTimeRemaining / 10;
17060     } else {
17061         time = blackTimeRemaining / 10;
17062         otime = whiteTimeRemaining / 10;
17063     }
17064     /* [HGM] translate opponent's time by time-odds factor */
17065     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17066
17067     if (time <= 0) time = 1;
17068     if (otime <= 0) otime = 1;
17069
17070     snprintf(message, MSG_SIZ, "time %ld\n", time);
17071     SendToProgram(message, cps);
17072
17073     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17074     SendToProgram(message, cps);
17075 }
17076
17077 char *
17078 EngineDefinedVariant (ChessProgramState *cps, int n)
17079 {   // return name of n-th unknown variant that engine supports
17080     static char buf[MSG_SIZ];
17081     char *p, *s = cps->variants;
17082     if(!s) return NULL;
17083     do { // parse string from variants feature
17084       VariantClass v;
17085         p = strchr(s, ',');
17086         if(p) *p = NULLCHAR;
17087       v = StringToVariant(s);
17088       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17089         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17090             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17091                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17092                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17093                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17094             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17095         }
17096         if(p) *p++ = ',';
17097         if(n < 0) return buf;
17098     } while(s = p);
17099     return NULL;
17100 }
17101
17102 int
17103 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17104 {
17105   char buf[MSG_SIZ];
17106   int len = strlen(name);
17107   int val;
17108
17109   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17110     (*p) += len + 1;
17111     sscanf(*p, "%d", &val);
17112     *loc = (val != 0);
17113     while (**p && **p != ' ')
17114       (*p)++;
17115     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17116     SendToProgram(buf, cps);
17117     return TRUE;
17118   }
17119   return FALSE;
17120 }
17121
17122 int
17123 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17124 {
17125   char buf[MSG_SIZ];
17126   int len = strlen(name);
17127   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17128     (*p) += len + 1;
17129     sscanf(*p, "%d", loc);
17130     while (**p && **p != ' ') (*p)++;
17131     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17132     SendToProgram(buf, cps);
17133     return TRUE;
17134   }
17135   return FALSE;
17136 }
17137
17138 int
17139 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17140 {
17141   char buf[MSG_SIZ];
17142   int len = strlen(name);
17143   if (strncmp((*p), name, len) == 0
17144       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17145     (*p) += len + 2;
17146     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17147     sscanf(*p, "%[^\"]", *loc);
17148     while (**p && **p != '\"') (*p)++;
17149     if (**p == '\"') (*p)++;
17150     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17151     SendToProgram(buf, cps);
17152     return TRUE;
17153   }
17154   return FALSE;
17155 }
17156
17157 int
17158 ParseOption (Option *opt, ChessProgramState *cps)
17159 // [HGM] options: process the string that defines an engine option, and determine
17160 // name, type, default value, and allowed value range
17161 {
17162         char *p, *q, buf[MSG_SIZ];
17163         int n, min = (-1)<<31, max = 1<<31, def;
17164
17165         opt->target = &opt->value;   // OK for spin/slider and checkbox
17166         if(p = strstr(opt->name, " -spin ")) {
17167             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17168             if(max < min) max = min; // enforce consistency
17169             if(def < min) def = min;
17170             if(def > max) def = max;
17171             opt->value = def;
17172             opt->min = min;
17173             opt->max = max;
17174             opt->type = Spin;
17175         } else if((p = strstr(opt->name, " -slider "))) {
17176             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17177             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17178             if(max < min) max = min; // enforce consistency
17179             if(def < min) def = min;
17180             if(def > max) def = max;
17181             opt->value = def;
17182             opt->min = min;
17183             opt->max = max;
17184             opt->type = Spin; // Slider;
17185         } else if((p = strstr(opt->name, " -string "))) {
17186             opt->textValue = p+9;
17187             opt->type = TextBox;
17188             opt->target = &opt->textValue;
17189         } else if((p = strstr(opt->name, " -file "))) {
17190             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17191             opt->target = opt->textValue = p+7;
17192             opt->type = FileName; // FileName;
17193             opt->target = &opt->textValue;
17194         } else if((p = strstr(opt->name, " -path "))) {
17195             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17196             opt->target = opt->textValue = p+7;
17197             opt->type = PathName; // PathName;
17198             opt->target = &opt->textValue;
17199         } else if(p = strstr(opt->name, " -check ")) {
17200             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17201             opt->value = (def != 0);
17202             opt->type = CheckBox;
17203         } else if(p = strstr(opt->name, " -combo ")) {
17204             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17205             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17206             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17207             opt->value = n = 0;
17208             while(q = StrStr(q, " /// ")) {
17209                 n++; *q = 0;    // count choices, and null-terminate each of them
17210                 q += 5;
17211                 if(*q == '*') { // remember default, which is marked with * prefix
17212                     q++;
17213                     opt->value = n;
17214                 }
17215                 cps->comboList[cps->comboCnt++] = q;
17216             }
17217             cps->comboList[cps->comboCnt++] = NULL;
17218             opt->max = n + 1;
17219             opt->type = ComboBox;
17220         } else if(p = strstr(opt->name, " -button")) {
17221             opt->type = Button;
17222         } else if(p = strstr(opt->name, " -save")) {
17223             opt->type = SaveButton;
17224         } else return FALSE;
17225         *p = 0; // terminate option name
17226         // now look if the command-line options define a setting for this engine option.
17227         if(cps->optionSettings && cps->optionSettings[0])
17228             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17229         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17230           snprintf(buf, MSG_SIZ, "option %s", p);
17231                 if(p = strstr(buf, ",")) *p = 0;
17232                 if(q = strchr(buf, '=')) switch(opt->type) {
17233                     case ComboBox:
17234                         for(n=0; n<opt->max; n++)
17235                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17236                         break;
17237                     case TextBox:
17238                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17239                         break;
17240                     case Spin:
17241                     case CheckBox:
17242                         opt->value = atoi(q+1);
17243                     default:
17244                         break;
17245                 }
17246                 strcat(buf, "\n");
17247                 SendToProgram(buf, cps);
17248         }
17249         return TRUE;
17250 }
17251
17252 void
17253 FeatureDone (ChessProgramState *cps, int val)
17254 {
17255   DelayedEventCallback cb = GetDelayedEvent();
17256   if ((cb == InitBackEnd3 && cps == &first) ||
17257       (cb == SettingsMenuIfReady && cps == &second) ||
17258       (cb == LoadEngine) ||
17259       (cb == TwoMachinesEventIfReady)) {
17260     CancelDelayedEvent();
17261     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17262   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17263   cps->initDone = val;
17264   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17265 }
17266
17267 /* Parse feature command from engine */
17268 void
17269 ParseFeatures (char *args, ChessProgramState *cps)
17270 {
17271   char *p = args;
17272   char *q = NULL;
17273   int val;
17274   char buf[MSG_SIZ];
17275
17276   for (;;) {
17277     while (*p == ' ') p++;
17278     if (*p == NULLCHAR) return;
17279
17280     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17281     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17282     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17283     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17284     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17285     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17286     if (BoolFeature(&p, "reuse", &val, cps)) {
17287       /* Engine can disable reuse, but can't enable it if user said no */
17288       if (!val) cps->reuse = FALSE;
17289       continue;
17290     }
17291     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17292     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17293       if (gameMode == TwoMachinesPlay) {
17294         DisplayTwoMachinesTitle();
17295       } else {
17296         DisplayTitle("");
17297       }
17298       continue;
17299     }
17300     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17301     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17302     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17303     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17304     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17305     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17306     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17307     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17308     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17309     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17310     if (IntFeature(&p, "done", &val, cps)) {
17311       FeatureDone(cps, val);
17312       continue;
17313     }
17314     /* Added by Tord: */
17315     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17316     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17317     /* End of additions by Tord */
17318
17319     /* [HGM] added features: */
17320     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17321     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17322     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17323     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17324     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17325     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17326     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17327     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17328         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17329         FREE(cps->option[cps->nrOptions].name);
17330         cps->option[cps->nrOptions].name = q; q = NULL;
17331         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17332           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17333             SendToProgram(buf, cps);
17334             continue;
17335         }
17336         if(cps->nrOptions >= MAX_OPTIONS) {
17337             cps->nrOptions--;
17338             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17339             DisplayError(buf, 0);
17340         }
17341         continue;
17342     }
17343     /* End of additions by HGM */
17344
17345     /* unknown feature: complain and skip */
17346     q = p;
17347     while (*q && *q != '=') q++;
17348     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17349     SendToProgram(buf, cps);
17350     p = q;
17351     if (*p == '=') {
17352       p++;
17353       if (*p == '\"') {
17354         p++;
17355         while (*p && *p != '\"') p++;
17356         if (*p == '\"') p++;
17357       } else {
17358         while (*p && *p != ' ') p++;
17359       }
17360     }
17361   }
17362
17363 }
17364
17365 void
17366 PeriodicUpdatesEvent (int newState)
17367 {
17368     if (newState == appData.periodicUpdates)
17369       return;
17370
17371     appData.periodicUpdates=newState;
17372
17373     /* Display type changes, so update it now */
17374 //    DisplayAnalysis();
17375
17376     /* Get the ball rolling again... */
17377     if (newState) {
17378         AnalysisPeriodicEvent(1);
17379         StartAnalysisClock();
17380     }
17381 }
17382
17383 void
17384 PonderNextMoveEvent (int newState)
17385 {
17386     if (newState == appData.ponderNextMove) return;
17387     if (gameMode == EditPosition) EditPositionDone(TRUE);
17388     if (newState) {
17389         SendToProgram("hard\n", &first);
17390         if (gameMode == TwoMachinesPlay) {
17391             SendToProgram("hard\n", &second);
17392         }
17393     } else {
17394         SendToProgram("easy\n", &first);
17395         thinkOutput[0] = NULLCHAR;
17396         if (gameMode == TwoMachinesPlay) {
17397             SendToProgram("easy\n", &second);
17398         }
17399     }
17400     appData.ponderNextMove = newState;
17401 }
17402
17403 void
17404 NewSettingEvent (int option, int *feature, char *command, int value)
17405 {
17406     char buf[MSG_SIZ];
17407
17408     if (gameMode == EditPosition) EditPositionDone(TRUE);
17409     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17410     if(feature == NULL || *feature) SendToProgram(buf, &first);
17411     if (gameMode == TwoMachinesPlay) {
17412         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17413     }
17414 }
17415
17416 void
17417 ShowThinkingEvent ()
17418 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17419 {
17420     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17421     int newState = appData.showThinking
17422         // [HGM] thinking: other features now need thinking output as well
17423         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17424
17425     if (oldState == newState) return;
17426     oldState = newState;
17427     if (gameMode == EditPosition) EditPositionDone(TRUE);
17428     if (oldState) {
17429         SendToProgram("post\n", &first);
17430         if (gameMode == TwoMachinesPlay) {
17431             SendToProgram("post\n", &second);
17432         }
17433     } else {
17434         SendToProgram("nopost\n", &first);
17435         thinkOutput[0] = NULLCHAR;
17436         if (gameMode == TwoMachinesPlay) {
17437             SendToProgram("nopost\n", &second);
17438         }
17439     }
17440 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17441 }
17442
17443 void
17444 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17445 {
17446   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17447   if (pr == NoProc) return;
17448   AskQuestion(title, question, replyPrefix, pr);
17449 }
17450
17451 void
17452 TypeInEvent (char firstChar)
17453 {
17454     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17455         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17456         gameMode == AnalyzeMode || gameMode == EditGame ||
17457         gameMode == EditPosition || gameMode == IcsExamining ||
17458         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17459         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17460                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17461                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17462         gameMode == Training) PopUpMoveDialog(firstChar);
17463 }
17464
17465 void
17466 TypeInDoneEvent (char *move)
17467 {
17468         Board board;
17469         int n, fromX, fromY, toX, toY;
17470         char promoChar;
17471         ChessMove moveType;
17472
17473         // [HGM] FENedit
17474         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17475                 EditPositionPasteFEN(move);
17476                 return;
17477         }
17478         // [HGM] movenum: allow move number to be typed in any mode
17479         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17480           ToNrEvent(2*n-1);
17481           return;
17482         }
17483         // undocumented kludge: allow command-line option to be typed in!
17484         // (potentially fatal, and does not implement the effect of the option.)
17485         // should only be used for options that are values on which future decisions will be made,
17486         // and definitely not on options that would be used during initialization.
17487         if(strstr(move, "!!! -") == move) {
17488             ParseArgsFromString(move+4);
17489             return;
17490         }
17491
17492       if (gameMode != EditGame && currentMove != forwardMostMove &&
17493         gameMode != Training) {
17494         DisplayMoveError(_("Displayed move is not current"));
17495       } else {
17496         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17497           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17498         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17499         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17500           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17501           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17502         } else {
17503           DisplayMoveError(_("Could not parse move"));
17504         }
17505       }
17506 }
17507
17508 void
17509 DisplayMove (int moveNumber)
17510 {
17511     char message[MSG_SIZ];
17512     char res[MSG_SIZ];
17513     char cpThinkOutput[MSG_SIZ];
17514
17515     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17516
17517     if (moveNumber == forwardMostMove - 1 ||
17518         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17519
17520         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17521
17522         if (strchr(cpThinkOutput, '\n')) {
17523             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17524         }
17525     } else {
17526         *cpThinkOutput = NULLCHAR;
17527     }
17528
17529     /* [AS] Hide thinking from human user */
17530     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17531         *cpThinkOutput = NULLCHAR;
17532         if( thinkOutput[0] != NULLCHAR ) {
17533             int i;
17534
17535             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17536                 cpThinkOutput[i] = '.';
17537             }
17538             cpThinkOutput[i] = NULLCHAR;
17539             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17540         }
17541     }
17542
17543     if (moveNumber == forwardMostMove - 1 &&
17544         gameInfo.resultDetails != NULL) {
17545         if (gameInfo.resultDetails[0] == NULLCHAR) {
17546           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17547         } else {
17548           snprintf(res, MSG_SIZ, " {%s} %s",
17549                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17550         }
17551     } else {
17552         res[0] = NULLCHAR;
17553     }
17554
17555     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17556         DisplayMessage(res, cpThinkOutput);
17557     } else {
17558       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17559                 WhiteOnMove(moveNumber) ? " " : ".. ",
17560                 parseList[moveNumber], res);
17561         DisplayMessage(message, cpThinkOutput);
17562     }
17563 }
17564
17565 void
17566 DisplayComment (int moveNumber, char *text)
17567 {
17568     char title[MSG_SIZ];
17569
17570     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17571       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17572     } else {
17573       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17574               WhiteOnMove(moveNumber) ? " " : ".. ",
17575               parseList[moveNumber]);
17576     }
17577     if (text != NULL && (appData.autoDisplayComment || commentUp))
17578         CommentPopUp(title, text);
17579 }
17580
17581 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17582  * might be busy thinking or pondering.  It can be omitted if your
17583  * gnuchess is configured to stop thinking immediately on any user
17584  * input.  However, that gnuchess feature depends on the FIONREAD
17585  * ioctl, which does not work properly on some flavors of Unix.
17586  */
17587 void
17588 Attention (ChessProgramState *cps)
17589 {
17590 #if ATTENTION
17591     if (!cps->useSigint) return;
17592     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17593     switch (gameMode) {
17594       case MachinePlaysWhite:
17595       case MachinePlaysBlack:
17596       case TwoMachinesPlay:
17597       case IcsPlayingWhite:
17598       case IcsPlayingBlack:
17599       case AnalyzeMode:
17600       case AnalyzeFile:
17601         /* Skip if we know it isn't thinking */
17602         if (!cps->maybeThinking) return;
17603         if (appData.debugMode)
17604           fprintf(debugFP, "Interrupting %s\n", cps->which);
17605         InterruptChildProcess(cps->pr);
17606         cps->maybeThinking = FALSE;
17607         break;
17608       default:
17609         break;
17610     }
17611 #endif /*ATTENTION*/
17612 }
17613
17614 int
17615 CheckFlags ()
17616 {
17617     if (whiteTimeRemaining <= 0) {
17618         if (!whiteFlag) {
17619             whiteFlag = TRUE;
17620             if (appData.icsActive) {
17621                 if (appData.autoCallFlag &&
17622                     gameMode == IcsPlayingBlack && !blackFlag) {
17623                   SendToICS(ics_prefix);
17624                   SendToICS("flag\n");
17625                 }
17626             } else {
17627                 if (blackFlag) {
17628                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17629                 } else {
17630                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17631                     if (appData.autoCallFlag) {
17632                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17633                         return TRUE;
17634                     }
17635                 }
17636             }
17637         }
17638     }
17639     if (blackTimeRemaining <= 0) {
17640         if (!blackFlag) {
17641             blackFlag = TRUE;
17642             if (appData.icsActive) {
17643                 if (appData.autoCallFlag &&
17644                     gameMode == IcsPlayingWhite && !whiteFlag) {
17645                   SendToICS(ics_prefix);
17646                   SendToICS("flag\n");
17647                 }
17648             } else {
17649                 if (whiteFlag) {
17650                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17651                 } else {
17652                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17653                     if (appData.autoCallFlag) {
17654                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17655                         return TRUE;
17656                     }
17657                 }
17658             }
17659         }
17660     }
17661     return FALSE;
17662 }
17663
17664 void
17665 CheckTimeControl ()
17666 {
17667     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17668         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17669
17670     /*
17671      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17672      */
17673     if ( !WhiteOnMove(forwardMostMove) ) {
17674         /* White made time control */
17675         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17676         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17677         /* [HGM] time odds: correct new time quota for time odds! */
17678                                             / WhitePlayer()->timeOdds;
17679         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17680     } else {
17681         lastBlack -= blackTimeRemaining;
17682         /* Black made time control */
17683         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17684                                             / WhitePlayer()->other->timeOdds;
17685         lastWhite = whiteTimeRemaining;
17686     }
17687 }
17688
17689 void
17690 DisplayBothClocks ()
17691 {
17692     int wom = gameMode == EditPosition ?
17693       !blackPlaysFirst : WhiteOnMove(currentMove);
17694     DisplayWhiteClock(whiteTimeRemaining, wom);
17695     DisplayBlackClock(blackTimeRemaining, !wom);
17696 }
17697
17698
17699 /* Timekeeping seems to be a portability nightmare.  I think everyone
17700    has ftime(), but I'm really not sure, so I'm including some ifdefs
17701    to use other calls if you don't.  Clocks will be less accurate if
17702    you have neither ftime nor gettimeofday.
17703 */
17704
17705 /* VS 2008 requires the #include outside of the function */
17706 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17707 #include <sys/timeb.h>
17708 #endif
17709
17710 /* Get the current time as a TimeMark */
17711 void
17712 GetTimeMark (TimeMark *tm)
17713 {
17714 #if HAVE_GETTIMEOFDAY
17715
17716     struct timeval timeVal;
17717     struct timezone timeZone;
17718
17719     gettimeofday(&timeVal, &timeZone);
17720     tm->sec = (long) timeVal.tv_sec;
17721     tm->ms = (int) (timeVal.tv_usec / 1000L);
17722
17723 #else /*!HAVE_GETTIMEOFDAY*/
17724 #if HAVE_FTIME
17725
17726 // include <sys/timeb.h> / moved to just above start of function
17727     struct timeb timeB;
17728
17729     ftime(&timeB);
17730     tm->sec = (long) timeB.time;
17731     tm->ms = (int) timeB.millitm;
17732
17733 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17734     tm->sec = (long) time(NULL);
17735     tm->ms = 0;
17736 #endif
17737 #endif
17738 }
17739
17740 /* Return the difference in milliseconds between two
17741    time marks.  We assume the difference will fit in a long!
17742 */
17743 long
17744 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17745 {
17746     return 1000L*(tm2->sec - tm1->sec) +
17747            (long) (tm2->ms - tm1->ms);
17748 }
17749
17750
17751 /*
17752  * Code to manage the game clocks.
17753  *
17754  * In tournament play, black starts the clock and then white makes a move.
17755  * We give the human user a slight advantage if he is playing white---the
17756  * clocks don't run until he makes his first move, so it takes zero time.
17757  * Also, we don't account for network lag, so we could get out of sync
17758  * with GNU Chess's clock -- but then, referees are always right.
17759  */
17760
17761 static TimeMark tickStartTM;
17762 static long intendedTickLength;
17763
17764 long
17765 NextTickLength (long timeRemaining)
17766 {
17767     long nominalTickLength, nextTickLength;
17768
17769     if (timeRemaining > 0L && timeRemaining <= 10000L)
17770       nominalTickLength = 100L;
17771     else
17772       nominalTickLength = 1000L;
17773     nextTickLength = timeRemaining % nominalTickLength;
17774     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17775
17776     return nextTickLength;
17777 }
17778
17779 /* Adjust clock one minute up or down */
17780 void
17781 AdjustClock (Boolean which, int dir)
17782 {
17783     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17784     if(which) blackTimeRemaining += 60000*dir;
17785     else      whiteTimeRemaining += 60000*dir;
17786     DisplayBothClocks();
17787     adjustedClock = TRUE;
17788 }
17789
17790 /* Stop clocks and reset to a fresh time control */
17791 void
17792 ResetClocks ()
17793 {
17794     (void) StopClockTimer();
17795     if (appData.icsActive) {
17796         whiteTimeRemaining = blackTimeRemaining = 0;
17797     } else if (searchTime) {
17798         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17799         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17800     } else { /* [HGM] correct new time quote for time odds */
17801         whiteTC = blackTC = fullTimeControlString;
17802         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17803         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17804     }
17805     if (whiteFlag || blackFlag) {
17806         DisplayTitle("");
17807         whiteFlag = blackFlag = FALSE;
17808     }
17809     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17810     DisplayBothClocks();
17811     adjustedClock = FALSE;
17812 }
17813
17814 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17815
17816 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17817
17818 /* Decrement running clock by amount of time that has passed */
17819 void
17820 DecrementClocks ()
17821 {
17822     long tRemaining;
17823     long lastTickLength, fudge;
17824     TimeMark now;
17825
17826     if (!appData.clockMode) return;
17827     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17828
17829     GetTimeMark(&now);
17830
17831     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17832
17833     /* Fudge if we woke up a little too soon */
17834     fudge = intendedTickLength - lastTickLength;
17835     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17836
17837     if (WhiteOnMove(forwardMostMove)) {
17838         if(whiteNPS >= 0) lastTickLength = 0;
17839          tRemaining = whiteTimeRemaining -= lastTickLength;
17840         if( tRemaining < 0 && !appData.icsActive) {
17841             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17842             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17843                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17844                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17845             }
17846         }
17847         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17848         DisplayWhiteClock(whiteTimeRemaining - fudge,
17849                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17850         timeSuffix = 0;
17851     } else {
17852         if(blackNPS >= 0) lastTickLength = 0;
17853          tRemaining = blackTimeRemaining -= lastTickLength;
17854         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17855             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17856             if(suddenDeath) {
17857                 blackStartMove = forwardMostMove;
17858                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17859             }
17860         }
17861         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17862         DisplayBlackClock(blackTimeRemaining - fudge,
17863                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17864         timeSuffix = 0;
17865     }
17866     if (CheckFlags()) return;
17867
17868     if(twoBoards) { // count down secondary board's clocks as well
17869         activePartnerTime -= lastTickLength;
17870         partnerUp = 1;
17871         if(activePartner == 'W')
17872             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17873         else
17874             DisplayBlackClock(activePartnerTime, TRUE);
17875         partnerUp = 0;
17876     }
17877
17878     tickStartTM = now;
17879     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17880     StartClockTimer(intendedTickLength);
17881
17882     /* if the time remaining has fallen below the alarm threshold, sound the
17883      * alarm. if the alarm has sounded and (due to a takeback or time control
17884      * with increment) the time remaining has increased to a level above the
17885      * threshold, reset the alarm so it can sound again.
17886      */
17887
17888     if (appData.icsActive && appData.icsAlarm) {
17889
17890         /* make sure we are dealing with the user's clock */
17891         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17892                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17893            )) return;
17894
17895         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17896             alarmSounded = FALSE;
17897         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17898             PlayAlarmSound();
17899             alarmSounded = TRUE;
17900         }
17901     }
17902 }
17903
17904
17905 /* A player has just moved, so stop the previously running
17906    clock and (if in clock mode) start the other one.
17907    We redisplay both clocks in case we're in ICS mode, because
17908    ICS gives us an update to both clocks after every move.
17909    Note that this routine is called *after* forwardMostMove
17910    is updated, so the last fractional tick must be subtracted
17911    from the color that is *not* on move now.
17912 */
17913 void
17914 SwitchClocks (int newMoveNr)
17915 {
17916     long lastTickLength;
17917     TimeMark now;
17918     int flagged = FALSE;
17919
17920     GetTimeMark(&now);
17921
17922     if (StopClockTimer() && appData.clockMode) {
17923         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17924         if (!WhiteOnMove(forwardMostMove)) {
17925             if(blackNPS >= 0) lastTickLength = 0;
17926             blackTimeRemaining -= lastTickLength;
17927            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17928 //         if(pvInfoList[forwardMostMove].time == -1)
17929                  pvInfoList[forwardMostMove].time =               // use GUI time
17930                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17931         } else {
17932            if(whiteNPS >= 0) lastTickLength = 0;
17933            whiteTimeRemaining -= lastTickLength;
17934            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17935 //         if(pvInfoList[forwardMostMove].time == -1)
17936                  pvInfoList[forwardMostMove].time =
17937                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17938         }
17939         flagged = CheckFlags();
17940     }
17941     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17942     CheckTimeControl();
17943
17944     if (flagged || !appData.clockMode) return;
17945
17946     switch (gameMode) {
17947       case MachinePlaysBlack:
17948       case MachinePlaysWhite:
17949       case BeginningOfGame:
17950         if (pausing) return;
17951         break;
17952
17953       case EditGame:
17954       case PlayFromGameFile:
17955       case IcsExamining:
17956         return;
17957
17958       default:
17959         break;
17960     }
17961
17962     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17963         if(WhiteOnMove(forwardMostMove))
17964              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17965         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17966     }
17967
17968     tickStartTM = now;
17969     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17970       whiteTimeRemaining : blackTimeRemaining);
17971     StartClockTimer(intendedTickLength);
17972 }
17973
17974
17975 /* Stop both clocks */
17976 void
17977 StopClocks ()
17978 {
17979     long lastTickLength;
17980     TimeMark now;
17981
17982     if (!StopClockTimer()) return;
17983     if (!appData.clockMode) return;
17984
17985     GetTimeMark(&now);
17986
17987     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17988     if (WhiteOnMove(forwardMostMove)) {
17989         if(whiteNPS >= 0) lastTickLength = 0;
17990         whiteTimeRemaining -= lastTickLength;
17991         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17992     } else {
17993         if(blackNPS >= 0) lastTickLength = 0;
17994         blackTimeRemaining -= lastTickLength;
17995         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17996     }
17997     CheckFlags();
17998 }
17999
18000 /* Start clock of player on move.  Time may have been reset, so
18001    if clock is already running, stop and restart it. */
18002 void
18003 StartClocks ()
18004 {
18005     (void) StopClockTimer(); /* in case it was running already */
18006     DisplayBothClocks();
18007     if (CheckFlags()) return;
18008
18009     if (!appData.clockMode) return;
18010     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18011
18012     GetTimeMark(&tickStartTM);
18013     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18014       whiteTimeRemaining : blackTimeRemaining);
18015
18016    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18017     whiteNPS = blackNPS = -1;
18018     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18019        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18020         whiteNPS = first.nps;
18021     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18022        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18023         blackNPS = first.nps;
18024     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18025         whiteNPS = second.nps;
18026     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18027         blackNPS = second.nps;
18028     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18029
18030     StartClockTimer(intendedTickLength);
18031 }
18032
18033 char *
18034 TimeString (long ms)
18035 {
18036     long second, minute, hour, day;
18037     char *sign = "";
18038     static char buf[40], moveTime[8];
18039
18040     if (ms > 0 && ms <= 9900) {
18041       /* convert milliseconds to tenths, rounding up */
18042       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18043
18044       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18045       return buf;
18046     }
18047
18048     /* convert milliseconds to seconds, rounding up */
18049     /* use floating point to avoid strangeness of integer division
18050        with negative dividends on many machines */
18051     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18052
18053     if (second < 0) {
18054         sign = "-";
18055         second = -second;
18056     }
18057
18058     day = second / (60 * 60 * 24);
18059     second = second % (60 * 60 * 24);
18060     hour = second / (60 * 60);
18061     second = second % (60 * 60);
18062     minute = second / 60;
18063     second = second % 60;
18064
18065     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18066     else *moveTime = NULLCHAR;
18067
18068     if (day > 0)
18069       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18070               sign, day, hour, minute, second, moveTime);
18071     else if (hour > 0)
18072       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18073     else
18074       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18075
18076     return buf;
18077 }
18078
18079
18080 /*
18081  * This is necessary because some C libraries aren't ANSI C compliant yet.
18082  */
18083 char *
18084 StrStr (char *string, char *match)
18085 {
18086     int i, length;
18087
18088     length = strlen(match);
18089
18090     for (i = strlen(string) - length; i >= 0; i--, string++)
18091       if (!strncmp(match, string, length))
18092         return string;
18093
18094     return NULL;
18095 }
18096
18097 char *
18098 StrCaseStr (char *string, char *match)
18099 {
18100     int i, j, length;
18101
18102     length = strlen(match);
18103
18104     for (i = strlen(string) - length; i >= 0; i--, string++) {
18105         for (j = 0; j < length; j++) {
18106             if (ToLower(match[j]) != ToLower(string[j]))
18107               break;
18108         }
18109         if (j == length) return string;
18110     }
18111
18112     return NULL;
18113 }
18114
18115 #ifndef _amigados
18116 int
18117 StrCaseCmp (char *s1, char *s2)
18118 {
18119     char c1, c2;
18120
18121     for (;;) {
18122         c1 = ToLower(*s1++);
18123         c2 = ToLower(*s2++);
18124         if (c1 > c2) return 1;
18125         if (c1 < c2) return -1;
18126         if (c1 == NULLCHAR) return 0;
18127     }
18128 }
18129
18130
18131 int
18132 ToLower (int c)
18133 {
18134     return isupper(c) ? tolower(c) : c;
18135 }
18136
18137
18138 int
18139 ToUpper (int c)
18140 {
18141     return islower(c) ? toupper(c) : c;
18142 }
18143 #endif /* !_amigados    */
18144
18145 char *
18146 StrSave (char *s)
18147 {
18148   char *ret;
18149
18150   if ((ret = (char *) malloc(strlen(s) + 1)))
18151     {
18152       safeStrCpy(ret, s, strlen(s)+1);
18153     }
18154   return ret;
18155 }
18156
18157 char *
18158 StrSavePtr (char *s, char **savePtr)
18159 {
18160     if (*savePtr) {
18161         free(*savePtr);
18162     }
18163     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18164       safeStrCpy(*savePtr, s, strlen(s)+1);
18165     }
18166     return(*savePtr);
18167 }
18168
18169 char *
18170 PGNDate ()
18171 {
18172     time_t clock;
18173     struct tm *tm;
18174     char buf[MSG_SIZ];
18175
18176     clock = time((time_t *)NULL);
18177     tm = localtime(&clock);
18178     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18179             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18180     return StrSave(buf);
18181 }
18182
18183
18184 char *
18185 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18186 {
18187     int i, j, fromX, fromY, toX, toY;
18188     int whiteToPlay, haveRights = nrCastlingRights;
18189     char buf[MSG_SIZ];
18190     char *p, *q;
18191     int emptycount;
18192     ChessSquare piece;
18193
18194     whiteToPlay = (gameMode == EditPosition) ?
18195       !blackPlaysFirst : (move % 2 == 0);
18196     p = buf;
18197
18198     /* Piece placement data */
18199     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18200         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18201         emptycount = 0;
18202         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18203             if (boards[move][i][j] == EmptySquare) {
18204                 emptycount++;
18205             } else { ChessSquare piece = boards[move][i][j];
18206                 if (emptycount > 0) {
18207                     if(emptycount<10) /* [HGM] can be >= 10 */
18208                         *p++ = '0' + emptycount;
18209                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18210                     emptycount = 0;
18211                 }
18212                 if(PieceToChar(piece) == '+') {
18213                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18214                     *p++ = '+';
18215                     piece = (ChessSquare)(CHUDEMOTED(piece));
18216                 }
18217                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18218                 if(*p = PieceSuffix(piece)) p++;
18219                 if(p[-1] == '~') {
18220                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18221                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18222                     *p++ = '~';
18223                 }
18224             }
18225         }
18226         if (emptycount > 0) {
18227             if(emptycount<10) /* [HGM] can be >= 10 */
18228                 *p++ = '0' + emptycount;
18229             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18230             emptycount = 0;
18231         }
18232         *p++ = '/';
18233     }
18234     *(p - 1) = ' ';
18235
18236     /* [HGM] print Crazyhouse or Shogi holdings */
18237     if( gameInfo.holdingsWidth ) {
18238         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18239         q = p;
18240         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18241             piece = boards[move][i][BOARD_WIDTH-1];
18242             if( piece != EmptySquare )
18243               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18244                   *p++ = PieceToChar(piece);
18245         }
18246         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18247             piece = boards[move][BOARD_HEIGHT-i-1][0];
18248             if( piece != EmptySquare )
18249               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18250                   *p++ = PieceToChar(piece);
18251         }
18252
18253         if( q == p ) *p++ = '-';
18254         *p++ = ']';
18255         *p++ = ' ';
18256     }
18257
18258     /* Active color */
18259     *p++ = whiteToPlay ? 'w' : 'b';
18260     *p++ = ' ';
18261
18262   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18263     haveRights = 0; q = p;
18264     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18265       piece = boards[move][0][i];
18266       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18267         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18268       }
18269     }
18270     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18271       piece = boards[move][BOARD_HEIGHT-1][i];
18272       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18273         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18274       }
18275     }
18276     if(p == q) *p++ = '-';
18277     *p++ = ' ';
18278   }
18279
18280   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18281     while(*p++ = *q++)
18282                       ;
18283     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18284   } else {
18285   if(haveRights) {
18286      int handW=0, handB=0;
18287      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18288         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18289         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18290      }
18291      q = p;
18292      if(appData.fischerCastling) {
18293         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18294            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18295                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18296         } else {
18297        /* [HGM] write directly from rights */
18298            if(boards[move][CASTLING][2] != NoRights &&
18299               boards[move][CASTLING][0] != NoRights   )
18300                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18301            if(boards[move][CASTLING][2] != NoRights &&
18302               boards[move][CASTLING][1] != NoRights   )
18303                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18304         }
18305         if(handB) {
18306            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18307                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18308         } else {
18309            if(boards[move][CASTLING][5] != NoRights &&
18310               boards[move][CASTLING][3] != NoRights   )
18311                 *p++ = boards[move][CASTLING][3] + AAA;
18312            if(boards[move][CASTLING][5] != NoRights &&
18313               boards[move][CASTLING][4] != NoRights   )
18314                 *p++ = boards[move][CASTLING][4] + AAA;
18315         }
18316      } else {
18317
18318         /* [HGM] write true castling rights */
18319         if( nrCastlingRights == 6 ) {
18320             int q, k=0;
18321             if(boards[move][CASTLING][0] != NoRights &&
18322                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18323             q = (boards[move][CASTLING][1] != NoRights &&
18324                  boards[move][CASTLING][2] != NoRights  );
18325             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18326                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18327                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18328                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18329             }
18330             if(q) *p++ = 'Q';
18331             k = 0;
18332             if(boards[move][CASTLING][3] != NoRights &&
18333                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18334             q = (boards[move][CASTLING][4] != NoRights &&
18335                  boards[move][CASTLING][5] != NoRights  );
18336             if(handB) {
18337                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18338                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18339                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18340             }
18341             if(q) *p++ = 'q';
18342         }
18343      }
18344      if (q == p) *p++ = '-'; /* No castling rights */
18345      *p++ = ' ';
18346   }
18347
18348   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18349      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18350      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18351     /* En passant target square */
18352     if (move > backwardMostMove) {
18353         fromX = moveList[move - 1][0] - AAA;
18354         fromY = moveList[move - 1][1] - ONE;
18355         toX = moveList[move - 1][2] - AAA;
18356         toY = moveList[move - 1][3] - ONE;
18357         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18358             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18359             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18360             fromX == toX) {
18361             /* 2-square pawn move just happened */
18362             *p++ = toX + AAA;
18363             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18364         } else {
18365             *p++ = '-';
18366         }
18367     } else if(move == backwardMostMove) {
18368         // [HGM] perhaps we should always do it like this, and forget the above?
18369         if((signed char)boards[move][EP_STATUS] >= 0) {
18370             *p++ = boards[move][EP_STATUS] + AAA;
18371             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18372         } else {
18373             *p++ = '-';
18374         }
18375     } else {
18376         *p++ = '-';
18377     }
18378     *p++ = ' ';
18379   }
18380   }
18381
18382     if(moveCounts)
18383     {   int i = 0, j=move;
18384
18385         /* [HGM] find reversible plies */
18386         if (appData.debugMode) { int k;
18387             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18388             for(k=backwardMostMove; k<=forwardMostMove; k++)
18389                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18390
18391         }
18392
18393         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18394         if( j == backwardMostMove ) i += initialRulePlies;
18395         sprintf(p, "%d ", i);
18396         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18397
18398         /* Fullmove number */
18399         sprintf(p, "%d", (move / 2) + 1);
18400     } else *--p = NULLCHAR;
18401
18402     return StrSave(buf);
18403 }
18404
18405 Boolean
18406 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18407 {
18408     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18409     char *p, c;
18410     int emptycount, virgin[BOARD_FILES];
18411     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18412
18413     p = fen;
18414
18415     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18416
18417     /* Piece placement data */
18418     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18419         j = 0;
18420         for (;;) {
18421             if (*p == '/' || *p == ' ' || *p == '[' ) {
18422                 if(j > w) w = j;
18423                 emptycount = gameInfo.boardWidth - j;
18424                 while (emptycount--)
18425                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18426                 if (*p == '/') p++;
18427                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18428                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18429                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18430                     }
18431                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18432                 }
18433                 break;
18434 #if(BOARD_FILES >= 10)*0
18435             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18436                 p++; emptycount=10;
18437                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18438                 while (emptycount--)
18439                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18440 #endif
18441             } else if (*p == '*') {
18442                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18443             } else if (isdigit(*p)) {
18444                 emptycount = *p++ - '0';
18445                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18446                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18447                 while (emptycount--)
18448                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18449             } else if (*p == '<') {
18450                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18451                 else if (i != 0 || !shuffle) return FALSE;
18452                 p++;
18453             } else if (shuffle && *p == '>') {
18454                 p++; // for now ignore closing shuffle range, and assume rank-end
18455             } else if (*p == '?') {
18456                 if (j >= gameInfo.boardWidth) return FALSE;
18457                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18458                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18459             } else if (*p == '+' || isalpha(*p)) {
18460                 char *q, *s = SUFFIXES;
18461                 if (j >= gameInfo.boardWidth) return FALSE;
18462                 if(*p=='+') {
18463                     char c = *++p;
18464                     if(q = strchr(s, p[1])) p++;
18465                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18466                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18467                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18468                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18469                 } else {
18470                     char c = *p++;
18471                     if(q = strchr(s, *p)) p++;
18472                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18473                 }
18474
18475                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18476                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18477                     piece = (ChessSquare) (PROMOTED(piece));
18478                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18479                     p++;
18480                 }
18481                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18482                 if(piece == king) wKingRank = i;
18483                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18484             } else {
18485                 return FALSE;
18486             }
18487         }
18488     }
18489     while (*p == '/' || *p == ' ') p++;
18490
18491     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18492
18493     /* [HGM] by default clear Crazyhouse holdings, if present */
18494     if(gameInfo.holdingsWidth) {
18495        for(i=0; i<BOARD_HEIGHT; i++) {
18496            board[i][0]             = EmptySquare; /* black holdings */
18497            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18498            board[i][1]             = (ChessSquare) 0; /* black counts */
18499            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18500        }
18501     }
18502
18503     /* [HGM] look for Crazyhouse holdings here */
18504     while(*p==' ') p++;
18505     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18506         int swap=0, wcnt=0, bcnt=0;
18507         if(*p == '[') p++;
18508         if(*p == '<') swap++, p++;
18509         if(*p == '-' ) p++; /* empty holdings */ else {
18510             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18511             /* if we would allow FEN reading to set board size, we would   */
18512             /* have to add holdings and shift the board read so far here   */
18513             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18514                 p++;
18515                 if((int) piece >= (int) BlackPawn ) {
18516                     i = (int)piece - (int)BlackPawn;
18517                     i = PieceToNumber((ChessSquare)i);
18518                     if( i >= gameInfo.holdingsSize ) return FALSE;
18519                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18520                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18521                     bcnt++;
18522                 } else {
18523                     i = (int)piece - (int)WhitePawn;
18524                     i = PieceToNumber((ChessSquare)i);
18525                     if( i >= gameInfo.holdingsSize ) return FALSE;
18526                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18527                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18528                     wcnt++;
18529                 }
18530             }
18531             if(subst) { // substitute back-rank question marks by holdings pieces
18532                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18533                     int k, m, n = bcnt + 1;
18534                     if(board[0][j] == ClearBoard) {
18535                         if(!wcnt) return FALSE;
18536                         n = rand() % wcnt;
18537                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18538                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18539                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18540                             break;
18541                         }
18542                     }
18543                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18544                         if(!bcnt) return FALSE;
18545                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18546                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18547                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18548                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18549                             break;
18550                         }
18551                     }
18552                 }
18553                 subst = 0;
18554             }
18555         }
18556         if(*p == ']') p++;
18557     }
18558
18559     if(subst) return FALSE; // substitution requested, but no holdings
18560
18561     while(*p == ' ') p++;
18562
18563     /* Active color */
18564     c = *p++;
18565     if(appData.colorNickNames) {
18566       if( c == appData.colorNickNames[0] ) c = 'w'; else
18567       if( c == appData.colorNickNames[1] ) c = 'b';
18568     }
18569     switch (c) {
18570       case 'w':
18571         *blackPlaysFirst = FALSE;
18572         break;
18573       case 'b':
18574         *blackPlaysFirst = TRUE;
18575         break;
18576       default:
18577         return FALSE;
18578     }
18579
18580     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18581     /* return the extra info in global variiables             */
18582
18583     while(*p==' ') p++;
18584
18585     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18586         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18587         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18588     }
18589
18590     /* set defaults in case FEN is incomplete */
18591     board[EP_STATUS] = EP_UNKNOWN;
18592     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18593     for(i=0; i<nrCastlingRights; i++ ) {
18594         board[CASTLING][i] =
18595             appData.fischerCastling ? NoRights : initialRights[i];
18596     }   /* assume possible unless obviously impossible */
18597     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18598     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18599     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18600                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18601     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18602     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18603     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18604                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18605     FENrulePlies = 0;
18606
18607     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18608       char *q = p;
18609       int w=0, b=0;
18610       while(isalpha(*p)) {
18611         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18612         if(islower(*p)) b |= 1 << (*p++ - 'a');
18613       }
18614       if(*p == '-') p++;
18615       if(p != q) {
18616         board[TOUCHED_W] = ~w;
18617         board[TOUCHED_B] = ~b;
18618         while(*p == ' ') p++;
18619       }
18620     } else
18621
18622     if(nrCastlingRights) {
18623       int fischer = 0;
18624       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18625       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18626           /* castling indicator present, so default becomes no castlings */
18627           for(i=0; i<nrCastlingRights; i++ ) {
18628                  board[CASTLING][i] = NoRights;
18629           }
18630       }
18631       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18632              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18633              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18634              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18635         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18636
18637         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18638             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18639             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18640         }
18641         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18642             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18643         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18644                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18645         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18646                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18647         switch(c) {
18648           case'K':
18649               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18650               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18651               board[CASTLING][2] = whiteKingFile;
18652               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18653               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18654               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18655               break;
18656           case'Q':
18657               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18658               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18659               board[CASTLING][2] = whiteKingFile;
18660               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18661               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18662               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18663               break;
18664           case'k':
18665               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18666               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18667               board[CASTLING][5] = blackKingFile;
18668               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18669               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18670               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18671               break;
18672           case'q':
18673               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18674               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18675               board[CASTLING][5] = blackKingFile;
18676               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18677               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18678               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18679           case '-':
18680               break;
18681           default: /* FRC castlings */
18682               if(c >= 'a') { /* black rights */
18683                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18684                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18685                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18686                   if(i == BOARD_RGHT) break;
18687                   board[CASTLING][5] = i;
18688                   c -= AAA;
18689                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18690                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18691                   if(c > i)
18692                       board[CASTLING][3] = c;
18693                   else
18694                       board[CASTLING][4] = c;
18695               } else { /* white rights */
18696                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18697                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18698                     if(board[0][i] == WhiteKing) break;
18699                   if(i == BOARD_RGHT) break;
18700                   board[CASTLING][2] = i;
18701                   c -= AAA - 'a' + 'A';
18702                   if(board[0][c] >= WhiteKing) break;
18703                   if(c > i)
18704                       board[CASTLING][0] = c;
18705                   else
18706                       board[CASTLING][1] = c;
18707               }
18708         }
18709       }
18710       for(i=0; i<nrCastlingRights; i++)
18711         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18712       if(gameInfo.variant == VariantSChess)
18713         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18714       if(fischer && shuffle) appData.fischerCastling = TRUE;
18715     if (appData.debugMode) {
18716         fprintf(debugFP, "FEN castling rights:");
18717         for(i=0; i<nrCastlingRights; i++)
18718         fprintf(debugFP, " %d", board[CASTLING][i]);
18719         fprintf(debugFP, "\n");
18720     }
18721
18722       while(*p==' ') p++;
18723     }
18724
18725     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18726
18727     /* read e.p. field in games that know e.p. capture */
18728     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18729        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18730        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18731       if(*p=='-') {
18732         p++; board[EP_STATUS] = EP_NONE;
18733       } else {
18734          char c = *p++ - AAA;
18735
18736          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18737          if(*p >= '0' && *p <='9') p++;
18738          board[EP_STATUS] = c;
18739       }
18740     }
18741
18742
18743     if(sscanf(p, "%d", &i) == 1) {
18744         FENrulePlies = i; /* 50-move ply counter */
18745         /* (The move number is still ignored)    */
18746     }
18747
18748     return TRUE;
18749 }
18750
18751 void
18752 EditPositionPasteFEN (char *fen)
18753 {
18754   if (fen != NULL) {
18755     Board initial_position;
18756
18757     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18758       DisplayError(_("Bad FEN position in clipboard"), 0);
18759       return ;
18760     } else {
18761       int savedBlackPlaysFirst = blackPlaysFirst;
18762       EditPositionEvent();
18763       blackPlaysFirst = savedBlackPlaysFirst;
18764       CopyBoard(boards[0], initial_position);
18765       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18766       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18767       DisplayBothClocks();
18768       DrawPosition(FALSE, boards[currentMove]);
18769     }
18770   }
18771 }
18772
18773 static char cseq[12] = "\\   ";
18774
18775 Boolean
18776 set_cont_sequence (char *new_seq)
18777 {
18778     int len;
18779     Boolean ret;
18780
18781     // handle bad attempts to set the sequence
18782         if (!new_seq)
18783                 return 0; // acceptable error - no debug
18784
18785     len = strlen(new_seq);
18786     ret = (len > 0) && (len < sizeof(cseq));
18787     if (ret)
18788       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18789     else if (appData.debugMode)
18790       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18791     return ret;
18792 }
18793
18794 /*
18795     reformat a source message so words don't cross the width boundary.  internal
18796     newlines are not removed.  returns the wrapped size (no null character unless
18797     included in source message).  If dest is NULL, only calculate the size required
18798     for the dest buffer.  lp argument indicats line position upon entry, and it's
18799     passed back upon exit.
18800 */
18801 int
18802 wrap (char *dest, char *src, int count, int width, int *lp)
18803 {
18804     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18805
18806     cseq_len = strlen(cseq);
18807     old_line = line = *lp;
18808     ansi = len = clen = 0;
18809
18810     for (i=0; i < count; i++)
18811     {
18812         if (src[i] == '\033')
18813             ansi = 1;
18814
18815         // if we hit the width, back up
18816         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18817         {
18818             // store i & len in case the word is too long
18819             old_i = i, old_len = len;
18820
18821             // find the end of the last word
18822             while (i && src[i] != ' ' && src[i] != '\n')
18823             {
18824                 i--;
18825                 len--;
18826             }
18827
18828             // word too long?  restore i & len before splitting it
18829             if ((old_i-i+clen) >= width)
18830             {
18831                 i = old_i;
18832                 len = old_len;
18833             }
18834
18835             // extra space?
18836             if (i && src[i-1] == ' ')
18837                 len--;
18838
18839             if (src[i] != ' ' && src[i] != '\n')
18840             {
18841                 i--;
18842                 if (len)
18843                     len--;
18844             }
18845
18846             // now append the newline and continuation sequence
18847             if (dest)
18848                 dest[len] = '\n';
18849             len++;
18850             if (dest)
18851                 strncpy(dest+len, cseq, cseq_len);
18852             len += cseq_len;
18853             line = cseq_len;
18854             clen = cseq_len;
18855             continue;
18856         }
18857
18858         if (dest)
18859             dest[len] = src[i];
18860         len++;
18861         if (!ansi)
18862             line++;
18863         if (src[i] == '\n')
18864             line = 0;
18865         if (src[i] == 'm')
18866             ansi = 0;
18867     }
18868     if (dest && appData.debugMode)
18869     {
18870         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18871             count, width, line, len, *lp);
18872         show_bytes(debugFP, src, count);
18873         fprintf(debugFP, "\ndest: ");
18874         show_bytes(debugFP, dest, len);
18875         fprintf(debugFP, "\n");
18876     }
18877     *lp = dest ? line : old_line;
18878
18879     return len;
18880 }
18881
18882 // [HGM] vari: routines for shelving variations
18883 Boolean modeRestore = FALSE;
18884
18885 void
18886 PushInner (int firstMove, int lastMove)
18887 {
18888         int i, j, nrMoves = lastMove - firstMove;
18889
18890         // push current tail of game on stack
18891         savedResult[storedGames] = gameInfo.result;
18892         savedDetails[storedGames] = gameInfo.resultDetails;
18893         gameInfo.resultDetails = NULL;
18894         savedFirst[storedGames] = firstMove;
18895         savedLast [storedGames] = lastMove;
18896         savedFramePtr[storedGames] = framePtr;
18897         framePtr -= nrMoves; // reserve space for the boards
18898         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18899             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18900             for(j=0; j<MOVE_LEN; j++)
18901                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18902             for(j=0; j<2*MOVE_LEN; j++)
18903                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18904             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18905             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18906             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18907             pvInfoList[firstMove+i-1].depth = 0;
18908             commentList[framePtr+i] = commentList[firstMove+i];
18909             commentList[firstMove+i] = NULL;
18910         }
18911
18912         storedGames++;
18913         forwardMostMove = firstMove; // truncate game so we can start variation
18914 }
18915
18916 void
18917 PushTail (int firstMove, int lastMove)
18918 {
18919         if(appData.icsActive) { // only in local mode
18920                 forwardMostMove = currentMove; // mimic old ICS behavior
18921                 return;
18922         }
18923         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18924
18925         PushInner(firstMove, lastMove);
18926         if(storedGames == 1) GreyRevert(FALSE);
18927         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18928 }
18929
18930 void
18931 PopInner (Boolean annotate)
18932 {
18933         int i, j, nrMoves;
18934         char buf[8000], moveBuf[20];
18935
18936         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18937         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18938         nrMoves = savedLast[storedGames] - currentMove;
18939         if(annotate) {
18940                 int cnt = 10;
18941                 if(!WhiteOnMove(currentMove))
18942                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18943                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18944                 for(i=currentMove; i<forwardMostMove; i++) {
18945                         if(WhiteOnMove(i))
18946                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18947                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18948                         strcat(buf, moveBuf);
18949                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18950                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18951                 }
18952                 strcat(buf, ")");
18953         }
18954         for(i=1; i<=nrMoves; i++) { // copy last variation back
18955             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18956             for(j=0; j<MOVE_LEN; j++)
18957                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18958             for(j=0; j<2*MOVE_LEN; j++)
18959                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18960             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18961             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18962             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18963             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18964             commentList[currentMove+i] = commentList[framePtr+i];
18965             commentList[framePtr+i] = NULL;
18966         }
18967         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18968         framePtr = savedFramePtr[storedGames];
18969         gameInfo.result = savedResult[storedGames];
18970         if(gameInfo.resultDetails != NULL) {
18971             free(gameInfo.resultDetails);
18972       }
18973         gameInfo.resultDetails = savedDetails[storedGames];
18974         forwardMostMove = currentMove + nrMoves;
18975 }
18976
18977 Boolean
18978 PopTail (Boolean annotate)
18979 {
18980         if(appData.icsActive) return FALSE; // only in local mode
18981         if(!storedGames) return FALSE; // sanity
18982         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18983
18984         PopInner(annotate);
18985         if(currentMove < forwardMostMove) ForwardEvent(); else
18986         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18987
18988         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18989         return TRUE;
18990 }
18991
18992 void
18993 CleanupTail ()
18994 {       // remove all shelved variations
18995         int i;
18996         for(i=0; i<storedGames; i++) {
18997             if(savedDetails[i])
18998                 free(savedDetails[i]);
18999             savedDetails[i] = NULL;
19000         }
19001         for(i=framePtr; i<MAX_MOVES; i++) {
19002                 if(commentList[i]) free(commentList[i]);
19003                 commentList[i] = NULL;
19004         }
19005         framePtr = MAX_MOVES-1;
19006         storedGames = 0;
19007 }
19008
19009 void
19010 LoadVariation (int index, char *text)
19011 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19012         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19013         int level = 0, move;
19014
19015         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19016         // first find outermost bracketing variation
19017         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19018             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19019                 if(*p == '{') wait = '}'; else
19020                 if(*p == '[') wait = ']'; else
19021                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19022                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19023             }
19024             if(*p == wait) wait = NULLCHAR; // closing ]} found
19025             p++;
19026         }
19027         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19028         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19029         end[1] = NULLCHAR; // clip off comment beyond variation
19030         ToNrEvent(currentMove-1);
19031         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19032         // kludge: use ParsePV() to append variation to game
19033         move = currentMove;
19034         ParsePV(start, TRUE, TRUE);
19035         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19036         ClearPremoveHighlights();
19037         CommentPopDown();
19038         ToNrEvent(currentMove+1);
19039 }
19040
19041 void
19042 LoadTheme ()
19043 {
19044     char *p, *q, buf[MSG_SIZ];
19045     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19046         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19047         ParseArgsFromString(buf);
19048         ActivateTheme(TRUE); // also redo colors
19049         return;
19050     }
19051     p = nickName;
19052     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19053     {
19054         int len;
19055         q = appData.themeNames;
19056         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19057       if(appData.useBitmaps) {
19058         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19059                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19060                 appData.liteBackTextureMode,
19061                 appData.darkBackTextureMode );
19062       } else {
19063         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19064                 Col2Text(2),   // lightSquareColor
19065                 Col2Text(3) ); // darkSquareColor
19066       }
19067       if(appData.useBorder) {
19068         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19069                 appData.border);
19070       } else {
19071         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19072       }
19073       if(appData.useFont) {
19074         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19075                 appData.renderPiecesWithFont,
19076                 appData.fontToPieceTable,
19077                 Col2Text(9),    // appData.fontBackColorWhite
19078                 Col2Text(10) ); // appData.fontForeColorBlack
19079       } else {
19080         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19081                 appData.pieceDirectory);
19082         if(!appData.pieceDirectory[0])
19083           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19084                 Col2Text(0),   // whitePieceColor
19085                 Col2Text(1) ); // blackPieceColor
19086       }
19087       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19088                 Col2Text(4),   // highlightSquareColor
19089                 Col2Text(5) ); // premoveHighlightColor
19090         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19091         if(insert != q) insert[-1] = NULLCHAR;
19092         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19093         if(q)   free(q);
19094     }
19095     ActivateTheme(FALSE);
19096 }