Implement saving of (modified) engine settings
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         ASSIGN(currentEngine[i], engineLine);
992         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996         appData.firstProtocolVersion = PROTOVER;
997         ParseArgsFromString(buf);
998         SwapEngines(i);
999         ReplaceEngine(cps, i);
1000         FloatToFront(&appData.recentEngineList, engineLine);
1001         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1002         return;
1003     }
1004     p = engineName;
1005     while(q = strchr(p, SLASH)) p = q+1;
1006     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007     if(engineDir[0] != NULLCHAR) {
1008         ASSIGN(appData.directory[i], engineDir); p = engineName;
1009     } else if(p != engineName) { // derive directory from engine path, when not given
1010         p[-1] = 0;
1011         ASSIGN(appData.directory[i], engineName);
1012         p[-1] = SLASH;
1013         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014     } else { ASSIGN(appData.directory[i], "."); }
1015     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016     if(params[0]) {
1017         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018         snprintf(command, MSG_SIZ, "%s %s", p, params);
1019         p = command;
1020     }
1021     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022     ASSIGN(appData.chessProgram[i], p);
1023     appData.isUCI[i] = isUCI;
1024     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025     appData.hasOwnBookUCI[i] = hasBook;
1026     if(!nickName[0]) useNick = FALSE;
1027     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028     if(addToList) {
1029         int len;
1030         char quote;
1031         q = firstChessProgramNames;
1032         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1035                         quote, p, quote, appData.directory[i],
1036                         useNick ? " -fn \"" : "",
1037                         useNick ? nickName : "",
1038                         useNick ? "\"" : "",
1039                         v1 ? " -firstProtocolVersion 1" : "",
1040                         hasBook ? "" : " -fNoOwnBookUCI",
1041                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042                         storeVariant ? " -variant " : "",
1043                         storeVariant ? VariantName(gameInfo.variant) : "");
1044         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1045         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1046         if(insert != q) insert[-1] = NULLCHAR;
1047         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1048         if(q)   free(q);
1049         FloatToFront(&appData.recentEngineList, buf);
1050         ASSIGN(currentEngine[i], buf);
1051     }
1052     ReplaceEngine(cps, i);
1053 }
1054
1055 void
1056 InitTimeControls ()
1057 {
1058     int matched, min, sec;
1059     /*
1060      * Parse timeControl resource
1061      */
1062     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063                           appData.movesPerSession)) {
1064         char buf[MSG_SIZ];
1065         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066         DisplayFatalError(buf, 0, 2);
1067     }
1068
1069     /*
1070      * Parse searchTime resource
1071      */
1072     if (*appData.searchTime != NULLCHAR) {
1073         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1074         if (matched == 1) {
1075             searchTime = min * 60;
1076         } else if (matched == 2) {
1077             searchTime = min * 60 + sec;
1078         } else {
1079             char buf[MSG_SIZ];
1080             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081             DisplayFatalError(buf, 0, 2);
1082         }
1083     }
1084 }
1085
1086 void
1087 InitBackEnd1 ()
1088 {
1089
1090     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1092
1093     GetTimeMark(&programStartTime);
1094     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095     appData.seedBase = random() + (random()<<15);
1096     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1097
1098     ClearProgramStats();
1099     programStats.ok_to_send = 1;
1100     programStats.seen_stat = 0;
1101
1102     /*
1103      * Initialize game list
1104      */
1105     ListNew(&gameList);
1106
1107
1108     /*
1109      * Internet chess server status
1110      */
1111     if (appData.icsActive) {
1112         appData.matchMode = FALSE;
1113         appData.matchGames = 0;
1114 #if ZIPPY
1115         appData.noChessProgram = !appData.zippyPlay;
1116 #else
1117         appData.zippyPlay = FALSE;
1118         appData.zippyTalk = FALSE;
1119         appData.noChessProgram = TRUE;
1120 #endif
1121         if (*appData.icsHelper != NULLCHAR) {
1122             appData.useTelnet = TRUE;
1123             appData.telnetProgram = appData.icsHelper;
1124         }
1125     } else {
1126         appData.zippyTalk = appData.zippyPlay = FALSE;
1127     }
1128
1129     /* [AS] Initialize pv info list [HGM] and game state */
1130     {
1131         int i, j;
1132
1133         for( i=0; i<=framePtr; i++ ) {
1134             pvInfoList[i].depth = -1;
1135             boards[i][EP_STATUS] = EP_NONE;
1136             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137         }
1138     }
1139
1140     InitTimeControls();
1141
1142     /* [AS] Adjudication threshold */
1143     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1144
1145     InitEngine(&first, 0);
1146     InitEngine(&second, 1);
1147     CommonEngineInit();
1148
1149     pairing.which = "pairing"; // pairing engine
1150     pairing.pr = NoProc;
1151     pairing.isr = NULL;
1152     pairing.program = appData.pairingEngine;
1153     pairing.host = "localhost";
1154     pairing.dir = ".";
1155
1156     if (appData.icsActive) {
1157         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1158     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159         appData.clockMode = FALSE;
1160         first.sendTime = second.sendTime = 0;
1161     }
1162
1163 #if ZIPPY
1164     /* Override some settings from environment variables, for backward
1165        compatibility.  Unfortunately it's not feasible to have the env
1166        vars just set defaults, at least in xboard.  Ugh.
1167     */
1168     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169       ZippyInit();
1170     }
1171 #endif
1172
1173     if (!appData.icsActive) {
1174       char buf[MSG_SIZ];
1175       int len;
1176
1177       /* Check for variants that are supported only in ICS mode,
1178          or not at all.  Some that are accepted here nevertheless
1179          have bugs; see comments below.
1180       */
1181       VariantClass variant = StringToVariant(appData.variant);
1182       switch (variant) {
1183       case VariantBughouse:     /* need four players and two boards */
1184       case VariantKriegspiel:   /* need to hide pieces and move details */
1185         /* case VariantFischeRandom: (Fabien: moved below) */
1186         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187         if( (len >= MSG_SIZ) && appData.debugMode )
1188           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1189
1190         DisplayFatalError(buf, 0, 2);
1191         return;
1192
1193       case VariantUnknown:
1194       case VariantLoadable:
1195       case Variant29:
1196       case Variant30:
1197       case Variant31:
1198       case Variant32:
1199       case Variant33:
1200       case Variant34:
1201       case Variant35:
1202       case Variant36:
1203       default:
1204         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantNormal:     /* definitely works! */
1212         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214           return;
1215         }
1216       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1217       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1218       case VariantGothic:     /* [HGM] should work */
1219       case VariantCapablanca: /* [HGM] should work */
1220       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1221       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1222       case VariantChu:        /* [HGM] experimental */
1223       case VariantKnightmate: /* [HGM] should work */
1224       case VariantCylinder:   /* [HGM] untested */
1225       case VariantFalcon:     /* [HGM] untested */
1226       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227                                  offboard interposition not understood */
1228       case VariantWildCastle: /* pieces not automatically shuffled */
1229       case VariantNoCastle:   /* pieces not automatically shuffled */
1230       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231       case VariantLosers:     /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantSuicide:    /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantGiveaway:   /* should work except for win condition,
1236                                  and doesn't know captures are mandatory */
1237       case VariantTwoKings:   /* should work */
1238       case VariantAtomic:     /* should work except for win condition */
1239       case Variant3Check:     /* should work except for win condition */
1240       case VariantShatranj:   /* should work except for all win conditions */
1241       case VariantMakruk:     /* should work except for draw countdown */
1242       case VariantASEAN :     /* should work except for draw countdown */
1243       case VariantBerolina:   /* might work if TestLegality is off */
1244       case VariantCapaRandom: /* should work */
1245       case VariantJanus:      /* should work */
1246       case VariantSuper:      /* experimental */
1247       case VariantGreat:      /* experimental, requires legality testing to be off */
1248       case VariantSChess:     /* S-Chess, should work */
1249       case VariantGrand:      /* should work */
1250       case VariantSpartan:    /* should work */
1251       case VariantLion:       /* should work */
1252       case VariantChuChess:   /* should work */
1253         break;
1254       }
1255     }
1256
1257 }
1258
1259 int
1260 NextIntegerFromString (char ** str, long * value)
1261 {
1262     int result = -1;
1263     char * s = *str;
1264
1265     while( *s == ' ' || *s == '\t' ) {
1266         s++;
1267     }
1268
1269     *value = 0;
1270
1271     if( *s >= '0' && *s <= '9' ) {
1272         while( *s >= '0' && *s <= '9' ) {
1273             *value = *value * 10 + (*s - '0');
1274             s++;
1275         }
1276
1277         result = 0;
1278     }
1279
1280     *str = s;
1281
1282     return result;
1283 }
1284
1285 int
1286 NextTimeControlFromString (char ** str, long * value)
1287 {
1288     long temp;
1289     int result = NextIntegerFromString( str, &temp );
1290
1291     if( result == 0 ) {
1292         *value = temp * 60; /* Minutes */
1293         if( **str == ':' ) {
1294             (*str)++;
1295             result = NextIntegerFromString( str, &temp );
1296             *value += temp; /* Seconds */
1297         }
1298     }
1299
1300     return result;
1301 }
1302
1303 int
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306     int result = -1, type = 0; long temp, temp2;
1307
1308     if(**str != ':') return -1; // old params remain in force!
1309     (*str)++;
1310     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311     if( NextIntegerFromString( str, &temp ) ) return -1;
1312     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313
1314     if(**str != '/') {
1315         /* time only: incremental or sudden-death time control */
1316         if(**str == '+') { /* increment follows; read it */
1317             (*str)++;
1318             if(**str == '!') type = *(*str)++; // Bronstein TC
1319             if(result = NextIntegerFromString( str, &temp2)) return -1;
1320             *inc = temp2 * 1000;
1321             if(**str == '.') { // read fraction of increment
1322                 char *start = ++(*str);
1323                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1324                 temp2 *= 1000;
1325                 while(start++ < *str) temp2 /= 10;
1326                 *inc += temp2;
1327             }
1328         } else *inc = 0;
1329         *moves = 0; *tc = temp * 1000; *incType = type;
1330         return 0;
1331     }
1332
1333     (*str)++; /* classical time control */
1334     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335
1336     if(result == 0) {
1337         *moves = temp;
1338         *tc    = temp2 * 1000;
1339         *inc   = 0;
1340         *incType = type;
1341     }
1342     return result;
1343 }
1344
1345 int
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 {   /* [HGM] get time to add from the multi-session time-control string */
1348     int incType, moves=1; /* kludge to force reading of first session */
1349     long time, increment;
1350     char *s = tcString;
1351
1352     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1353     do {
1354         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356         if(movenr == -1) return time;    /* last move before new session     */
1357         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359         if(!moves) return increment;     /* current session is incremental   */
1360         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361     } while(movenr >= -1);               /* try again for next session       */
1362
1363     return 0; // no new time quota on this move
1364 }
1365
1366 int
1367 ParseTimeControl (char *tc, float ti, int mps)
1368 {
1369   long tc1;
1370   long tc2;
1371   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372   int min, sec=0;
1373
1374   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377   if(ti > 0) {
1378
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383   } else {
1384     if(mps)
1385       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1386     else
1387       snprintf(buf, MSG_SIZ, ":%s", mytc);
1388   }
1389   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1390
1391   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392     return FALSE;
1393   }
1394
1395   if( *tc == '/' ) {
1396     /* Parse second time control */
1397     tc++;
1398
1399     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1400       return FALSE;
1401     }
1402
1403     if( tc2 == 0 ) {
1404       return FALSE;
1405     }
1406
1407     timeControl_2 = tc2 * 1000;
1408   }
1409   else {
1410     timeControl_2 = 0;
1411   }
1412
1413   if( tc1 == 0 ) {
1414     return FALSE;
1415   }
1416
1417   timeControl = tc1 * 1000;
1418
1419   if (ti >= 0) {
1420     timeIncrement = ti * 1000;  /* convert to ms */
1421     movesPerSession = 0;
1422   } else {
1423     timeIncrement = 0;
1424     movesPerSession = mps;
1425   }
1426   return TRUE;
1427 }
1428
1429 void
1430 InitBackEnd2 ()
1431 {
1432     if (appData.debugMode) {
1433 #    ifdef __GIT_VERSION
1434       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1435 #    else
1436       fprintf(debugFP, "Version: %s\n", programVersion);
1437 #    endif
1438     }
1439     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1440
1441     set_cont_sequence(appData.wrapContSeq);
1442     if (appData.matchGames > 0) {
1443         appData.matchMode = TRUE;
1444     } else if (appData.matchMode) {
1445         appData.matchGames = 1;
1446     }
1447     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448         appData.matchGames = appData.sameColorGames;
1449     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452     }
1453     Reset(TRUE, FALSE);
1454     if (appData.noChessProgram || first.protocolVersion == 1) {
1455       InitBackEnd3();
1456     } else {
1457       /* kludge: allow timeout for initial "feature" commands */
1458       FreezeUI();
1459       DisplayMessage("", _("Starting chess program"));
1460       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461     }
1462 }
1463
1464 int
1465 CalculateIndex (int index, int gameNr)
1466 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1467     int res;
1468     if(index > 0) return index; // fixed nmber
1469     if(index == 0) return 1;
1470     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472     return res;
1473 }
1474
1475 int
1476 LoadGameOrPosition (int gameNr)
1477 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478     if (*appData.loadGameFile != NULLCHAR) {
1479         if (!LoadGameFromFile(appData.loadGameFile,
1480                 CalculateIndex(appData.loadGameIndex, gameNr),
1481                               appData.loadGameFile, FALSE)) {
1482             DisplayFatalError(_("Bad game file"), 0, 1);
1483             return 0;
1484         }
1485     } else if (*appData.loadPositionFile != NULLCHAR) {
1486         if (!LoadPositionFromFile(appData.loadPositionFile,
1487                 CalculateIndex(appData.loadPositionIndex, gameNr),
1488                                   appData.loadPositionFile)) {
1489             DisplayFatalError(_("Bad position file"), 0, 1);
1490             return 0;
1491         }
1492     }
1493     return 1;
1494 }
1495
1496 void
1497 ReserveGame (int gameNr, char resChar)
1498 {
1499     FILE *tf = fopen(appData.tourneyFile, "r+");
1500     char *p, *q, c, buf[MSG_SIZ];
1501     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502     safeStrCpy(buf, lastMsg, MSG_SIZ);
1503     DisplayMessage(_("Pick new game"), "");
1504     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505     ParseArgsFromFile(tf);
1506     p = q = appData.results;
1507     if(appData.debugMode) {
1508       char *r = appData.participants;
1509       fprintf(debugFP, "results = '%s'\n", p);
1510       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511       fprintf(debugFP, "\n");
1512     }
1513     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1514     nextGame = q - p;
1515     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516     safeStrCpy(q, p, strlen(p) + 2);
1517     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521         q[nextGame] = '*';
1522     }
1523     fseek(tf, -(strlen(p)+4), SEEK_END);
1524     c = fgetc(tf);
1525     if(c != '"') // depending on DOS or Unix line endings we can be one off
1526          fseek(tf, -(strlen(p)+2), SEEK_END);
1527     else fseek(tf, -(strlen(p)+3), SEEK_END);
1528     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529     DisplayMessage(buf, "");
1530     free(p); appData.results = q;
1531     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533       int round = appData.defaultMatchGames * appData.tourneyType;
1534       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1535          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536         UnloadEngine(&first);  // next game belongs to other pairing;
1537         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1538     }
1539     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 }
1541
1542 void
1543 MatchEvent (int mode)
1544 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1545         int dummy;
1546         if(matchMode) { // already in match mode: switch it off
1547             abortMatch = TRUE;
1548             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549             return;
1550         }
1551 //      if(gameMode != BeginningOfGame) {
1552 //          DisplayError(_("You can only start a match from the initial position."), 0);
1553 //          return;
1554 //      }
1555         abortMatch = FALSE;
1556         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557         /* Set up machine vs. machine match */
1558         nextGame = 0;
1559         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560         if(appData.tourneyFile[0]) {
1561             ReserveGame(-1, 0);
1562             if(nextGame > appData.matchGames) {
1563                 char buf[MSG_SIZ];
1564                 if(strchr(appData.results, '*') == NULL) {
1565                     FILE *f;
1566                     appData.tourneyCycles++;
1567                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1568                         fclose(f);
1569                         NextTourneyGame(-1, &dummy);
1570                         ReserveGame(-1, 0);
1571                         if(nextGame <= appData.matchGames) {
1572                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1573                             matchMode = mode;
1574                             ScheduleDelayedEvent(NextMatchGame, 10000);
1575                             return;
1576                         }
1577                     }
1578                 }
1579                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580                 DisplayError(buf, 0);
1581                 appData.tourneyFile[0] = 0;
1582                 return;
1583             }
1584         } else
1585         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1586             DisplayFatalError(_("Can't have a match with no chess programs"),
1587                               0, 2);
1588             return;
1589         }
1590         matchMode = mode;
1591         matchGame = roundNr = 1;
1592         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593         NextMatchGame();
1594 }
1595
1596 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1597
1598 void
1599 InitBackEnd3 P((void))
1600 {
1601     GameMode initialMode;
1602     char buf[MSG_SIZ];
1603     int err, len;
1604
1605     ParseFeatures(appData.features[0], &first);
1606     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1607        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1608         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1609        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1610        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1611         char c, *q = first.variants, *p = strchr(q, ',');
1612         if(p) *p = NULLCHAR;
1613         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1614             int w, h, s;
1615             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1616                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1617             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1618             Reset(TRUE, FALSE);         // and re-initialize
1619         }
1620         if(p) *p = ',';
1621     }
1622
1623     InitChessProgram(&first, startedFromSetupPosition);
1624
1625     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1626         free(programVersion);
1627         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1628         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1629         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1630     }
1631
1632     if (appData.icsActive) {
1633 #ifdef WIN32
1634         /* [DM] Make a console window if needed [HGM] merged ifs */
1635         ConsoleCreate();
1636 #endif
1637         err = establish();
1638         if (err != 0)
1639           {
1640             if (*appData.icsCommPort != NULLCHAR)
1641               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1642                              appData.icsCommPort);
1643             else
1644               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1645                         appData.icsHost, appData.icsPort);
1646
1647             if( (len >= MSG_SIZ) && appData.debugMode )
1648               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1649
1650             DisplayFatalError(buf, err, 1);
1651             return;
1652         }
1653         SetICSMode();
1654         telnetISR =
1655           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1656         fromUserISR =
1657           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1658         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1659             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1660     } else if (appData.noChessProgram) {
1661         SetNCPMode();
1662     } else {
1663         SetGNUMode();
1664     }
1665
1666     if (*appData.cmailGameName != NULLCHAR) {
1667         SetCmailMode();
1668         OpenLoopback(&cmailPR);
1669         cmailISR =
1670           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1671     }
1672
1673     ThawUI();
1674     DisplayMessage("", "");
1675     if (StrCaseCmp(appData.initialMode, "") == 0) {
1676       initialMode = BeginningOfGame;
1677       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1678         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1679         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1680         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1681         ModeHighlight();
1682       }
1683     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1684       initialMode = TwoMachinesPlay;
1685     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1686       initialMode = AnalyzeFile;
1687     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1688       initialMode = AnalyzeMode;
1689     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1690       initialMode = MachinePlaysWhite;
1691     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1692       initialMode = MachinePlaysBlack;
1693     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1694       initialMode = EditGame;
1695     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1696       initialMode = EditPosition;
1697     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1698       initialMode = Training;
1699     } else {
1700       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1701       if( (len >= MSG_SIZ) && appData.debugMode )
1702         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1703
1704       DisplayFatalError(buf, 0, 2);
1705       return;
1706     }
1707
1708     if (appData.matchMode) {
1709         if(appData.tourneyFile[0]) { // start tourney from command line
1710             FILE *f;
1711             if(f = fopen(appData.tourneyFile, "r")) {
1712                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1713                 fclose(f);
1714                 appData.clockMode = TRUE;
1715                 SetGNUMode();
1716             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1717         }
1718         MatchEvent(TRUE);
1719     } else if (*appData.cmailGameName != NULLCHAR) {
1720         /* Set up cmail mode */
1721         ReloadCmailMsgEvent(TRUE);
1722     } else {
1723         /* Set up other modes */
1724         if (initialMode == AnalyzeFile) {
1725           if (*appData.loadGameFile == NULLCHAR) {
1726             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1727             return;
1728           }
1729         }
1730         if (*appData.loadGameFile != NULLCHAR) {
1731             (void) LoadGameFromFile(appData.loadGameFile,
1732                                     appData.loadGameIndex,
1733                                     appData.loadGameFile, TRUE);
1734         } else if (*appData.loadPositionFile != NULLCHAR) {
1735             (void) LoadPositionFromFile(appData.loadPositionFile,
1736                                         appData.loadPositionIndex,
1737                                         appData.loadPositionFile);
1738             /* [HGM] try to make self-starting even after FEN load */
1739             /* to allow automatic setup of fairy variants with wtm */
1740             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1741                 gameMode = BeginningOfGame;
1742                 setboardSpoiledMachineBlack = 1;
1743             }
1744             /* [HGM] loadPos: make that every new game uses the setup */
1745             /* from file as long as we do not switch variant          */
1746             if(!blackPlaysFirst) {
1747                 startedFromPositionFile = TRUE;
1748                 CopyBoard(filePosition, boards[0]);
1749                 CopyBoard(initialPosition, boards[0]);
1750             }
1751         } else if(*appData.fen != NULLCHAR) {
1752             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1753                 startedFromPositionFile = TRUE;
1754                 Reset(TRUE, TRUE);
1755             }
1756         }
1757         if (initialMode == AnalyzeMode) {
1758           if (appData.noChessProgram) {
1759             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1760             return;
1761           }
1762           if (appData.icsActive) {
1763             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1764             return;
1765           }
1766           AnalyzeModeEvent();
1767         } else if (initialMode == AnalyzeFile) {
1768           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1769           ShowThinkingEvent();
1770           AnalyzeFileEvent();
1771           AnalysisPeriodicEvent(1);
1772         } else if (initialMode == MachinePlaysWhite) {
1773           if (appData.noChessProgram) {
1774             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1775                               0, 2);
1776             return;
1777           }
1778           if (appData.icsActive) {
1779             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1780                               0, 2);
1781             return;
1782           }
1783           MachineWhiteEvent();
1784         } else if (initialMode == MachinePlaysBlack) {
1785           if (appData.noChessProgram) {
1786             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1787                               0, 2);
1788             return;
1789           }
1790           if (appData.icsActive) {
1791             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1792                               0, 2);
1793             return;
1794           }
1795           MachineBlackEvent();
1796         } else if (initialMode == TwoMachinesPlay) {
1797           if (appData.noChessProgram) {
1798             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1799                               0, 2);
1800             return;
1801           }
1802           if (appData.icsActive) {
1803             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1804                               0, 2);
1805             return;
1806           }
1807           TwoMachinesEvent();
1808         } else if (initialMode == EditGame) {
1809           EditGameEvent();
1810         } else if (initialMode == EditPosition) {
1811           EditPositionEvent();
1812         } else if (initialMode == Training) {
1813           if (*appData.loadGameFile == NULLCHAR) {
1814             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1815             return;
1816           }
1817           TrainingEvent();
1818         }
1819     }
1820 }
1821
1822 void
1823 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1824 {
1825     DisplayBook(current+1);
1826
1827     MoveHistorySet( movelist, first, last, current, pvInfoList );
1828
1829     EvalGraphSet( first, last, current, pvInfoList );
1830
1831     MakeEngineOutputTitle();
1832 }
1833
1834 /*
1835  * Establish will establish a contact to a remote host.port.
1836  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1837  *  used to talk to the host.
1838  * Returns 0 if okay, error code if not.
1839  */
1840 int
1841 establish ()
1842 {
1843     char buf[MSG_SIZ];
1844
1845     if (*appData.icsCommPort != NULLCHAR) {
1846         /* Talk to the host through a serial comm port */
1847         return OpenCommPort(appData.icsCommPort, &icsPR);
1848
1849     } else if (*appData.gateway != NULLCHAR) {
1850         if (*appData.remoteShell == NULLCHAR) {
1851             /* Use the rcmd protocol to run telnet program on a gateway host */
1852             snprintf(buf, sizeof(buf), "%s %s %s",
1853                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1854             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1855
1856         } else {
1857             /* Use the rsh program to run telnet program on a gateway host */
1858             if (*appData.remoteUser == NULLCHAR) {
1859                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1860                         appData.gateway, appData.telnetProgram,
1861                         appData.icsHost, appData.icsPort);
1862             } else {
1863                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1864                         appData.remoteShell, appData.gateway,
1865                         appData.remoteUser, appData.telnetProgram,
1866                         appData.icsHost, appData.icsPort);
1867             }
1868             return StartChildProcess(buf, "", &icsPR);
1869
1870         }
1871     } else if (appData.useTelnet) {
1872         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1873
1874     } else {
1875         /* TCP socket interface differs somewhat between
1876            Unix and NT; handle details in the front end.
1877            */
1878         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1879     }
1880 }
1881
1882 void
1883 EscapeExpand (char *p, char *q)
1884 {       // [HGM] initstring: routine to shape up string arguments
1885         while(*p++ = *q++) if(p[-1] == '\\')
1886             switch(*q++) {
1887                 case 'n': p[-1] = '\n'; break;
1888                 case 'r': p[-1] = '\r'; break;
1889                 case 't': p[-1] = '\t'; break;
1890                 case '\\': p[-1] = '\\'; break;
1891                 case 0: *p = 0; return;
1892                 default: p[-1] = q[-1]; break;
1893             }
1894 }
1895
1896 void
1897 show_bytes (FILE *fp, char *buf, int count)
1898 {
1899     while (count--) {
1900         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1901             fprintf(fp, "\\%03o", *buf & 0xff);
1902         } else {
1903             putc(*buf, fp);
1904         }
1905         buf++;
1906     }
1907     fflush(fp);
1908 }
1909
1910 /* Returns an errno value */
1911 int
1912 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1913 {
1914     char buf[8192], *p, *q, *buflim;
1915     int left, newcount, outcount;
1916
1917     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1918         *appData.gateway != NULLCHAR) {
1919         if (appData.debugMode) {
1920             fprintf(debugFP, ">ICS: ");
1921             show_bytes(debugFP, message, count);
1922             fprintf(debugFP, "\n");
1923         }
1924         return OutputToProcess(pr, message, count, outError);
1925     }
1926
1927     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1928     p = message;
1929     q = buf;
1930     left = count;
1931     newcount = 0;
1932     while (left) {
1933         if (q >= buflim) {
1934             if (appData.debugMode) {
1935                 fprintf(debugFP, ">ICS: ");
1936                 show_bytes(debugFP, buf, newcount);
1937                 fprintf(debugFP, "\n");
1938             }
1939             outcount = OutputToProcess(pr, buf, newcount, outError);
1940             if (outcount < newcount) return -1; /* to be sure */
1941             q = buf;
1942             newcount = 0;
1943         }
1944         if (*p == '\n') {
1945             *q++ = '\r';
1946             newcount++;
1947         } else if (((unsigned char) *p) == TN_IAC) {
1948             *q++ = (char) TN_IAC;
1949             newcount ++;
1950         }
1951         *q++ = *p++;
1952         newcount++;
1953         left--;
1954     }
1955     if (appData.debugMode) {
1956         fprintf(debugFP, ">ICS: ");
1957         show_bytes(debugFP, buf, newcount);
1958         fprintf(debugFP, "\n");
1959     }
1960     outcount = OutputToProcess(pr, buf, newcount, outError);
1961     if (outcount < newcount) return -1; /* to be sure */
1962     return count;
1963 }
1964
1965 void
1966 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1967 {
1968     int outError, outCount;
1969     static int gotEof = 0;
1970     static FILE *ini;
1971
1972     /* Pass data read from player on to ICS */
1973     if (count > 0) {
1974         gotEof = 0;
1975         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1976         if (outCount < count) {
1977             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1978         }
1979         if(have_sent_ICS_logon == 2) {
1980           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1981             fprintf(ini, "%s", message);
1982             have_sent_ICS_logon = 3;
1983           } else
1984             have_sent_ICS_logon = 1;
1985         } else if(have_sent_ICS_logon == 3) {
1986             fprintf(ini, "%s", message);
1987             fclose(ini);
1988           have_sent_ICS_logon = 1;
1989         }
1990     } else if (count < 0) {
1991         RemoveInputSource(isr);
1992         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1993     } else if (gotEof++ > 0) {
1994         RemoveInputSource(isr);
1995         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1996     }
1997 }
1998
1999 void
2000 KeepAlive ()
2001 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2002     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2003     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2004     SendToICS("date\n");
2005     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2006 }
2007
2008 /* added routine for printf style output to ics */
2009 void
2010 ics_printf (char *format, ...)
2011 {
2012     char buffer[MSG_SIZ];
2013     va_list args;
2014
2015     va_start(args, format);
2016     vsnprintf(buffer, sizeof(buffer), format, args);
2017     buffer[sizeof(buffer)-1] = '\0';
2018     SendToICS(buffer);
2019     va_end(args);
2020 }
2021
2022 void
2023 SendToICS (char *s)
2024 {
2025     int count, outCount, outError;
2026
2027     if (icsPR == NoProc) return;
2028
2029     count = strlen(s);
2030     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2031     if (outCount < count) {
2032         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2033     }
2034 }
2035
2036 /* This is used for sending logon scripts to the ICS. Sending
2037    without a delay causes problems when using timestamp on ICC
2038    (at least on my machine). */
2039 void
2040 SendToICSDelayed (char *s, long msdelay)
2041 {
2042     int count, outCount, outError;
2043
2044     if (icsPR == NoProc) return;
2045
2046     count = strlen(s);
2047     if (appData.debugMode) {
2048         fprintf(debugFP, ">ICS: ");
2049         show_bytes(debugFP, s, count);
2050         fprintf(debugFP, "\n");
2051     }
2052     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2053                                       msdelay);
2054     if (outCount < count) {
2055         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2056     }
2057 }
2058
2059
2060 /* Remove all highlighting escape sequences in s
2061    Also deletes any suffix starting with '('
2062    */
2063 char *
2064 StripHighlightAndTitle (char *s)
2065 {
2066     static char retbuf[MSG_SIZ];
2067     char *p = retbuf;
2068
2069     while (*s != NULLCHAR) {
2070         while (*s == '\033') {
2071             while (*s != NULLCHAR && !isalpha(*s)) s++;
2072             if (*s != NULLCHAR) s++;
2073         }
2074         while (*s != NULLCHAR && *s != '\033') {
2075             if (*s == '(' || *s == '[') {
2076                 *p = NULLCHAR;
2077                 return retbuf;
2078             }
2079             *p++ = *s++;
2080         }
2081     }
2082     *p = NULLCHAR;
2083     return retbuf;
2084 }
2085
2086 /* Remove all highlighting escape sequences in s */
2087 char *
2088 StripHighlight (char *s)
2089 {
2090     static char retbuf[MSG_SIZ];
2091     char *p = retbuf;
2092
2093     while (*s != NULLCHAR) {
2094         while (*s == '\033') {
2095             while (*s != NULLCHAR && !isalpha(*s)) s++;
2096             if (*s != NULLCHAR) s++;
2097         }
2098         while (*s != NULLCHAR && *s != '\033') {
2099             *p++ = *s++;
2100         }
2101     }
2102     *p = NULLCHAR;
2103     return retbuf;
2104 }
2105
2106 char engineVariant[MSG_SIZ];
2107 char *variantNames[] = VARIANT_NAMES;
2108 char *
2109 VariantName (VariantClass v)
2110 {
2111     if(v == VariantUnknown || *engineVariant) return engineVariant;
2112     return variantNames[v];
2113 }
2114
2115
2116 /* Identify a variant from the strings the chess servers use or the
2117    PGN Variant tag names we use. */
2118 VariantClass
2119 StringToVariant (char *e)
2120 {
2121     char *p;
2122     int wnum = -1;
2123     VariantClass v = VariantNormal;
2124     int i, found = FALSE;
2125     char buf[MSG_SIZ], c;
2126     int len;
2127
2128     if (!e) return v;
2129
2130     /* [HGM] skip over optional board-size prefixes */
2131     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2132         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2133         while( *e++ != '_');
2134     }
2135
2136     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2137         v = VariantNormal;
2138         found = TRUE;
2139     } else
2140     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2141       if (p = StrCaseStr(e, variantNames[i])) {
2142         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2143         v = (VariantClass) i;
2144         found = TRUE;
2145         break;
2146       }
2147     }
2148
2149     if (!found) {
2150       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2151           || StrCaseStr(e, "wild/fr")
2152           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2153         v = VariantFischeRandom;
2154       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2155                  (i = 1, p = StrCaseStr(e, "w"))) {
2156         p += i;
2157         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2158         if (isdigit(*p)) {
2159           wnum = atoi(p);
2160         } else {
2161           wnum = -1;
2162         }
2163         switch (wnum) {
2164         case 0: /* FICS only, actually */
2165         case 1:
2166           /* Castling legal even if K starts on d-file */
2167           v = VariantWildCastle;
2168           break;
2169         case 2:
2170         case 3:
2171         case 4:
2172           /* Castling illegal even if K & R happen to start in
2173              normal positions. */
2174           v = VariantNoCastle;
2175           break;
2176         case 5:
2177         case 7:
2178         case 8:
2179         case 10:
2180         case 11:
2181         case 12:
2182         case 13:
2183         case 14:
2184         case 15:
2185         case 18:
2186         case 19:
2187           /* Castling legal iff K & R start in normal positions */
2188           v = VariantNormal;
2189           break;
2190         case 6:
2191         case 20:
2192         case 21:
2193           /* Special wilds for position setup; unclear what to do here */
2194           v = VariantLoadable;
2195           break;
2196         case 9:
2197           /* Bizarre ICC game */
2198           v = VariantTwoKings;
2199           break;
2200         case 16:
2201           v = VariantKriegspiel;
2202           break;
2203         case 17:
2204           v = VariantLosers;
2205           break;
2206         case 22:
2207           v = VariantFischeRandom;
2208           break;
2209         case 23:
2210           v = VariantCrazyhouse;
2211           break;
2212         case 24:
2213           v = VariantBughouse;
2214           break;
2215         case 25:
2216           v = Variant3Check;
2217           break;
2218         case 26:
2219           /* Not quite the same as FICS suicide! */
2220           v = VariantGiveaway;
2221           break;
2222         case 27:
2223           v = VariantAtomic;
2224           break;
2225         case 28:
2226           v = VariantShatranj;
2227           break;
2228
2229         /* Temporary names for future ICC types.  The name *will* change in
2230            the next xboard/WinBoard release after ICC defines it. */
2231         case 29:
2232           v = Variant29;
2233           break;
2234         case 30:
2235           v = Variant30;
2236           break;
2237         case 31:
2238           v = Variant31;
2239           break;
2240         case 32:
2241           v = Variant32;
2242           break;
2243         case 33:
2244           v = Variant33;
2245           break;
2246         case 34:
2247           v = Variant34;
2248           break;
2249         case 35:
2250           v = Variant35;
2251           break;
2252         case 36:
2253           v = Variant36;
2254           break;
2255         case 37:
2256           v = VariantShogi;
2257           break;
2258         case 38:
2259           v = VariantXiangqi;
2260           break;
2261         case 39:
2262           v = VariantCourier;
2263           break;
2264         case 40:
2265           v = VariantGothic;
2266           break;
2267         case 41:
2268           v = VariantCapablanca;
2269           break;
2270         case 42:
2271           v = VariantKnightmate;
2272           break;
2273         case 43:
2274           v = VariantFairy;
2275           break;
2276         case 44:
2277           v = VariantCylinder;
2278           break;
2279         case 45:
2280           v = VariantFalcon;
2281           break;
2282         case 46:
2283           v = VariantCapaRandom;
2284           break;
2285         case 47:
2286           v = VariantBerolina;
2287           break;
2288         case 48:
2289           v = VariantJanus;
2290           break;
2291         case 49:
2292           v = VariantSuper;
2293           break;
2294         case 50:
2295           v = VariantGreat;
2296           break;
2297         case -1:
2298           /* Found "wild" or "w" in the string but no number;
2299              must assume it's normal chess. */
2300           v = VariantNormal;
2301           break;
2302         default:
2303           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2304           if( (len >= MSG_SIZ) && appData.debugMode )
2305             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2306
2307           DisplayError(buf, 0);
2308           v = VariantUnknown;
2309           break;
2310         }
2311       }
2312     }
2313     if (appData.debugMode) {
2314       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2315               e, wnum, VariantName(v));
2316     }
2317     return v;
2318 }
2319
2320 static int leftover_start = 0, leftover_len = 0;
2321 char star_match[STAR_MATCH_N][MSG_SIZ];
2322
2323 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2324    advance *index beyond it, and set leftover_start to the new value of
2325    *index; else return FALSE.  If pattern contains the character '*', it
2326    matches any sequence of characters not containing '\r', '\n', or the
2327    character following the '*' (if any), and the matched sequence(s) are
2328    copied into star_match.
2329    */
2330 int
2331 looking_at ( char *buf, int *index, char *pattern)
2332 {
2333     char *bufp = &buf[*index], *patternp = pattern;
2334     int star_count = 0;
2335     char *matchp = star_match[0];
2336
2337     for (;;) {
2338         if (*patternp == NULLCHAR) {
2339             *index = leftover_start = bufp - buf;
2340             *matchp = NULLCHAR;
2341             return TRUE;
2342         }
2343         if (*bufp == NULLCHAR) return FALSE;
2344         if (*patternp == '*') {
2345             if (*bufp == *(patternp + 1)) {
2346                 *matchp = NULLCHAR;
2347                 matchp = star_match[++star_count];
2348                 patternp += 2;
2349                 bufp++;
2350                 continue;
2351             } else if (*bufp == '\n' || *bufp == '\r') {
2352                 patternp++;
2353                 if (*patternp == NULLCHAR)
2354                   continue;
2355                 else
2356                   return FALSE;
2357             } else {
2358                 *matchp++ = *bufp++;
2359                 continue;
2360             }
2361         }
2362         if (*patternp != *bufp) return FALSE;
2363         patternp++;
2364         bufp++;
2365     }
2366 }
2367
2368 void
2369 SendToPlayer (char *data, int length)
2370 {
2371     int error, outCount;
2372     outCount = OutputToProcess(NoProc, data, length, &error);
2373     if (outCount < length) {
2374         DisplayFatalError(_("Error writing to display"), error, 1);
2375     }
2376 }
2377
2378 void
2379 PackHolding (char packed[], char *holding)
2380 {
2381     char *p = holding;
2382     char *q = packed;
2383     int runlength = 0;
2384     int curr = 9999;
2385     do {
2386         if (*p == curr) {
2387             runlength++;
2388         } else {
2389             switch (runlength) {
2390               case 0:
2391                 break;
2392               case 1:
2393                 *q++ = curr;
2394                 break;
2395               case 2:
2396                 *q++ = curr;
2397                 *q++ = curr;
2398                 break;
2399               default:
2400                 sprintf(q, "%d", runlength);
2401                 while (*q) q++;
2402                 *q++ = curr;
2403                 break;
2404             }
2405             runlength = 1;
2406             curr = *p;
2407         }
2408     } while (*p++);
2409     *q = NULLCHAR;
2410 }
2411
2412 /* Telnet protocol requests from the front end */
2413 void
2414 TelnetRequest (unsigned char ddww, unsigned char option)
2415 {
2416     unsigned char msg[3];
2417     int outCount, outError;
2418
2419     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2420
2421     if (appData.debugMode) {
2422         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2423         switch (ddww) {
2424           case TN_DO:
2425             ddwwStr = "DO";
2426             break;
2427           case TN_DONT:
2428             ddwwStr = "DONT";
2429             break;
2430           case TN_WILL:
2431             ddwwStr = "WILL";
2432             break;
2433           case TN_WONT:
2434             ddwwStr = "WONT";
2435             break;
2436           default:
2437             ddwwStr = buf1;
2438             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2439             break;
2440         }
2441         switch (option) {
2442           case TN_ECHO:
2443             optionStr = "ECHO";
2444             break;
2445           default:
2446             optionStr = buf2;
2447             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2448             break;
2449         }
2450         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2451     }
2452     msg[0] = TN_IAC;
2453     msg[1] = ddww;
2454     msg[2] = option;
2455     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2456     if (outCount < 3) {
2457         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2458     }
2459 }
2460
2461 void
2462 DoEcho ()
2463 {
2464     if (!appData.icsActive) return;
2465     TelnetRequest(TN_DO, TN_ECHO);
2466 }
2467
2468 void
2469 DontEcho ()
2470 {
2471     if (!appData.icsActive) return;
2472     TelnetRequest(TN_DONT, TN_ECHO);
2473 }
2474
2475 void
2476 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2477 {
2478     /* put the holdings sent to us by the server on the board holdings area */
2479     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2480     char p;
2481     ChessSquare piece;
2482
2483     if(gameInfo.holdingsWidth < 2)  return;
2484     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2485         return; // prevent overwriting by pre-board holdings
2486
2487     if( (int)lowestPiece >= BlackPawn ) {
2488         holdingsColumn = 0;
2489         countsColumn = 1;
2490         holdingsStartRow = BOARD_HEIGHT-1;
2491         direction = -1;
2492     } else {
2493         holdingsColumn = BOARD_WIDTH-1;
2494         countsColumn = BOARD_WIDTH-2;
2495         holdingsStartRow = 0;
2496         direction = 1;
2497     }
2498
2499     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2500         board[i][holdingsColumn] = EmptySquare;
2501         board[i][countsColumn]   = (ChessSquare) 0;
2502     }
2503     while( (p=*holdings++) != NULLCHAR ) {
2504         piece = CharToPiece( ToUpper(p) );
2505         if(piece == EmptySquare) continue;
2506         /*j = (int) piece - (int) WhitePawn;*/
2507         j = PieceToNumber(piece);
2508         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2509         if(j < 0) continue;               /* should not happen */
2510         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2511         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2512         board[holdingsStartRow+j*direction][countsColumn]++;
2513     }
2514 }
2515
2516
2517 void
2518 VariantSwitch (Board board, VariantClass newVariant)
2519 {
2520    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2521    static Board oldBoard;
2522
2523    startedFromPositionFile = FALSE;
2524    if(gameInfo.variant == newVariant) return;
2525
2526    /* [HGM] This routine is called each time an assignment is made to
2527     * gameInfo.variant during a game, to make sure the board sizes
2528     * are set to match the new variant. If that means adding or deleting
2529     * holdings, we shift the playing board accordingly
2530     * This kludge is needed because in ICS observe mode, we get boards
2531     * of an ongoing game without knowing the variant, and learn about the
2532     * latter only later. This can be because of the move list we requested,
2533     * in which case the game history is refilled from the beginning anyway,
2534     * but also when receiving holdings of a crazyhouse game. In the latter
2535     * case we want to add those holdings to the already received position.
2536     */
2537
2538
2539    if (appData.debugMode) {
2540      fprintf(debugFP, "Switch board from %s to %s\n",
2541              VariantName(gameInfo.variant), VariantName(newVariant));
2542      setbuf(debugFP, NULL);
2543    }
2544    shuffleOpenings = 0;       /* [HGM] shuffle */
2545    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2546    switch(newVariant)
2547      {
2548      case VariantShogi:
2549        newWidth = 9;  newHeight = 9;
2550        gameInfo.holdingsSize = 7;
2551      case VariantBughouse:
2552      case VariantCrazyhouse:
2553        newHoldingsWidth = 2; break;
2554      case VariantGreat:
2555        newWidth = 10;
2556      case VariantSuper:
2557        newHoldingsWidth = 2;
2558        gameInfo.holdingsSize = 8;
2559        break;
2560      case VariantGothic:
2561      case VariantCapablanca:
2562      case VariantCapaRandom:
2563        newWidth = 10;
2564      default:
2565        newHoldingsWidth = gameInfo.holdingsSize = 0;
2566      };
2567
2568    if(newWidth  != gameInfo.boardWidth  ||
2569       newHeight != gameInfo.boardHeight ||
2570       newHoldingsWidth != gameInfo.holdingsWidth ) {
2571
2572      /* shift position to new playing area, if needed */
2573      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2574        for(i=0; i<BOARD_HEIGHT; i++)
2575          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2576            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2577              board[i][j];
2578        for(i=0; i<newHeight; i++) {
2579          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2580          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2581        }
2582      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2583        for(i=0; i<BOARD_HEIGHT; i++)
2584          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2585            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2586              board[i][j];
2587      }
2588      board[HOLDINGS_SET] = 0;
2589      gameInfo.boardWidth  = newWidth;
2590      gameInfo.boardHeight = newHeight;
2591      gameInfo.holdingsWidth = newHoldingsWidth;
2592      gameInfo.variant = newVariant;
2593      InitDrawingSizes(-2, 0);
2594    } else gameInfo.variant = newVariant;
2595    CopyBoard(oldBoard, board);   // remember correctly formatted board
2596      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2597    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2598 }
2599
2600 static int loggedOn = FALSE;
2601
2602 /*-- Game start info cache: --*/
2603 int gs_gamenum;
2604 char gs_kind[MSG_SIZ];
2605 static char player1Name[128] = "";
2606 static char player2Name[128] = "";
2607 static char cont_seq[] = "\n\\   ";
2608 static int player1Rating = -1;
2609 static int player2Rating = -1;
2610 /*----------------------------*/
2611
2612 ColorClass curColor = ColorNormal;
2613 int suppressKibitz = 0;
2614
2615 // [HGM] seekgraph
2616 Boolean soughtPending = FALSE;
2617 Boolean seekGraphUp;
2618 #define MAX_SEEK_ADS 200
2619 #define SQUARE 0x80
2620 char *seekAdList[MAX_SEEK_ADS];
2621 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2622 float tcList[MAX_SEEK_ADS];
2623 char colorList[MAX_SEEK_ADS];
2624 int nrOfSeekAds = 0;
2625 int minRating = 1010, maxRating = 2800;
2626 int hMargin = 10, vMargin = 20, h, w;
2627 extern int squareSize, lineGap;
2628
2629 void
2630 PlotSeekAd (int i)
2631 {
2632         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2633         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2634         if(r < minRating+100 && r >=0 ) r = minRating+100;
2635         if(r > maxRating) r = maxRating;
2636         if(tc < 1.f) tc = 1.f;
2637         if(tc > 95.f) tc = 95.f;
2638         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2639         y = ((double)r - minRating)/(maxRating - minRating)
2640             * (h-vMargin-squareSize/8-1) + vMargin;
2641         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2642         if(strstr(seekAdList[i], " u ")) color = 1;
2643         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2644            !strstr(seekAdList[i], "bullet") &&
2645            !strstr(seekAdList[i], "blitz") &&
2646            !strstr(seekAdList[i], "standard") ) color = 2;
2647         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2648         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2649 }
2650
2651 void
2652 PlotSingleSeekAd (int i)
2653 {
2654         PlotSeekAd(i);
2655 }
2656
2657 void
2658 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2659 {
2660         char buf[MSG_SIZ], *ext = "";
2661         VariantClass v = StringToVariant(type);
2662         if(strstr(type, "wild")) {
2663             ext = type + 4; // append wild number
2664             if(v == VariantFischeRandom) type = "chess960"; else
2665             if(v == VariantLoadable) type = "setup"; else
2666             type = VariantName(v);
2667         }
2668         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2669         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2670             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2671             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2672             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2673             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2674             seekNrList[nrOfSeekAds] = nr;
2675             zList[nrOfSeekAds] = 0;
2676             seekAdList[nrOfSeekAds++] = StrSave(buf);
2677             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2678         }
2679 }
2680
2681 void
2682 EraseSeekDot (int i)
2683 {
2684     int x = xList[i], y = yList[i], d=squareSize/4, k;
2685     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2686     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2687     // now replot every dot that overlapped
2688     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2689         int xx = xList[k], yy = yList[k];
2690         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2691             DrawSeekDot(xx, yy, colorList[k]);
2692     }
2693 }
2694
2695 void
2696 RemoveSeekAd (int nr)
2697 {
2698         int i;
2699         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2700             EraseSeekDot(i);
2701             if(seekAdList[i]) free(seekAdList[i]);
2702             seekAdList[i] = seekAdList[--nrOfSeekAds];
2703             seekNrList[i] = seekNrList[nrOfSeekAds];
2704             ratingList[i] = ratingList[nrOfSeekAds];
2705             colorList[i]  = colorList[nrOfSeekAds];
2706             tcList[i] = tcList[nrOfSeekAds];
2707             xList[i]  = xList[nrOfSeekAds];
2708             yList[i]  = yList[nrOfSeekAds];
2709             zList[i]  = zList[nrOfSeekAds];
2710             seekAdList[nrOfSeekAds] = NULL;
2711             break;
2712         }
2713 }
2714
2715 Boolean
2716 MatchSoughtLine (char *line)
2717 {
2718     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2719     int nr, base, inc, u=0; char dummy;
2720
2721     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2723        (u=1) &&
2724        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2725         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2726         // match: compact and save the line
2727         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2728         return TRUE;
2729     }
2730     return FALSE;
2731 }
2732
2733 int
2734 DrawSeekGraph ()
2735 {
2736     int i;
2737     if(!seekGraphUp) return FALSE;
2738     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2739     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2740
2741     DrawSeekBackground(0, 0, w, h);
2742     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2743     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2744     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2745         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2746         yy = h-1-yy;
2747         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2748         if(i%500 == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2752         }
2753     }
2754     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2755     for(i=1; i<100; i+=(i<10?1:5)) {
2756         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2757         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2758         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2759             char buf[MSG_SIZ];
2760             snprintf(buf, MSG_SIZ, "%d", i);
2761             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2762         }
2763     }
2764     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2765     return TRUE;
2766 }
2767
2768 int
2769 SeekGraphClick (ClickType click, int x, int y, int moving)
2770 {
2771     static int lastDown = 0, displayed = 0, lastSecond;
2772     if(y < 0) return FALSE;
2773     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2774         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2775         if(!seekGraphUp) return FALSE;
2776         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2777         DrawPosition(TRUE, NULL);
2778         return TRUE;
2779     }
2780     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2781         if(click == Release || moving) return FALSE;
2782         nrOfSeekAds = 0;
2783         soughtPending = TRUE;
2784         SendToICS(ics_prefix);
2785         SendToICS("sought\n"); // should this be "sought all"?
2786     } else { // issue challenge based on clicked ad
2787         int dist = 10000; int i, closest = 0, second = 0;
2788         for(i=0; i<nrOfSeekAds; i++) {
2789             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2790             if(d < dist) { dist = d; closest = i; }
2791             second += (d - zList[i] < 120); // count in-range ads
2792             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2793         }
2794         if(dist < 120) {
2795             char buf[MSG_SIZ];
2796             second = (second > 1);
2797             if(displayed != closest || second != lastSecond) {
2798                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2799                 lastSecond = second; displayed = closest;
2800             }
2801             if(click == Press) {
2802                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2803                 lastDown = closest;
2804                 return TRUE;
2805             } // on press 'hit', only show info
2806             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2807             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2808             SendToICS(ics_prefix);
2809             SendToICS(buf);
2810             return TRUE; // let incoming board of started game pop down the graph
2811         } else if(click == Release) { // release 'miss' is ignored
2812             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2813             if(moving == 2) { // right up-click
2814                 nrOfSeekAds = 0; // refresh graph
2815                 soughtPending = TRUE;
2816                 SendToICS(ics_prefix);
2817                 SendToICS("sought\n"); // should this be "sought all"?
2818             }
2819             return TRUE;
2820         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2821         // press miss or release hit 'pop down' seek graph
2822         seekGraphUp = FALSE;
2823         DrawPosition(TRUE, NULL);
2824     }
2825     return TRUE;
2826 }
2827
2828 void
2829 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2830 {
2831 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2832 #define STARTED_NONE 0
2833 #define STARTED_MOVES 1
2834 #define STARTED_BOARD 2
2835 #define STARTED_OBSERVE 3
2836 #define STARTED_HOLDINGS 4
2837 #define STARTED_CHATTER 5
2838 #define STARTED_COMMENT 6
2839 #define STARTED_MOVES_NOHIDE 7
2840
2841     static int started = STARTED_NONE;
2842     static char parse[20000];
2843     static int parse_pos = 0;
2844     static char buf[BUF_SIZE + 1];
2845     static int firstTime = TRUE, intfSet = FALSE;
2846     static ColorClass prevColor = ColorNormal;
2847     static int savingComment = FALSE;
2848     static int cmatch = 0; // continuation sequence match
2849     char *bp;
2850     char str[MSG_SIZ];
2851     int i, oldi;
2852     int buf_len;
2853     int next_out;
2854     int tkind;
2855     int backup;    /* [DM] For zippy color lines */
2856     char *p;
2857     char talker[MSG_SIZ]; // [HGM] chat
2858     int channel, collective=0;
2859
2860     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2861
2862     if (appData.debugMode) {
2863       if (!error) {
2864         fprintf(debugFP, "<ICS: ");
2865         show_bytes(debugFP, data, count);
2866         fprintf(debugFP, "\n");
2867       }
2868     }
2869
2870     if (appData.debugMode) { int f = forwardMostMove;
2871         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2872                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2873                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2874     }
2875     if (count > 0) {
2876         /* If last read ended with a partial line that we couldn't parse,
2877            prepend it to the new read and try again. */
2878         if (leftover_len > 0) {
2879             for (i=0; i<leftover_len; i++)
2880               buf[i] = buf[leftover_start + i];
2881         }
2882
2883     /* copy new characters into the buffer */
2884     bp = buf + leftover_len;
2885     buf_len=leftover_len;
2886     for (i=0; i<count; i++)
2887     {
2888         // ignore these
2889         if (data[i] == '\r')
2890             continue;
2891
2892         // join lines split by ICS?
2893         if (!appData.noJoin)
2894         {
2895             /*
2896                 Joining just consists of finding matches against the
2897                 continuation sequence, and discarding that sequence
2898                 if found instead of copying it.  So, until a match
2899                 fails, there's nothing to do since it might be the
2900                 complete sequence, and thus, something we don't want
2901                 copied.
2902             */
2903             if (data[i] == cont_seq[cmatch])
2904             {
2905                 cmatch++;
2906                 if (cmatch == strlen(cont_seq))
2907                 {
2908                     cmatch = 0; // complete match.  just reset the counter
2909
2910                     /*
2911                         it's possible for the ICS to not include the space
2912                         at the end of the last word, making our [correct]
2913                         join operation fuse two separate words.  the server
2914                         does this when the space occurs at the width setting.
2915                     */
2916                     if (!buf_len || buf[buf_len-1] != ' ')
2917                     {
2918                         *bp++ = ' ';
2919                         buf_len++;
2920                     }
2921                 }
2922                 continue;
2923             }
2924             else if (cmatch)
2925             {
2926                 /*
2927                     match failed, so we have to copy what matched before
2928                     falling through and copying this character.  In reality,
2929                     this will only ever be just the newline character, but
2930                     it doesn't hurt to be precise.
2931                 */
2932                 strncpy(bp, cont_seq, cmatch);
2933                 bp += cmatch;
2934                 buf_len += cmatch;
2935                 cmatch = 0;
2936             }
2937         }
2938
2939         // copy this char
2940         *bp++ = data[i];
2941         buf_len++;
2942     }
2943
2944         buf[buf_len] = NULLCHAR;
2945 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2946         next_out = 0;
2947         leftover_start = 0;
2948
2949         i = 0;
2950         while (i < buf_len) {
2951             /* Deal with part of the TELNET option negotiation
2952                protocol.  We refuse to do anything beyond the
2953                defaults, except that we allow the WILL ECHO option,
2954                which ICS uses to turn off password echoing when we are
2955                directly connected to it.  We reject this option
2956                if localLineEditing mode is on (always on in xboard)
2957                and we are talking to port 23, which might be a real
2958                telnet server that will try to keep WILL ECHO on permanently.
2959              */
2960             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2961                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2962                 unsigned char option;
2963                 oldi = i;
2964                 switch ((unsigned char) buf[++i]) {
2965                   case TN_WILL:
2966                     if (appData.debugMode)
2967                       fprintf(debugFP, "\n<WILL ");
2968                     switch (option = (unsigned char) buf[++i]) {
2969                       case TN_ECHO:
2970                         if (appData.debugMode)
2971                           fprintf(debugFP, "ECHO ");
2972                         /* Reply only if this is a change, according
2973                            to the protocol rules. */
2974                         if (remoteEchoOption) break;
2975                         if (appData.localLineEditing &&
2976                             atoi(appData.icsPort) == TN_PORT) {
2977                             TelnetRequest(TN_DONT, TN_ECHO);
2978                         } else {
2979                             EchoOff();
2980                             TelnetRequest(TN_DO, TN_ECHO);
2981                             remoteEchoOption = TRUE;
2982                         }
2983                         break;
2984                       default:
2985                         if (appData.debugMode)
2986                           fprintf(debugFP, "%d ", option);
2987                         /* Whatever this is, we don't want it. */
2988                         TelnetRequest(TN_DONT, option);
2989                         break;
2990                     }
2991                     break;
2992                   case TN_WONT:
2993                     if (appData.debugMode)
2994                       fprintf(debugFP, "\n<WONT ");
2995                     switch (option = (unsigned char) buf[++i]) {
2996                       case TN_ECHO:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "ECHO ");
2999                         /* Reply only if this is a change, according
3000                            to the protocol rules. */
3001                         if (!remoteEchoOption) break;
3002                         EchoOn();
3003                         TelnetRequest(TN_DONT, TN_ECHO);
3004                         remoteEchoOption = FALSE;
3005                         break;
3006                       default:
3007                         if (appData.debugMode)
3008                           fprintf(debugFP, "%d ", (unsigned char) option);
3009                         /* Whatever this is, it must already be turned
3010                            off, because we never agree to turn on
3011                            anything non-default, so according to the
3012                            protocol rules, we don't reply. */
3013                         break;
3014                     }
3015                     break;
3016                   case TN_DO:
3017                     if (appData.debugMode)
3018                       fprintf(debugFP, "\n<DO ");
3019                     switch (option = (unsigned char) buf[++i]) {
3020                       default:
3021                         /* Whatever this is, we refuse to do it. */
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         TelnetRequest(TN_WONT, option);
3025                         break;
3026                     }
3027                     break;
3028                   case TN_DONT:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<DONT ");
3031                     switch (option = (unsigned char) buf[++i]) {
3032                       default:
3033                         if (appData.debugMode)
3034                           fprintf(debugFP, "%d ", option);
3035                         /* Whatever this is, we are already not doing
3036                            it, because we never agree to do anything
3037                            non-default, so according to the protocol
3038                            rules, we don't reply. */
3039                         break;
3040                     }
3041                     break;
3042                   case TN_IAC:
3043                     if (appData.debugMode)
3044                       fprintf(debugFP, "\n<IAC ");
3045                     /* Doubled IAC; pass it through */
3046                     i--;
3047                     break;
3048                   default:
3049                     if (appData.debugMode)
3050                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3051                     /* Drop all other telnet commands on the floor */
3052                     break;
3053                 }
3054                 if (oldi > next_out)
3055                   SendToPlayer(&buf[next_out], oldi - next_out);
3056                 if (++i > next_out)
3057                   next_out = i;
3058                 continue;
3059             }
3060
3061             /* OK, this at least will *usually* work */
3062             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3063                 loggedOn = TRUE;
3064             }
3065
3066             if (loggedOn && !intfSet) {
3067                 if (ics_type == ICS_ICC) {
3068                   snprintf(str, MSG_SIZ,
3069                           "/set-quietly interface %s\n/set-quietly style 12\n",
3070                           programVersion);
3071                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3072                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3073                 } else if (ics_type == ICS_CHESSNET) {
3074                   snprintf(str, MSG_SIZ, "/style 12\n");
3075                 } else {
3076                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3077                   strcat(str, programVersion);
3078                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3079                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3080                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3081 #ifdef WIN32
3082                   strcat(str, "$iset nohighlight 1\n");
3083 #endif
3084                   strcat(str, "$iset lock 1\n$style 12\n");
3085                 }
3086                 SendToICS(str);
3087                 NotifyFrontendLogin();
3088                 intfSet = TRUE;
3089             }
3090
3091             if (started == STARTED_COMMENT) {
3092                 /* Accumulate characters in comment */
3093                 parse[parse_pos++] = buf[i];
3094                 if (buf[i] == '\n') {
3095                     parse[parse_pos] = NULLCHAR;
3096                     if(chattingPartner>=0) {
3097                         char mess[MSG_SIZ];
3098                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3099                         OutputChatMessage(chattingPartner, mess);
3100                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3101                             int p;
3102                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3103                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3104                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3105                                 OutputChatMessage(p, mess);
3106                                 break;
3107                             }
3108                         }
3109                         chattingPartner = -1;
3110                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3111                         collective = 0;
3112                     } else
3113                     if(!suppressKibitz) // [HGM] kibitz
3114                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3115                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3116                         int nrDigit = 0, nrAlph = 0, j;
3117                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3118                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3119                         parse[parse_pos] = NULLCHAR;
3120                         // try to be smart: if it does not look like search info, it should go to
3121                         // ICS interaction window after all, not to engine-output window.
3122                         for(j=0; j<parse_pos; j++) { // count letters and digits
3123                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3124                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3125                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3126                         }
3127                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3128                             int depth=0; float score;
3129                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3130                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3131                                 pvInfoList[forwardMostMove-1].depth = depth;
3132                                 pvInfoList[forwardMostMove-1].score = 100*score;
3133                             }
3134                             OutputKibitz(suppressKibitz, parse);
3135                         } else {
3136                             char tmp[MSG_SIZ];
3137                             if(gameMode == IcsObserving) // restore original ICS messages
3138                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3140                             else
3141                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3142                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3143                             SendToPlayer(tmp, strlen(tmp));
3144                         }
3145                         next_out = i+1; // [HGM] suppress printing in ICS window
3146                     }
3147                     started = STARTED_NONE;
3148                 } else {
3149                     /* Don't match patterns against characters in comment */
3150                     i++;
3151                     continue;
3152                 }
3153             }
3154             if (started == STARTED_CHATTER) {
3155                 if (buf[i] != '\n') {
3156                     /* Don't match patterns against characters in chatter */
3157                     i++;
3158                     continue;
3159                 }
3160                 started = STARTED_NONE;
3161                 if(suppressKibitz) next_out = i+1;
3162             }
3163
3164             /* Kludge to deal with rcmd protocol */
3165             if (firstTime && looking_at(buf, &i, "\001*")) {
3166                 DisplayFatalError(&buf[1], 0, 1);
3167                 continue;
3168             } else {
3169                 firstTime = FALSE;
3170             }
3171
3172             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3173                 ics_type = ICS_ICC;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3180                 ics_type = ICS_FICS;
3181                 ics_prefix = "$";
3182                 if (appData.debugMode)
3183                   fprintf(debugFP, "ics_type %d\n", ics_type);
3184                 continue;
3185             }
3186             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3187                 ics_type = ICS_CHESSNET;
3188                 ics_prefix = "/";
3189                 if (appData.debugMode)
3190                   fprintf(debugFP, "ics_type %d\n", ics_type);
3191                 continue;
3192             }
3193
3194             if (!loggedOn &&
3195                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3196                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3197                  looking_at(buf, &i, "will be \"*\""))) {
3198               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3199               continue;
3200             }
3201
3202             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3203               char buf[MSG_SIZ];
3204               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3205               DisplayIcsInteractionTitle(buf);
3206               have_set_title = TRUE;
3207             }
3208
3209             /* skip finger notes */
3210             if (started == STARTED_NONE &&
3211                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3212                  (buf[i] == '1' && buf[i+1] == '0')) &&
3213                 buf[i+2] == ':' && buf[i+3] == ' ') {
3214               started = STARTED_CHATTER;
3215               i += 3;
3216               continue;
3217             }
3218
3219             oldi = i;
3220             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3221             if(appData.seekGraph) {
3222                 if(soughtPending && MatchSoughtLine(buf+i)) {
3223                     i = strstr(buf+i, "rated") - buf;
3224                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3225                     next_out = leftover_start = i;
3226                     started = STARTED_CHATTER;
3227                     suppressKibitz = TRUE;
3228                     continue;
3229                 }
3230                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3231                         && looking_at(buf, &i, "* ads displayed")) {
3232                     soughtPending = FALSE;
3233                     seekGraphUp = TRUE;
3234                     DrawSeekGraph();
3235                     continue;
3236                 }
3237                 if(appData.autoRefresh) {
3238                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3239                         int s = (ics_type == ICS_ICC); // ICC format differs
3240                         if(seekGraphUp)
3241                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3242                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3243                         looking_at(buf, &i, "*% "); // eat prompt
3244                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i; // suppress
3247                         continue;
3248                     }
3249                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3250                         char *p = star_match[0];
3251                         while(*p) {
3252                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3253                             while(*p && *p++ != ' '); // next
3254                         }
3255                         looking_at(buf, &i, "*% "); // eat prompt
3256                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3257                         next_out = i;
3258                         continue;
3259                     }
3260                 }
3261             }
3262
3263             /* skip formula vars */
3264             if (started == STARTED_NONE &&
3265                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3266               started = STARTED_CHATTER;
3267               i += 3;
3268               continue;
3269             }
3270
3271             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3272             if (appData.autoKibitz && started == STARTED_NONE &&
3273                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3274                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3275                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3276                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3277                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3278                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3279                         suppressKibitz = TRUE;
3280                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3281                         next_out = i;
3282                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3283                                 && (gameMode == IcsPlayingWhite)) ||
3284                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3285                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3286                             started = STARTED_CHATTER; // own kibitz we simply discard
3287                         else {
3288                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3289                             parse_pos = 0; parse[0] = NULLCHAR;
3290                             savingComment = TRUE;
3291                             suppressKibitz = gameMode != IcsObserving ? 2 :
3292                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3293                         }
3294                         continue;
3295                 } else
3296                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3297                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3298                          && atoi(star_match[0])) {
3299                     // suppress the acknowledgements of our own autoKibitz
3300                     char *p;
3301                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3302                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3303                     SendToPlayer(star_match[0], strlen(star_match[0]));
3304                     if(looking_at(buf, &i, "*% ")) // eat prompt
3305                         suppressKibitz = FALSE;
3306                     next_out = i;
3307                     continue;
3308                 }
3309             } // [HGM] kibitz: end of patch
3310
3311             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3312
3313             // [HGM] chat: intercept tells by users for which we have an open chat window
3314             channel = -1;
3315             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3316                                            looking_at(buf, &i, "* whispers:") ||
3317                                            looking_at(buf, &i, "* kibitzes:") ||
3318                                            looking_at(buf, &i, "* shouts:") ||
3319                                            looking_at(buf, &i, "* c-shouts:") ||
3320                                            looking_at(buf, &i, "--> * ") ||
3321                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3322                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3323                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3324                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3325                 int p;
3326                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3327                 chattingPartner = -1; collective = 0;
3328
3329                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3330                 for(p=0; p<MAX_CHAT; p++) {
3331                     collective = 1;
3332                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3333                     talker[0] = '['; strcat(talker, "] ");
3334                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3335                     chattingPartner = p; break;
3336                     }
3337                 } else
3338                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3339                 for(p=0; p<MAX_CHAT; p++) {
3340                     collective = 1;
3341                     if(!strcmp("kibitzes", chatPartner[p])) {
3342                         talker[0] = '['; strcat(talker, "] ");
3343                         chattingPartner = p; break;
3344                     }
3345                 } else
3346                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3347                 for(p=0; p<MAX_CHAT; p++) {
3348                     collective = 1;
3349                     if(!strcmp("whispers", chatPartner[p])) {
3350                         talker[0] = '['; strcat(talker, "] ");
3351                         chattingPartner = p; break;
3352                     }
3353                 } else
3354                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3355                   if(buf[i-8] == '-' && buf[i-3] == 't')
3356                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3357                     collective = 1;
3358                     if(!strcmp("c-shouts", chatPartner[p])) {
3359                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                   if(chattingPartner < 0)
3364                   for(p=0; p<MAX_CHAT; p++) {
3365                     collective = 1;
3366                     if(!strcmp("shouts", chatPartner[p])) {
3367                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3368                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3369                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3370                         chattingPartner = p; break;
3371                     }
3372                   }
3373                 }
3374                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3375                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3376                     talker[0] = 0;
3377                     Colorize(ColorTell, FALSE);
3378                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3379                     collective |= 2;
3380                     chattingPartner = p; break;
3381                 }
3382                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3383                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3384                     started = STARTED_COMMENT;
3385                     parse_pos = 0; parse[0] = NULLCHAR;
3386                     savingComment = 3 + chattingPartner; // counts as TRUE
3387                     if(collective == 3) i = oldi; else {
3388                         suppressKibitz = TRUE;
3389                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3390                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3391                         continue;
3392                     }
3393                 }
3394             } // [HGM] chat: end of patch
3395
3396           backup = i;
3397             if (appData.zippyTalk || appData.zippyPlay) {
3398                 /* [DM] Backup address for color zippy lines */
3399 #if ZIPPY
3400                if (loggedOn == TRUE)
3401                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3402                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3403                        ;
3404 #endif
3405             } // [DM] 'else { ' deleted
3406                 if (
3407                     /* Regular tells and says */
3408                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3409                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3410                     looking_at(buf, &i, "* says: ") ||
3411                     /* Don't color "message" or "messages" output */
3412                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3413                     looking_at(buf, &i, "*. * at *:*: ") ||
3414                     looking_at(buf, &i, "--* (*:*): ") ||
3415                     /* Message notifications (same color as tells) */
3416                     looking_at(buf, &i, "* has left a message ") ||
3417                     looking_at(buf, &i, "* just sent you a message:\n") ||
3418                     /* Whispers and kibitzes */
3419                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3420                     looking_at(buf, &i, "* kibitzes: ") ||
3421                     /* Channel tells */
3422                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3423
3424                   if (tkind == 1 && strchr(star_match[0], ':')) {
3425                       /* Avoid "tells you:" spoofs in channels */
3426                      tkind = 3;
3427                   }
3428                   if (star_match[0][0] == NULLCHAR ||
3429                       strchr(star_match[0], ' ') ||
3430                       (tkind == 3 && strchr(star_match[1], ' '))) {
3431                     /* Reject bogus matches */
3432                     i = oldi;
3433                   } else {
3434                     if (appData.colorize) {
3435                       if (oldi > next_out) {
3436                         SendToPlayer(&buf[next_out], oldi - next_out);
3437                         next_out = oldi;
3438                       }
3439                       switch (tkind) {
3440                       case 1:
3441                         Colorize(ColorTell, FALSE);
3442                         curColor = ColorTell;
3443                         break;
3444                       case 2:
3445                         Colorize(ColorKibitz, FALSE);
3446                         curColor = ColorKibitz;
3447                         break;
3448                       case 3:
3449                         p = strrchr(star_match[1], '(');
3450                         if (p == NULL) {
3451                           p = star_match[1];
3452                         } else {
3453                           p++;
3454                         }
3455                         if (atoi(p) == 1) {
3456                           Colorize(ColorChannel1, FALSE);
3457                           curColor = ColorChannel1;
3458                         } else {
3459                           Colorize(ColorChannel, FALSE);
3460                           curColor = ColorChannel;
3461                         }
3462                         break;
3463                       case 5:
3464                         curColor = ColorNormal;
3465                         break;
3466                       }
3467                     }
3468                     if (started == STARTED_NONE && appData.autoComment &&
3469                         (gameMode == IcsObserving ||
3470                          gameMode == IcsPlayingWhite ||
3471                          gameMode == IcsPlayingBlack)) {
3472                       parse_pos = i - oldi;
3473                       memcpy(parse, &buf[oldi], parse_pos);
3474                       parse[parse_pos] = NULLCHAR;
3475                       started = STARTED_COMMENT;
3476                       savingComment = TRUE;
3477                     } else if(collective != 3) {
3478                       started = STARTED_CHATTER;
3479                       savingComment = FALSE;
3480                     }
3481                     loggedOn = TRUE;
3482                     continue;
3483                   }
3484                 }
3485
3486                 if (looking_at(buf, &i, "* s-shouts: ") ||
3487                     looking_at(buf, &i, "* c-shouts: ")) {
3488                     if (appData.colorize) {
3489                         if (oldi > next_out) {
3490                             SendToPlayer(&buf[next_out], oldi - next_out);
3491                             next_out = oldi;
3492                         }
3493                         Colorize(ColorSShout, FALSE);
3494                         curColor = ColorSShout;
3495                     }
3496                     loggedOn = TRUE;
3497                     started = STARTED_CHATTER;
3498                     continue;
3499                 }
3500
3501                 if (looking_at(buf, &i, "--->")) {
3502                     loggedOn = TRUE;
3503                     continue;
3504                 }
3505
3506                 if (looking_at(buf, &i, "* shouts: ") ||
3507                     looking_at(buf, &i, "--> ")) {
3508                     if (appData.colorize) {
3509                         if (oldi > next_out) {
3510                             SendToPlayer(&buf[next_out], oldi - next_out);
3511                             next_out = oldi;
3512                         }
3513                         Colorize(ColorShout, FALSE);
3514                         curColor = ColorShout;
3515                     }
3516                     loggedOn = TRUE;
3517                     started = STARTED_CHATTER;
3518                     continue;
3519                 }
3520
3521                 if (looking_at( buf, &i, "Challenge:")) {
3522                     if (appData.colorize) {
3523                         if (oldi > next_out) {
3524                             SendToPlayer(&buf[next_out], oldi - next_out);
3525                             next_out = oldi;
3526                         }
3527                         Colorize(ColorChallenge, FALSE);
3528                         curColor = ColorChallenge;
3529                     }
3530                     loggedOn = TRUE;
3531                     continue;
3532                 }
3533
3534                 if (looking_at(buf, &i, "* offers you") ||
3535                     looking_at(buf, &i, "* offers to be") ||
3536                     looking_at(buf, &i, "* would like to") ||
3537                     looking_at(buf, &i, "* requests to") ||
3538                     looking_at(buf, &i, "Your opponent offers") ||
3539                     looking_at(buf, &i, "Your opponent requests")) {
3540
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorRequest, FALSE);
3547                         curColor = ColorRequest;
3548                     }
3549                     continue;
3550                 }
3551
3552                 if (looking_at(buf, &i, "* (*) seeking")) {
3553                     if (appData.colorize) {
3554                         if (oldi > next_out) {
3555                             SendToPlayer(&buf[next_out], oldi - next_out);
3556                             next_out = oldi;
3557                         }
3558                         Colorize(ColorSeek, FALSE);
3559                         curColor = ColorSeek;
3560                     }
3561                     continue;
3562             }
3563
3564           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3565
3566             if (looking_at(buf, &i, "\\   ")) {
3567                 if (prevColor != ColorNormal) {
3568                     if (oldi > next_out) {
3569                         SendToPlayer(&buf[next_out], oldi - next_out);
3570                         next_out = oldi;
3571                     }
3572                     Colorize(prevColor, TRUE);
3573                     curColor = prevColor;
3574                 }
3575                 if (savingComment) {
3576                     parse_pos = i - oldi;
3577                     memcpy(parse, &buf[oldi], parse_pos);
3578                     parse[parse_pos] = NULLCHAR;
3579                     started = STARTED_COMMENT;
3580                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3581                         chattingPartner = savingComment - 3; // kludge to remember the box
3582                 } else {
3583                     started = STARTED_CHATTER;
3584                 }
3585                 continue;
3586             }
3587
3588             if (looking_at(buf, &i, "Black Strength :") ||
3589                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3590                 looking_at(buf, &i, "<10>") ||
3591                 looking_at(buf, &i, "#@#")) {
3592                 /* Wrong board style */
3593                 loggedOn = TRUE;
3594                 SendToICS(ics_prefix);
3595                 SendToICS("set style 12\n");
3596                 SendToICS(ics_prefix);
3597                 SendToICS("refresh\n");
3598                 continue;
3599             }
3600
3601             if (looking_at(buf, &i, "login:")) {
3602               if (!have_sent_ICS_logon) {
3603                 if(ICSInitScript())
3604                   have_sent_ICS_logon = 1;
3605                 else // no init script was found
3606                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3607               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3608                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3609               }
3610                 continue;
3611             }
3612
3613             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3614                 (looking_at(buf, &i, "\n<12> ") ||
3615                  looking_at(buf, &i, "<12> "))) {
3616                 loggedOn = TRUE;
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_BOARD;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3627                 looking_at(buf, &i, "<b1> ")) {
3628                 if (oldi > next_out) {
3629                     SendToPlayer(&buf[next_out], oldi - next_out);
3630                 }
3631                 next_out = i;
3632                 started = STARTED_HOLDINGS;
3633                 parse_pos = 0;
3634                 continue;
3635             }
3636
3637             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3638                 loggedOn = TRUE;
3639                 /* Header for a move list -- first line */
3640
3641                 switch (ics_getting_history) {
3642                   case H_FALSE:
3643                     switch (gameMode) {
3644                       case IcsIdle:
3645                       case BeginningOfGame:
3646                         /* User typed "moves" or "oldmoves" while we
3647                            were idle.  Pretend we asked for these
3648                            moves and soak them up so user can step
3649                            through them and/or save them.
3650                            */
3651                         Reset(FALSE, TRUE);
3652                         gameMode = IcsObserving;
3653                         ModeHighlight();
3654                         ics_gamenum = -1;
3655                         ics_getting_history = H_GOT_UNREQ_HEADER;
3656                         break;
3657                       case EditGame: /*?*/
3658                       case EditPosition: /*?*/
3659                         /* Should above feature work in these modes too? */
3660                         /* For now it doesn't */
3661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3662                         break;
3663                       default:
3664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3665                         break;
3666                     }
3667                     break;
3668                   case H_REQUESTED:
3669                     /* Is this the right one? */
3670                     if (gameInfo.white && gameInfo.black &&
3671                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3672                         strcmp(gameInfo.black, star_match[2]) == 0) {
3673                         /* All is well */
3674                         ics_getting_history = H_GOT_REQ_HEADER;
3675                     }
3676                     break;
3677                   case H_GOT_REQ_HEADER:
3678                   case H_GOT_UNREQ_HEADER:
3679                   case H_GOT_UNWANTED_HEADER:
3680                   case H_GETTING_MOVES:
3681                     /* Should not happen */
3682                     DisplayError(_("Error gathering move list: two headers"), 0);
3683                     ics_getting_history = H_FALSE;
3684                     break;
3685                 }
3686
3687                 /* Save player ratings into gameInfo if needed */
3688                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3689                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3690                     (gameInfo.whiteRating == -1 ||
3691                      gameInfo.blackRating == -1)) {
3692
3693                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3694                     gameInfo.blackRating = string_to_rating(star_match[3]);
3695                     if (appData.debugMode)
3696                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3697                               gameInfo.whiteRating, gameInfo.blackRating);
3698                 }
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i,
3703               "* * match, initial time: * minute*, increment: * second")) {
3704                 /* Header for a move list -- second line */
3705                 /* Initial board will follow if this is a wild game */
3706                 if (gameInfo.event != NULL) free(gameInfo.event);
3707                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3708                 gameInfo.event = StrSave(str);
3709                 /* [HGM] we switched variant. Translate boards if needed. */
3710                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3711                 continue;
3712             }
3713
3714             if (looking_at(buf, &i, "Move  ")) {
3715                 /* Beginning of a move list */
3716                 switch (ics_getting_history) {
3717                   case H_FALSE:
3718                     /* Normally should not happen */
3719                     /* Maybe user hit reset while we were parsing */
3720                     break;
3721                   case H_REQUESTED:
3722                     /* Happens if we are ignoring a move list that is not
3723                      * the one we just requested.  Common if the user
3724                      * tries to observe two games without turning off
3725                      * getMoveList */
3726                     break;
3727                   case H_GETTING_MOVES:
3728                     /* Should not happen */
3729                     DisplayError(_("Error gathering move list: nested"), 0);
3730                     ics_getting_history = H_FALSE;
3731                     break;
3732                   case H_GOT_REQ_HEADER:
3733                     ics_getting_history = H_GETTING_MOVES;
3734                     started = STARTED_MOVES;
3735                     parse_pos = 0;
3736                     if (oldi > next_out) {
3737                         SendToPlayer(&buf[next_out], oldi - next_out);
3738                     }
3739                     break;
3740                   case H_GOT_UNREQ_HEADER:
3741                     ics_getting_history = H_GETTING_MOVES;
3742                     started = STARTED_MOVES_NOHIDE;
3743                     parse_pos = 0;
3744                     break;
3745                   case H_GOT_UNWANTED_HEADER:
3746                     ics_getting_history = H_FALSE;
3747                     break;
3748                 }
3749                 continue;
3750             }
3751
3752             if (looking_at(buf, &i, "% ") ||
3753                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3754                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3755                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3756                     soughtPending = FALSE;
3757                     seekGraphUp = TRUE;
3758                     DrawSeekGraph();
3759                 }
3760                 if(suppressKibitz) next_out = i;
3761                 savingComment = FALSE;
3762                 suppressKibitz = 0;
3763                 switch (started) {
3764                   case STARTED_MOVES:
3765                   case STARTED_MOVES_NOHIDE:
3766                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3767                     parse[parse_pos + i - oldi] = NULLCHAR;
3768                     ParseGameHistory(parse);
3769 #if ZIPPY
3770                     if (appData.zippyPlay && first.initDone) {
3771                         FeedMovesToProgram(&first, forwardMostMove);
3772                         if (gameMode == IcsPlayingWhite) {
3773                             if (WhiteOnMove(forwardMostMove)) {
3774                                 if (first.sendTime) {
3775                                   if (first.useColors) {
3776                                     SendToProgram("black\n", &first);
3777                                   }
3778                                   SendTimeRemaining(&first, TRUE);
3779                                 }
3780                                 if (first.useColors) {
3781                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3782                                 }
3783                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3784                                 first.maybeThinking = TRUE;
3785                             } else {
3786                                 if (first.usePlayother) {
3787                                   if (first.sendTime) {
3788                                     SendTimeRemaining(&first, TRUE);
3789                                   }
3790                                   SendToProgram("playother\n", &first);
3791                                   firstMove = FALSE;
3792                                 } else {
3793                                   firstMove = TRUE;
3794                                 }
3795                             }
3796                         } else if (gameMode == IcsPlayingBlack) {
3797                             if (!WhiteOnMove(forwardMostMove)) {
3798                                 if (first.sendTime) {
3799                                   if (first.useColors) {
3800                                     SendToProgram("white\n", &first);
3801                                   }
3802                                   SendTimeRemaining(&first, FALSE);
3803                                 }
3804                                 if (first.useColors) {
3805                                   SendToProgram("black\n", &first);
3806                                 }
3807                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3808                                 first.maybeThinking = TRUE;
3809                             } else {
3810                                 if (first.usePlayother) {
3811                                   if (first.sendTime) {
3812                                     SendTimeRemaining(&first, FALSE);
3813                                   }
3814                                   SendToProgram("playother\n", &first);
3815                                   firstMove = FALSE;
3816                                 } else {
3817                                   firstMove = TRUE;
3818                                 }
3819                             }
3820                         }
3821                     }
3822 #endif
3823                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3824                         /* Moves came from oldmoves or moves command
3825                            while we weren't doing anything else.
3826                            */
3827                         currentMove = forwardMostMove;
3828                         ClearHighlights();/*!!could figure this out*/
3829                         flipView = appData.flipView;
3830                         DrawPosition(TRUE, boards[currentMove]);
3831                         DisplayBothClocks();
3832                         snprintf(str, MSG_SIZ, "%s %s %s",
3833                                 gameInfo.white, _("vs."),  gameInfo.black);
3834                         DisplayTitle(str);
3835                         gameMode = IcsIdle;
3836                     } else {
3837                         /* Moves were history of an active game */
3838                         if (gameInfo.resultDetails != NULL) {
3839                             free(gameInfo.resultDetails);
3840                             gameInfo.resultDetails = NULL;
3841                         }
3842                     }
3843                     HistorySet(parseList, backwardMostMove,
3844                                forwardMostMove, currentMove-1);
3845                     DisplayMove(currentMove - 1);
3846                     if (started == STARTED_MOVES) next_out = i;
3847                     started = STARTED_NONE;
3848                     ics_getting_history = H_FALSE;
3849                     break;
3850
3851                   case STARTED_OBSERVE:
3852                     started = STARTED_NONE;
3853                     SendToICS(ics_prefix);
3854                     SendToICS("refresh\n");
3855                     break;
3856
3857                   default:
3858                     break;
3859                 }
3860                 if(bookHit) { // [HGM] book: simulate book reply
3861                     static char bookMove[MSG_SIZ]; // a bit generous?
3862
3863                     programStats.nodes = programStats.depth = programStats.time =
3864                     programStats.score = programStats.got_only_move = 0;
3865                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3866
3867                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3868                     strcat(bookMove, bookHit);
3869                     HandleMachineMove(bookMove, &first);
3870                 }
3871                 continue;
3872             }
3873
3874             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3875                  started == STARTED_HOLDINGS ||
3876                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3877                 /* Accumulate characters in move list or board */
3878                 parse[parse_pos++] = buf[i];
3879             }
3880
3881             /* Start of game messages.  Mostly we detect start of game
3882                when the first board image arrives.  On some versions
3883                of the ICS, though, we need to do a "refresh" after starting
3884                to observe in order to get the current board right away. */
3885             if (looking_at(buf, &i, "Adding game * to observation list")) {
3886                 started = STARTED_OBSERVE;
3887                 continue;
3888             }
3889
3890             /* Handle auto-observe */
3891             if (appData.autoObserve &&
3892                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3893                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3894                 char *player;
3895                 /* Choose the player that was highlighted, if any. */
3896                 if (star_match[0][0] == '\033' ||
3897                     star_match[1][0] != '\033') {
3898                     player = star_match[0];
3899                 } else {
3900                     player = star_match[2];
3901                 }
3902                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3903                         ics_prefix, StripHighlightAndTitle(player));
3904                 SendToICS(str);
3905
3906                 /* Save ratings from notify string */
3907                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3908                 player1Rating = string_to_rating(star_match[1]);
3909                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3910                 player2Rating = string_to_rating(star_match[3]);
3911
3912                 if (appData.debugMode)
3913                   fprintf(debugFP,
3914                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3915                           player1Name, player1Rating,
3916                           player2Name, player2Rating);
3917
3918                 continue;
3919             }
3920
3921             /* Deal with automatic examine mode after a game,
3922                and with IcsObserving -> IcsExamining transition */
3923             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3924                 looking_at(buf, &i, "has made you an examiner of game *")) {
3925
3926                 int gamenum = atoi(star_match[0]);
3927                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3928                     gamenum == ics_gamenum) {
3929                     /* We were already playing or observing this game;
3930                        no need to refetch history */
3931                     gameMode = IcsExamining;
3932                     if (pausing) {
3933                         pauseExamForwardMostMove = forwardMostMove;
3934                     } else if (currentMove < forwardMostMove) {
3935                         ForwardInner(forwardMostMove);
3936                     }
3937                 } else {
3938                     /* I don't think this case really can happen */
3939                     SendToICS(ics_prefix);
3940                     SendToICS("refresh\n");
3941                 }
3942                 continue;
3943             }
3944
3945             /* Error messages */
3946 //          if (ics_user_moved) {
3947             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3948                 if (looking_at(buf, &i, "Illegal move") ||
3949                     looking_at(buf, &i, "Not a legal move") ||
3950                     looking_at(buf, &i, "Your king is in check") ||
3951                     looking_at(buf, &i, "It isn't your turn") ||
3952                     looking_at(buf, &i, "It is not your move")) {
3953                     /* Illegal move */
3954                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3955                         currentMove = forwardMostMove-1;
3956                         DisplayMove(currentMove - 1); /* before DMError */
3957                         DrawPosition(FALSE, boards[currentMove]);
3958                         SwitchClocks(forwardMostMove-1); // [HGM] race
3959                         DisplayBothClocks();
3960                     }
3961                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3962                     ics_user_moved = 0;
3963                     continue;
3964                 }
3965             }
3966
3967             if (looking_at(buf, &i, "still have time") ||
3968                 looking_at(buf, &i, "not out of time") ||
3969                 looking_at(buf, &i, "either player is out of time") ||
3970                 looking_at(buf, &i, "has timeseal; checking")) {
3971                 /* We must have called his flag a little too soon */
3972                 whiteFlag = blackFlag = FALSE;
3973                 continue;
3974             }
3975
3976             if (looking_at(buf, &i, "added * seconds to") ||
3977                 looking_at(buf, &i, "seconds were added to")) {
3978                 /* Update the clocks */
3979                 SendToICS(ics_prefix);
3980                 SendToICS("refresh\n");
3981                 continue;
3982             }
3983
3984             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3985                 ics_clock_paused = TRUE;
3986                 StopClocks();
3987                 continue;
3988             }
3989
3990             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3991                 ics_clock_paused = FALSE;
3992                 StartClocks();
3993                 continue;
3994             }
3995
3996             /* Grab player ratings from the Creating: message.
3997                Note we have to check for the special case when
3998                the ICS inserts things like [white] or [black]. */
3999             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4000                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4001                 /* star_matches:
4002                    0    player 1 name (not necessarily white)
4003                    1    player 1 rating
4004                    2    empty, white, or black (IGNORED)
4005                    3    player 2 name (not necessarily black)
4006                    4    player 2 rating
4007
4008                    The names/ratings are sorted out when the game
4009                    actually starts (below).
4010                 */
4011                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4012                 player1Rating = string_to_rating(star_match[1]);
4013                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4014                 player2Rating = string_to_rating(star_match[4]);
4015
4016                 if (appData.debugMode)
4017                   fprintf(debugFP,
4018                           "Ratings from 'Creating:' %s %d, %s %d\n",
4019                           player1Name, player1Rating,
4020                           player2Name, player2Rating);
4021
4022                 continue;
4023             }
4024
4025             /* Improved generic start/end-of-game messages */
4026             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4027                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4028                 /* If tkind == 0: */
4029                 /* star_match[0] is the game number */
4030                 /*           [1] is the white player's name */
4031                 /*           [2] is the black player's name */
4032                 /* For end-of-game: */
4033                 /*           [3] is the reason for the game end */
4034                 /*           [4] is a PGN end game-token, preceded by " " */
4035                 /* For start-of-game: */
4036                 /*           [3] begins with "Creating" or "Continuing" */
4037                 /*           [4] is " *" or empty (don't care). */
4038                 int gamenum = atoi(star_match[0]);
4039                 char *whitename, *blackname, *why, *endtoken;
4040                 ChessMove endtype = EndOfFile;
4041
4042                 if (tkind == 0) {
4043                   whitename = star_match[1];
4044                   blackname = star_match[2];
4045                   why = star_match[3];
4046                   endtoken = star_match[4];
4047                 } else {
4048                   whitename = star_match[1];
4049                   blackname = star_match[3];
4050                   why = star_match[5];
4051                   endtoken = star_match[6];
4052                 }
4053
4054                 /* Game start messages */
4055                 if (strncmp(why, "Creating ", 9) == 0 ||
4056                     strncmp(why, "Continuing ", 11) == 0) {
4057                     gs_gamenum = gamenum;
4058                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4059                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4060                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4061 #if ZIPPY
4062                     if (appData.zippyPlay) {
4063                         ZippyGameStart(whitename, blackname);
4064                     }
4065 #endif /*ZIPPY*/
4066                     partnerBoardValid = FALSE; // [HGM] bughouse
4067                     continue;
4068                 }
4069
4070                 /* Game end messages */
4071                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4072                     ics_gamenum != gamenum) {
4073                     continue;
4074                 }
4075                 while (endtoken[0] == ' ') endtoken++;
4076                 switch (endtoken[0]) {
4077                   case '*':
4078                   default:
4079                     endtype = GameUnfinished;
4080                     break;
4081                   case '0':
4082                     endtype = BlackWins;
4083                     break;
4084                   case '1':
4085                     if (endtoken[1] == '/')
4086                       endtype = GameIsDrawn;
4087                     else
4088                       endtype = WhiteWins;
4089                     break;
4090                 }
4091                 GameEnds(endtype, why, GE_ICS);
4092 #if ZIPPY
4093                 if (appData.zippyPlay && first.initDone) {
4094                     ZippyGameEnd(endtype, why);
4095                     if (first.pr == NoProc) {
4096                       /* Start the next process early so that we'll
4097                          be ready for the next challenge */
4098                       StartChessProgram(&first);
4099                     }
4100                     /* Send "new" early, in case this command takes
4101                        a long time to finish, so that we'll be ready
4102                        for the next challenge. */
4103                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4104                     Reset(TRUE, TRUE);
4105                 }
4106 #endif /*ZIPPY*/
4107                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4108                 continue;
4109             }
4110
4111             if (looking_at(buf, &i, "Removing game * from observation") ||
4112                 looking_at(buf, &i, "no longer observing game *") ||
4113                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4114                 if (gameMode == IcsObserving &&
4115                     atoi(star_match[0]) == ics_gamenum)
4116                   {
4117                       /* icsEngineAnalyze */
4118                       if (appData.icsEngineAnalyze) {
4119                             ExitAnalyzeMode();
4120                             ModeHighlight();
4121                       }
4122                       StopClocks();
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             if (looking_at(buf, &i, "no longer examining game *")) {
4131                 if (gameMode == IcsExamining &&
4132                     atoi(star_match[0]) == ics_gamenum)
4133                   {
4134                       gameMode = IcsIdle;
4135                       ics_gamenum = -1;
4136                       ics_user_moved = FALSE;
4137                   }
4138                 continue;
4139             }
4140
4141             /* Advance leftover_start past any newlines we find,
4142                so only partial lines can get reparsed */
4143             if (looking_at(buf, &i, "\n")) {
4144                 prevColor = curColor;
4145                 if (curColor != ColorNormal) {
4146                     if (oldi > next_out) {
4147                         SendToPlayer(&buf[next_out], oldi - next_out);
4148                         next_out = oldi;
4149                     }
4150                     Colorize(ColorNormal, FALSE);
4151                     curColor = ColorNormal;
4152                 }
4153                 if (started == STARTED_BOARD) {
4154                     started = STARTED_NONE;
4155                     parse[parse_pos] = NULLCHAR;
4156                     ParseBoard12(parse);
4157                     ics_user_moved = 0;
4158
4159                     /* Send premove here */
4160                     if (appData.premove) {
4161                       char str[MSG_SIZ];
4162                       if (currentMove == 0 &&
4163                           gameMode == IcsPlayingWhite &&
4164                           appData.premoveWhite) {
4165                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4166                         if (appData.debugMode)
4167                           fprintf(debugFP, "Sending premove:\n");
4168                         SendToICS(str);
4169                       } else if (currentMove == 1 &&
4170                                  gameMode == IcsPlayingBlack &&
4171                                  appData.premoveBlack) {
4172                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4173                         if (appData.debugMode)
4174                           fprintf(debugFP, "Sending premove:\n");
4175                         SendToICS(str);
4176                       } else if (gotPremove) {
4177                         int oldFMM = forwardMostMove;
4178                         gotPremove = 0;
4179                         ClearPremoveHighlights();
4180                         if (appData.debugMode)
4181                           fprintf(debugFP, "Sending premove:\n");
4182                           UserMoveEvent(premoveFromX, premoveFromY,
4183                                         premoveToX, premoveToY,
4184                                         premovePromoChar);
4185                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4186                           if(moveList[oldFMM-1][1] != '@')
4187                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4188                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189                           else // (drop)
4190                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4191                         }
4192                       }
4193                     }
4194
4195                     /* Usually suppress following prompt */
4196                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4197                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4198                         if (looking_at(buf, &i, "*% ")) {
4199                             savingComment = FALSE;
4200                             suppressKibitz = 0;
4201                         }
4202                     }
4203                     next_out = i;
4204                 } else if (started == STARTED_HOLDINGS) {
4205                     int gamenum;
4206                     char new_piece[MSG_SIZ];
4207                     started = STARTED_NONE;
4208                     parse[parse_pos] = NULLCHAR;
4209                     if (appData.debugMode)
4210                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4211                                                         parse, currentMove);
4212                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4213                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4214                         if (gameInfo.variant == VariantNormal) {
4215                           /* [HGM] We seem to switch variant during a game!
4216                            * Presumably no holdings were displayed, so we have
4217                            * to move the position two files to the right to
4218                            * create room for them!
4219                            */
4220                           VariantClass newVariant;
4221                           switch(gameInfo.boardWidth) { // base guess on board width
4222                                 case 9:  newVariant = VariantShogi; break;
4223                                 case 10: newVariant = VariantGreat; break;
4224                                 default: newVariant = VariantCrazyhouse; break;
4225                           }
4226                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4227                           /* Get a move list just to see the header, which
4228                              will tell us whether this is really bug or zh */
4229                           if (ics_getting_history == H_FALSE) {
4230                             ics_getting_history = H_REQUESTED;
4231                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4232                             SendToICS(str);
4233                           }
4234                         }
4235                         new_piece[0] = NULLCHAR;
4236                         sscanf(parse, "game %d white [%s black [%s <- %s",
4237                                &gamenum, white_holding, black_holding,
4238                                new_piece);
4239                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4240                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4241                         /* [HGM] copy holdings to board holdings area */
4242                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4243                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4244                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4245 #if ZIPPY
4246                         if (appData.zippyPlay && first.initDone) {
4247                             ZippyHoldings(white_holding, black_holding,
4248                                           new_piece);
4249                         }
4250 #endif /*ZIPPY*/
4251                         if (tinyLayout || smallLayout) {
4252                             char wh[16], bh[16];
4253                             PackHolding(wh, white_holding);
4254                             PackHolding(bh, black_holding);
4255                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4256                                     gameInfo.white, gameInfo.black);
4257                         } else {
4258                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4259                                     gameInfo.white, white_holding, _("vs."),
4260                                     gameInfo.black, black_holding);
4261                         }
4262                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4263                         DrawPosition(FALSE, boards[currentMove]);
4264                         DisplayTitle(str);
4265                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4266                         sscanf(parse, "game %d white [%s black [%s <- %s",
4267                                &gamenum, white_holding, black_holding,
4268                                new_piece);
4269                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4270                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4271                         /* [HGM] copy holdings to partner-board holdings area */
4272                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4273                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4274                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4275                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4276                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4277                       }
4278                     }
4279                     /* Suppress following prompt */
4280                     if (looking_at(buf, &i, "*% ")) {
4281                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4282                         savingComment = FALSE;
4283                         suppressKibitz = 0;
4284                     }
4285                     next_out = i;
4286                 }
4287                 continue;
4288             }
4289
4290             i++;                /* skip unparsed character and loop back */
4291         }
4292
4293         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4294 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4295 //          SendToPlayer(&buf[next_out], i - next_out);
4296             started != STARTED_HOLDINGS && leftover_start > next_out) {
4297             SendToPlayer(&buf[next_out], leftover_start - next_out);
4298             next_out = i;
4299         }
4300
4301         leftover_len = buf_len - leftover_start;
4302         /* if buffer ends with something we couldn't parse,
4303            reparse it after appending the next read */
4304
4305     } else if (count == 0) {
4306         RemoveInputSource(isr);
4307         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4308     } else {
4309         DisplayFatalError(_("Error reading from ICS"), error, 1);
4310     }
4311 }
4312
4313
4314 /* Board style 12 looks like this:
4315
4316    <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
4317
4318  * The "<12> " is stripped before it gets to this routine.  The two
4319  * trailing 0's (flip state and clock ticking) are later addition, and
4320  * some chess servers may not have them, or may have only the first.
4321  * Additional trailing fields may be added in the future.
4322  */
4323
4324 #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"
4325
4326 #define RELATION_OBSERVING_PLAYED    0
4327 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4328 #define RELATION_PLAYING_MYMOVE      1
4329 #define RELATION_PLAYING_NOTMYMOVE  -1
4330 #define RELATION_EXAMINING           2
4331 #define RELATION_ISOLATED_BOARD     -3
4332 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4333
4334 void
4335 ParseBoard12 (char *string)
4336 {
4337 #if ZIPPY
4338     int i, takeback;
4339     char *bookHit = NULL; // [HGM] book
4340 #endif
4341     GameMode newGameMode;
4342     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4343     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4344     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4345     char to_play, board_chars[200];
4346     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4347     char black[32], white[32];
4348     Board board;
4349     int prevMove = currentMove;
4350     int ticking = 2;
4351     ChessMove moveType;
4352     int fromX, fromY, toX, toY;
4353     char promoChar;
4354     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4355     Boolean weird = FALSE, reqFlag = FALSE;
4356
4357     fromX = fromY = toX = toY = -1;
4358
4359     newGame = FALSE;
4360
4361     if (appData.debugMode)
4362       fprintf(debugFP, "Parsing board: %s\n", string);
4363
4364     move_str[0] = NULLCHAR;
4365     elapsed_time[0] = NULLCHAR;
4366     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4367         int  i = 0, j;
4368         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4369             if(string[i] == ' ') { ranks++; files = 0; }
4370             else files++;
4371             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4372             i++;
4373         }
4374         for(j = 0; j <i; j++) board_chars[j] = string[j];
4375         board_chars[i] = '\0';
4376         string += i + 1;
4377     }
4378     n = sscanf(string, PATTERN, &to_play, &double_push,
4379                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4380                &gamenum, white, black, &relation, &basetime, &increment,
4381                &white_stren, &black_stren, &white_time, &black_time,
4382                &moveNum, str, elapsed_time, move_str, &ics_flip,
4383                &ticking);
4384
4385     if (n < 21) {
4386         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4387         DisplayError(str, 0);
4388         return;
4389     }
4390
4391     /* Convert the move number to internal form */
4392     moveNum = (moveNum - 1) * 2;
4393     if (to_play == 'B') moveNum++;
4394     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4395       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4396                         0, 1);
4397       return;
4398     }
4399
4400     switch (relation) {
4401       case RELATION_OBSERVING_PLAYED:
4402       case RELATION_OBSERVING_STATIC:
4403         if (gamenum == -1) {
4404             /* Old ICC buglet */
4405             relation = RELATION_OBSERVING_STATIC;
4406         }
4407         newGameMode = IcsObserving;
4408         break;
4409       case RELATION_PLAYING_MYMOVE:
4410       case RELATION_PLAYING_NOTMYMOVE:
4411         newGameMode =
4412           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4413             IcsPlayingWhite : IcsPlayingBlack;
4414         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4415         break;
4416       case RELATION_EXAMINING:
4417         newGameMode = IcsExamining;
4418         break;
4419       case RELATION_ISOLATED_BOARD:
4420       default:
4421         /* Just display this board.  If user was doing something else,
4422            we will forget about it until the next board comes. */
4423         newGameMode = IcsIdle;
4424         break;
4425       case RELATION_STARTING_POSITION:
4426         newGameMode = gameMode;
4427         break;
4428     }
4429
4430     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4431         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4432          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4433       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4434       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4435       static int lastBgGame = -1;
4436       char *toSqr;
4437       for (k = 0; k < ranks; k++) {
4438         for (j = 0; j < files; j++)
4439           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4440         if(gameInfo.holdingsWidth > 1) {
4441              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4442              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4443         }
4444       }
4445       CopyBoard(partnerBoard, board);
4446       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4447         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4448         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4449       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4450       if(toSqr = strchr(str, '-')) {
4451         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4452         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4453       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4454       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4455       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4456       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4457       if(twoBoards) {
4458           DisplayWhiteClock(white_time*fac, to_play == 'W');
4459           DisplayBlackClock(black_time*fac, to_play != 'W');
4460           activePartner = to_play;
4461           if(gamenum != lastBgGame) {
4462               char buf[MSG_SIZ];
4463               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4464               DisplayTitle(buf);
4465           }
4466           lastBgGame = gamenum;
4467           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4468                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4469       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4470                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4471       if(!twoBoards) DisplayMessage(partnerStatus, "");
4472         partnerBoardValid = TRUE;
4473       return;
4474     }
4475
4476     if(appData.dualBoard && appData.bgObserve) {
4477         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4478             SendToICS(ics_prefix), SendToICS("pobserve\n");
4479         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4480             char buf[MSG_SIZ];
4481             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4482             SendToICS(buf);
4483         }
4484     }
4485
4486     /* Modify behavior for initial board display on move listing
4487        of wild games.
4488        */
4489     switch (ics_getting_history) {
4490       case H_FALSE:
4491       case H_REQUESTED:
4492         break;
4493       case H_GOT_REQ_HEADER:
4494       case H_GOT_UNREQ_HEADER:
4495         /* This is the initial position of the current game */
4496         gamenum = ics_gamenum;
4497         moveNum = 0;            /* old ICS bug workaround */
4498         if (to_play == 'B') {
4499           startedFromSetupPosition = TRUE;
4500           blackPlaysFirst = TRUE;
4501           moveNum = 1;
4502           if (forwardMostMove == 0) forwardMostMove = 1;
4503           if (backwardMostMove == 0) backwardMostMove = 1;
4504           if (currentMove == 0) currentMove = 1;
4505         }
4506         newGameMode = gameMode;
4507         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4508         break;
4509       case H_GOT_UNWANTED_HEADER:
4510         /* This is an initial board that we don't want */
4511         return;
4512       case H_GETTING_MOVES:
4513         /* Should not happen */
4514         DisplayError(_("Error gathering move list: extra board"), 0);
4515         ics_getting_history = H_FALSE;
4516         return;
4517     }
4518
4519    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4520                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4521                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4522      /* [HGM] We seem to have switched variant unexpectedly
4523       * Try to guess new variant from board size
4524       */
4525           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4526           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4527           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4528           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4529           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4530           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4531           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4532           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4533           /* Get a move list just to see the header, which
4534              will tell us whether this is really bug or zh */
4535           if (ics_getting_history == H_FALSE) {
4536             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539           }
4540     }
4541
4542     /* Take action if this is the first board of a new game, or of a
4543        different game than is currently being displayed.  */
4544     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4545         relation == RELATION_ISOLATED_BOARD) {
4546
4547         /* Forget the old game and get the history (if any) of the new one */
4548         if (gameMode != BeginningOfGame) {
4549           Reset(TRUE, TRUE);
4550         }
4551         newGame = TRUE;
4552         if (appData.autoRaiseBoard) BoardToTop();
4553         prevMove = -3;
4554         if (gamenum == -1) {
4555             newGameMode = IcsIdle;
4556         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4557                    appData.getMoveList && !reqFlag) {
4558             /* Need to get game history */
4559             ics_getting_history = H_REQUESTED;
4560             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561             SendToICS(str);
4562         }
4563
4564         /* Initially flip the board to have black on the bottom if playing
4565            black or if the ICS flip flag is set, but let the user change
4566            it with the Flip View button. */
4567         flipView = appData.autoFlipView ?
4568           (newGameMode == IcsPlayingBlack) || ics_flip :
4569           appData.flipView;
4570
4571         /* Done with values from previous mode; copy in new ones */
4572         gameMode = newGameMode;
4573         ModeHighlight();
4574         ics_gamenum = gamenum;
4575         if (gamenum == gs_gamenum) {
4576             int klen = strlen(gs_kind);
4577             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4578             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4579             gameInfo.event = StrSave(str);
4580         } else {
4581             gameInfo.event = StrSave("ICS game");
4582         }
4583         gameInfo.site = StrSave(appData.icsHost);
4584         gameInfo.date = PGNDate();
4585         gameInfo.round = StrSave("-");
4586         gameInfo.white = StrSave(white);
4587         gameInfo.black = StrSave(black);
4588         timeControl = basetime * 60 * 1000;
4589         timeControl_2 = 0;
4590         timeIncrement = increment * 1000;
4591         movesPerSession = 0;
4592         gameInfo.timeControl = TimeControlTagValue();
4593         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4594   if (appData.debugMode) {
4595     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4596     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4597     setbuf(debugFP, NULL);
4598   }
4599
4600         gameInfo.outOfBook = NULL;
4601
4602         /* Do we have the ratings? */
4603         if (strcmp(player1Name, white) == 0 &&
4604             strcmp(player2Name, black) == 0) {
4605             if (appData.debugMode)
4606               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4607                       player1Rating, player2Rating);
4608             gameInfo.whiteRating = player1Rating;
4609             gameInfo.blackRating = player2Rating;
4610         } else if (strcmp(player2Name, white) == 0 &&
4611                    strcmp(player1Name, black) == 0) {
4612             if (appData.debugMode)
4613               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4614                       player2Rating, player1Rating);
4615             gameInfo.whiteRating = player2Rating;
4616             gameInfo.blackRating = player1Rating;
4617         }
4618         player1Name[0] = player2Name[0] = NULLCHAR;
4619
4620         /* Silence shouts if requested */
4621         if (appData.quietPlay &&
4622             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4623             SendToICS(ics_prefix);
4624             SendToICS("set shout 0\n");
4625         }
4626     }
4627
4628     /* Deal with midgame name changes */
4629     if (!newGame) {
4630         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4631             if (gameInfo.white) free(gameInfo.white);
4632             gameInfo.white = StrSave(white);
4633         }
4634         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4635             if (gameInfo.black) free(gameInfo.black);
4636             gameInfo.black = StrSave(black);
4637         }
4638     }
4639
4640     /* Throw away game result if anything actually changes in examine mode */
4641     if (gameMode == IcsExamining && !newGame) {
4642         gameInfo.result = GameUnfinished;
4643         if (gameInfo.resultDetails != NULL) {
4644             free(gameInfo.resultDetails);
4645             gameInfo.resultDetails = NULL;
4646         }
4647     }
4648
4649     /* In pausing && IcsExamining mode, we ignore boards coming
4650        in if they are in a different variation than we are. */
4651     if (pauseExamInvalid) return;
4652     if (pausing && gameMode == IcsExamining) {
4653         if (moveNum <= pauseExamForwardMostMove) {
4654             pauseExamInvalid = TRUE;
4655             forwardMostMove = pauseExamForwardMostMove;
4656             return;
4657         }
4658     }
4659
4660   if (appData.debugMode) {
4661     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4662   }
4663     /* Parse the board */
4664     for (k = 0; k < ranks; k++) {
4665       for (j = 0; j < files; j++)
4666         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4667       if(gameInfo.holdingsWidth > 1) {
4668            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4669            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4670       }
4671     }
4672     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4673       board[5][BOARD_RGHT+1] = WhiteAngel;
4674       board[6][BOARD_RGHT+1] = WhiteMarshall;
4675       board[1][0] = BlackMarshall;
4676       board[2][0] = BlackAngel;
4677       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4678     }
4679     CopyBoard(boards[moveNum], board);
4680     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4681     if (moveNum == 0) {
4682         startedFromSetupPosition =
4683           !CompareBoards(board, initialPosition);
4684         if(startedFromSetupPosition)
4685             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4686     }
4687
4688     /* [HGM] Set castling rights. Take the outermost Rooks,
4689        to make it also work for FRC opening positions. Note that board12
4690        is really defective for later FRC positions, as it has no way to
4691        indicate which Rook can castle if they are on the same side of King.
4692        For the initial position we grant rights to the outermost Rooks,
4693        and remember thos rights, and we then copy them on positions
4694        later in an FRC game. This means WB might not recognize castlings with
4695        Rooks that have moved back to their original position as illegal,
4696        but in ICS mode that is not its job anyway.
4697     */
4698     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4699     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4700
4701         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4702             if(board[0][i] == WhiteRook) j = i;
4703         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4705             if(board[0][i] == WhiteRook) j = i;
4706         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4711             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4712         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4713
4714         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4715         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4716         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4717             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4718         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4719             if(board[BOARD_HEIGHT-1][k] == bKing)
4720                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4721         if(gameInfo.variant == VariantTwoKings) {
4722             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4723             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4724             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4725         }
4726     } else { int r;
4727         r = boards[moveNum][CASTLING][0] = initialRights[0];
4728         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4729         r = boards[moveNum][CASTLING][1] = initialRights[1];
4730         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4731         r = boards[moveNum][CASTLING][3] = initialRights[3];
4732         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4733         r = boards[moveNum][CASTLING][4] = initialRights[4];
4734         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4735         /* wildcastle kludge: always assume King has rights */
4736         r = boards[moveNum][CASTLING][2] = initialRights[2];
4737         r = boards[moveNum][CASTLING][5] = initialRights[5];
4738     }
4739     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4740     boards[moveNum][EP_STATUS] = EP_NONE;
4741     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4742     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4743     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4744
4745
4746     if (ics_getting_history == H_GOT_REQ_HEADER ||
4747         ics_getting_history == H_GOT_UNREQ_HEADER) {
4748         /* This was an initial position from a move list, not
4749            the current position */
4750         return;
4751     }
4752
4753     /* Update currentMove and known move number limits */
4754     newMove = newGame || moveNum > forwardMostMove;
4755
4756     if (newGame) {
4757         forwardMostMove = backwardMostMove = currentMove = moveNum;
4758         if (gameMode == IcsExamining && moveNum == 0) {
4759           /* Workaround for ICS limitation: we are not told the wild
4760              type when starting to examine a game.  But if we ask for
4761              the move list, the move list header will tell us */
4762             ics_getting_history = H_REQUESTED;
4763             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4764             SendToICS(str);
4765         }
4766     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4767                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4768 #if ZIPPY
4769         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4770         /* [HGM] applied this also to an engine that is silently watching        */
4771         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4772             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4773             gameInfo.variant == currentlyInitializedVariant) {
4774           takeback = forwardMostMove - moveNum;
4775           for (i = 0; i < takeback; i++) {
4776             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4777             SendToProgram("undo\n", &first);
4778           }
4779         }
4780 #endif
4781
4782         forwardMostMove = moveNum;
4783         if (!pausing || currentMove > forwardMostMove)
4784           currentMove = forwardMostMove;
4785     } else {
4786         /* New part of history that is not contiguous with old part */
4787         if (pausing && gameMode == IcsExamining) {
4788             pauseExamInvalid = TRUE;
4789             forwardMostMove = pauseExamForwardMostMove;
4790             return;
4791         }
4792         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4793 #if ZIPPY
4794             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4795                 // [HGM] when we will receive the move list we now request, it will be
4796                 // fed to the engine from the first move on. So if the engine is not
4797                 // in the initial position now, bring it there.
4798                 InitChessProgram(&first, 0);
4799             }
4800 #endif
4801             ics_getting_history = H_REQUESTED;
4802             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4803             SendToICS(str);
4804         }
4805         forwardMostMove = backwardMostMove = currentMove = moveNum;
4806     }
4807
4808     /* Update the clocks */
4809     if (strchr(elapsed_time, '.')) {
4810       /* Time is in ms */
4811       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4812       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4813     } else {
4814       /* Time is in seconds */
4815       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4816       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4817     }
4818
4819
4820 #if ZIPPY
4821     if (appData.zippyPlay && newGame &&
4822         gameMode != IcsObserving && gameMode != IcsIdle &&
4823         gameMode != IcsExamining)
4824       ZippyFirstBoard(moveNum, basetime, increment);
4825 #endif
4826
4827     /* Put the move on the move list, first converting
4828        to canonical algebraic form. */
4829     if (moveNum > 0) {
4830   if (appData.debugMode) {
4831     int f = forwardMostMove;
4832     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4833             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4834             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4835     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4836     fprintf(debugFP, "moveNum = %d\n", moveNum);
4837     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4838     setbuf(debugFP, NULL);
4839   }
4840         if (moveNum <= backwardMostMove) {
4841             /* We don't know what the board looked like before
4842                this move.  Punt. */
4843           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4844             strcat(parseList[moveNum - 1], " ");
4845             strcat(parseList[moveNum - 1], elapsed_time);
4846             moveList[moveNum - 1][0] = NULLCHAR;
4847         } else if (strcmp(move_str, "none") == 0) {
4848             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4849             /* Again, we don't know what the board looked like;
4850                this is really the start of the game. */
4851             parseList[moveNum - 1][0] = NULLCHAR;
4852             moveList[moveNum - 1][0] = NULLCHAR;
4853             backwardMostMove = moveNum;
4854             startedFromSetupPosition = TRUE;
4855             fromX = fromY = toX = toY = -1;
4856         } else {
4857           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4858           //                 So we parse the long-algebraic move string in stead of the SAN move
4859           int valid; char buf[MSG_SIZ], *prom;
4860
4861           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4862                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4863           // str looks something like "Q/a1-a2"; kill the slash
4864           if(str[1] == '/')
4865             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4866           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4867           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4868                 strcat(buf, prom); // long move lacks promo specification!
4869           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4870                 if(appData.debugMode)
4871                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4872                 safeStrCpy(move_str, buf, MSG_SIZ);
4873           }
4874           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4875                                 &fromX, &fromY, &toX, &toY, &promoChar)
4876                || ParseOneMove(buf, moveNum - 1, &moveType,
4877                                 &fromX, &fromY, &toX, &toY, &promoChar);
4878           // end of long SAN patch
4879           if (valid) {
4880             (void) CoordsToAlgebraic(boards[moveNum - 1],
4881                                      PosFlags(moveNum - 1),
4882                                      fromY, fromX, toY, toX, promoChar,
4883                                      parseList[moveNum-1]);
4884             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4885               case MT_NONE:
4886               case MT_STALEMATE:
4887               default:
4888                 break;
4889               case MT_CHECK:
4890                 if(!IS_SHOGI(gameInfo.variant))
4891                     strcat(parseList[moveNum - 1], "+");
4892                 break;
4893               case MT_CHECKMATE:
4894               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4895                 strcat(parseList[moveNum - 1], "#");
4896                 break;
4897             }
4898             strcat(parseList[moveNum - 1], " ");
4899             strcat(parseList[moveNum - 1], elapsed_time);
4900             /* currentMoveString is set as a side-effect of ParseOneMove */
4901             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4902             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4903             strcat(moveList[moveNum - 1], "\n");
4904
4905             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4906                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4907               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4908                 ChessSquare old, new = boards[moveNum][k][j];
4909                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4910                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4911                   if(old == new) continue;
4912                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4913                   else if(new == WhiteWazir || new == BlackWazir) {
4914                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4915                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4916                       else boards[moveNum][k][j] = old; // preserve type of Gold
4917                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4918                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4919               }
4920           } else {
4921             /* Move from ICS was illegal!?  Punt. */
4922             if (appData.debugMode) {
4923               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4924               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4925             }
4926             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4927             strcat(parseList[moveNum - 1], " ");
4928             strcat(parseList[moveNum - 1], elapsed_time);
4929             moveList[moveNum - 1][0] = NULLCHAR;
4930             fromX = fromY = toX = toY = -1;
4931           }
4932         }
4933   if (appData.debugMode) {
4934     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4935     setbuf(debugFP, NULL);
4936   }
4937
4938 #if ZIPPY
4939         /* Send move to chess program (BEFORE animating it). */
4940         if (appData.zippyPlay && !newGame && newMove &&
4941            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4942
4943             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4944                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4945                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4946                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4947                             move_str);
4948                     DisplayError(str, 0);
4949                 } else {
4950                     if (first.sendTime) {
4951                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4952                     }
4953                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4954                     if (firstMove && !bookHit) {
4955                         firstMove = FALSE;
4956                         if (first.useColors) {
4957                           SendToProgram(gameMode == IcsPlayingWhite ?
4958                                         "white\ngo\n" :
4959                                         "black\ngo\n", &first);
4960                         } else {
4961                           SendToProgram("go\n", &first);
4962                         }
4963                         first.maybeThinking = TRUE;
4964                     }
4965                 }
4966             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4967               if (moveList[moveNum - 1][0] == NULLCHAR) {
4968                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4969                 DisplayError(str, 0);
4970               } else {
4971                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4972                 SendMoveToProgram(moveNum - 1, &first);
4973               }
4974             }
4975         }
4976 #endif
4977     }
4978
4979     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4980         /* If move comes from a remote source, animate it.  If it
4981            isn't remote, it will have already been animated. */
4982         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4983             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4984         }
4985         if (!pausing && appData.highlightLastMove) {
4986             SetHighlights(fromX, fromY, toX, toY);
4987         }
4988     }
4989
4990     /* Start the clocks */
4991     whiteFlag = blackFlag = FALSE;
4992     appData.clockMode = !(basetime == 0 && increment == 0);
4993     if (ticking == 0) {
4994       ics_clock_paused = TRUE;
4995       StopClocks();
4996     } else if (ticking == 1) {
4997       ics_clock_paused = FALSE;
4998     }
4999     if (gameMode == IcsIdle ||
5000         relation == RELATION_OBSERVING_STATIC ||
5001         relation == RELATION_EXAMINING ||
5002         ics_clock_paused)
5003       DisplayBothClocks();
5004     else
5005       StartClocks();
5006
5007     /* Display opponents and material strengths */
5008     if (gameInfo.variant != VariantBughouse &&
5009         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5010         if (tinyLayout || smallLayout) {
5011             if(gameInfo.variant == VariantNormal)
5012               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5013                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5014                     basetime, increment);
5015             else
5016               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5017                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5018                     basetime, increment, (int) gameInfo.variant);
5019         } else {
5020             if(gameInfo.variant == VariantNormal)
5021               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5022                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023                     basetime, increment);
5024             else
5025               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5026                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5027                     basetime, increment, VariantName(gameInfo.variant));
5028         }
5029         DisplayTitle(str);
5030   if (appData.debugMode) {
5031     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5032   }
5033     }
5034
5035
5036     /* Display the board */
5037     if (!pausing && !appData.noGUI) {
5038
5039       if (appData.premove)
5040           if (!gotPremove ||
5041              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5042              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5043               ClearPremoveHighlights();
5044
5045       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5046         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5047       DrawPosition(j, boards[currentMove]);
5048
5049       DisplayMove(moveNum - 1);
5050       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5051             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5052               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5053         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5054       }
5055     }
5056
5057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5058 #if ZIPPY
5059     if(bookHit) { // [HGM] book: simulate book reply
5060         static char bookMove[MSG_SIZ]; // a bit generous?
5061
5062         programStats.nodes = programStats.depth = programStats.time =
5063         programStats.score = programStats.got_only_move = 0;
5064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5065
5066         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5067         strcat(bookMove, bookHit);
5068         HandleMachineMove(bookMove, &first);
5069     }
5070 #endif
5071 }
5072
5073 void
5074 GetMoveListEvent ()
5075 {
5076     char buf[MSG_SIZ];
5077     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5078         ics_getting_history = H_REQUESTED;
5079         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5080         SendToICS(buf);
5081     }
5082 }
5083
5084 void
5085 SendToBoth (char *msg)
5086 {   // to make it easy to keep two engines in step in dual analysis
5087     SendToProgram(msg, &first);
5088     if(second.analyzing) SendToProgram(msg, &second);
5089 }
5090
5091 void
5092 AnalysisPeriodicEvent (int force)
5093 {
5094     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5095          && !force) || !appData.periodicUpdates)
5096       return;
5097
5098     /* Send . command to Crafty to collect stats */
5099     SendToBoth(".\n");
5100
5101     /* Don't send another until we get a response (this makes
5102        us stop sending to old Crafty's which don't understand
5103        the "." command (sending illegal cmds resets node count & time,
5104        which looks bad)) */
5105     programStats.ok_to_send = 0;
5106 }
5107
5108 void
5109 ics_update_width (int new_width)
5110 {
5111         ics_printf("set width %d\n", new_width);
5112 }
5113
5114 void
5115 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5116 {
5117     char buf[MSG_SIZ];
5118
5119     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5120         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5121             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5122             SendToProgram(buf, cps);
5123             return;
5124         }
5125         // null move in variant where engine does not understand it (for analysis purposes)
5126         SendBoard(cps, moveNum + 1); // send position after move in stead.
5127         return;
5128     }
5129     if (cps->useUsermove) {
5130       SendToProgram("usermove ", cps);
5131     }
5132     if (cps->useSAN) {
5133       char *space;
5134       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5135         int len = space - parseList[moveNum];
5136         memcpy(buf, parseList[moveNum], len);
5137         buf[len++] = '\n';
5138         buf[len] = NULLCHAR;
5139       } else {
5140         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5141       }
5142       SendToProgram(buf, cps);
5143     } else {
5144       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5145         AlphaRank(moveList[moveNum], 4);
5146         SendToProgram(moveList[moveNum], cps);
5147         AlphaRank(moveList[moveNum], 4); // and back
5148       } else
5149       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5150        * the engine. It would be nice to have a better way to identify castle
5151        * moves here. */
5152       if(appData.fischerCastling && cps->useOOCastle) {
5153         int fromX = moveList[moveNum][0] - AAA;
5154         int fromY = moveList[moveNum][1] - ONE;
5155         int toX = moveList[moveNum][2] - AAA;
5156         int toY = moveList[moveNum][3] - ONE;
5157         if((boards[moveNum][fromY][fromX] == WhiteKing
5158             && boards[moveNum][toY][toX] == WhiteRook)
5159            || (boards[moveNum][fromY][fromX] == BlackKing
5160                && boards[moveNum][toY][toX] == BlackRook)) {
5161           if(toX > fromX) SendToProgram("O-O\n", cps);
5162           else SendToProgram("O-O-O\n", cps);
5163         }
5164         else SendToProgram(moveList[moveNum], cps);
5165       } else
5166       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5167         char *m = moveList[moveNum];
5168         static char c[2];
5169         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5170         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
5171           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5172                                                m[2], m[3] - '0',
5173                                                m[5], m[6] - '0',
5174                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5175         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5176           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5177           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
5178                                                m[7], m[8] - '0',
5179                                                m[7], m[8] - '0',
5180                                                m[5], m[6] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[2], m[3] - '0', c);
5183         } else
5184           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5185                                                m[5], m[6] - '0',
5186                                                m[5], m[6] - '0',
5187                                                m[2], m[3] - '0', c);
5188           SendToProgram(buf, cps);
5189       } else
5190       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5191         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5192           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5193           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5194                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5195         } else
5196           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5197                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5198         SendToProgram(buf, cps);
5199       }
5200       else SendToProgram(moveList[moveNum], cps);
5201       /* End of additions by Tord */
5202     }
5203
5204     /* [HGM] setting up the opening has brought engine in force mode! */
5205     /*       Send 'go' if we are in a mode where machine should play. */
5206     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5207         (gameMode == TwoMachinesPlay   ||
5208 #if ZIPPY
5209          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5210 #endif
5211          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5212         SendToProgram("go\n", cps);
5213   if (appData.debugMode) {
5214     fprintf(debugFP, "(extra)\n");
5215   }
5216     }
5217     setboardSpoiledMachineBlack = 0;
5218 }
5219
5220 void
5221 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5222 {
5223     char user_move[MSG_SIZ];
5224     char suffix[4];
5225
5226     if(gameInfo.variant == VariantSChess && promoChar) {
5227         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5228         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5229     } else suffix[0] = NULLCHAR;
5230
5231     switch (moveType) {
5232       default:
5233         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5234                 (int)moveType, fromX, fromY, toX, toY);
5235         DisplayError(user_move + strlen("say "), 0);
5236         break;
5237       case WhiteKingSideCastle:
5238       case BlackKingSideCastle:
5239       case WhiteQueenSideCastleWild:
5240       case BlackQueenSideCastleWild:
5241       /* PUSH Fabien */
5242       case WhiteHSideCastleFR:
5243       case BlackHSideCastleFR:
5244       /* POP Fabien */
5245         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5246         break;
5247       case WhiteQueenSideCastle:
5248       case BlackQueenSideCastle:
5249       case WhiteKingSideCastleWild:
5250       case BlackKingSideCastleWild:
5251       /* PUSH Fabien */
5252       case WhiteASideCastleFR:
5253       case BlackASideCastleFR:
5254       /* POP Fabien */
5255         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5256         break;
5257       case WhiteNonPromotion:
5258       case BlackNonPromotion:
5259         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5260         break;
5261       case WhitePromotion:
5262       case BlackPromotion:
5263         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5264            gameInfo.variant == VariantMakruk)
5265           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5266                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267                 PieceToChar(WhiteFerz));
5268         else if(gameInfo.variant == VariantGreat)
5269           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5271                 PieceToChar(WhiteMan));
5272         else
5273           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5274                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5275                 promoChar);
5276         break;
5277       case WhiteDrop:
5278       case BlackDrop:
5279       drop:
5280         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5281                  ToUpper(PieceToChar((ChessSquare) fromX)),
5282                  AAA + toX, ONE + toY);
5283         break;
5284       case IllegalMove:  /* could be a variant we don't quite understand */
5285         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5286       case NormalMove:
5287       case WhiteCapturesEnPassant:
5288       case BlackCapturesEnPassant:
5289         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5290                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5291         break;
5292     }
5293     SendToICS(user_move);
5294     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5295         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5296 }
5297
5298 void
5299 UploadGameEvent ()
5300 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5301     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5302     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5303     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5304       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5305       return;
5306     }
5307     if(gameMode != IcsExamining) { // is this ever not the case?
5308         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5309
5310         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5311           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5312         } else { // on FICS we must first go to general examine mode
5313           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5314         }
5315         if(gameInfo.variant != VariantNormal) {
5316             // try figure out wild number, as xboard names are not always valid on ICS
5317             for(i=1; i<=36; i++) {
5318               snprintf(buf, MSG_SIZ, "wild/%d", i);
5319                 if(StringToVariant(buf) == gameInfo.variant) break;
5320             }
5321             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5322             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5323             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5324         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5325         SendToICS(ics_prefix);
5326         SendToICS(buf);
5327         if(startedFromSetupPosition || backwardMostMove != 0) {
5328           fen = PositionToFEN(backwardMostMove, NULL, 1);
5329           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5330             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5331             SendToICS(buf);
5332           } else { // FICS: everything has to set by separate bsetup commands
5333             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5334             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5335             SendToICS(buf);
5336             if(!WhiteOnMove(backwardMostMove)) {
5337                 SendToICS("bsetup tomove black\n");
5338             }
5339             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5340             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5341             SendToICS(buf);
5342             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5343             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5344             SendToICS(buf);
5345             i = boards[backwardMostMove][EP_STATUS];
5346             if(i >= 0) { // set e.p.
5347               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5348                 SendToICS(buf);
5349             }
5350             bsetup++;
5351           }
5352         }
5353       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5354             SendToICS("bsetup done\n"); // switch to normal examining.
5355     }
5356     for(i = backwardMostMove; i<last; i++) {
5357         char buf[20];
5358         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5359         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5360             int len = strlen(moveList[i]);
5361             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5362             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5363         }
5364         SendToICS(buf);
5365     }
5366     SendToICS(ics_prefix);
5367     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5368 }
5369
5370 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5371 int legNr = 1;
5372
5373 void
5374 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5375 {
5376     if (rf == DROP_RANK) {
5377       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5378       sprintf(move, "%c@%c%c\n",
5379                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5380     } else {
5381         if (promoChar == 'x' || promoChar == NULLCHAR) {
5382           sprintf(move, "%c%c%c%c\n",
5383                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5384           if(killX >= 0 && killY >= 0) {
5385             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5386             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5387           }
5388         } else {
5389             sprintf(move, "%c%c%c%c%c\n",
5390                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5391           if(killX >= 0 && killY >= 0) {
5392             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5393             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5394           }
5395         }
5396     }
5397 }
5398
5399 void
5400 ProcessICSInitScript (FILE *f)
5401 {
5402     char buf[MSG_SIZ];
5403
5404     while (fgets(buf, MSG_SIZ, f)) {
5405         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5406     }
5407
5408     fclose(f);
5409 }
5410
5411
5412 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5413 int dragging;
5414 static ClickType lastClickType;
5415
5416 int
5417 PieceInString (char *s, ChessSquare piece)
5418 {
5419   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5420   while((p = strchr(s, ID))) {
5421     if(!suffix || p[1] == suffix) return TRUE;
5422     s = p;
5423   }
5424   return FALSE;
5425 }
5426
5427 int
5428 Partner (ChessSquare *p)
5429 { // change piece into promotion partner if one shogi-promotes to the other
5430   ChessSquare partner = promoPartner[*p];
5431   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5432   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5433   *p = partner;
5434   return 1;
5435 }
5436
5437 void
5438 Sweep (int step)
5439 {
5440     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5441     static int toggleFlag;
5442     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5443     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5444     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5445     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5446     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5447     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5448     do {
5449         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5450         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5451         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5452         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5453         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5454         if(!step) step = -1;
5455     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5456             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5457             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5458             promoSweep == pawn ||
5459             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5460             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5461     if(toX >= 0) {
5462         int victim = boards[currentMove][toY][toX];
5463         boards[currentMove][toY][toX] = promoSweep;
5464         DrawPosition(FALSE, boards[currentMove]);
5465         boards[currentMove][toY][toX] = victim;
5466     } else
5467     ChangeDragPiece(promoSweep);
5468 }
5469
5470 int
5471 PromoScroll (int x, int y)
5472 {
5473   int step = 0;
5474
5475   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5476   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5477   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5478   if(!step) return FALSE;
5479   lastX = x; lastY = y;
5480   if((promoSweep < BlackPawn) == flipView) step = -step;
5481   if(step > 0) selectFlag = 1;
5482   if(!selectFlag) Sweep(step);
5483   return FALSE;
5484 }
5485
5486 void
5487 NextPiece (int step)
5488 {
5489     ChessSquare piece = boards[currentMove][toY][toX];
5490     do {
5491         pieceSweep -= step;
5492         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5493         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5494         if(!step) step = -1;
5495     } while(PieceToChar(pieceSweep) == '.');
5496     boards[currentMove][toY][toX] = pieceSweep;
5497     DrawPosition(FALSE, boards[currentMove]);
5498     boards[currentMove][toY][toX] = piece;
5499 }
5500 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5501 void
5502 AlphaRank (char *move, int n)
5503 {
5504 //    char *p = move, c; int x, y;
5505
5506     if (appData.debugMode) {
5507         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5508     }
5509
5510     if(move[1]=='*' &&
5511        move[2]>='0' && move[2]<='9' &&
5512        move[3]>='a' && move[3]<='x'    ) {
5513         move[1] = '@';
5514         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5515         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5516     } else
5517     if(move[0]>='0' && move[0]<='9' &&
5518        move[1]>='a' && move[1]<='x' &&
5519        move[2]>='0' && move[2]<='9' &&
5520        move[3]>='a' && move[3]<='x'    ) {
5521         /* input move, Shogi -> normal */
5522         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5523         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5524         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5525         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5526     } else
5527     if(move[1]=='@' &&
5528        move[3]>='0' && move[3]<='9' &&
5529        move[2]>='a' && move[2]<='x'    ) {
5530         move[1] = '*';
5531         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5532         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5533     } else
5534     if(
5535        move[0]>='a' && move[0]<='x' &&
5536        move[3]>='0' && move[3]<='9' &&
5537        move[2]>='a' && move[2]<='x'    ) {
5538          /* output move, normal -> Shogi */
5539         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5540         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5541         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5542         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5543         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5544     }
5545     if (appData.debugMode) {
5546         fprintf(debugFP, "   out = '%s'\n", move);
5547     }
5548 }
5549
5550 char yy_textstr[8000];
5551
5552 /* Parser for moves from gnuchess, ICS, or user typein box */
5553 Boolean
5554 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5555 {
5556     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5557
5558     switch (*moveType) {
5559       case WhitePromotion:
5560       case BlackPromotion:
5561       case WhiteNonPromotion:
5562       case BlackNonPromotion:
5563       case NormalMove:
5564       case FirstLeg:
5565       case WhiteCapturesEnPassant:
5566       case BlackCapturesEnPassant:
5567       case WhiteKingSideCastle:
5568       case WhiteQueenSideCastle:
5569       case BlackKingSideCastle:
5570       case BlackQueenSideCastle:
5571       case WhiteKingSideCastleWild:
5572       case WhiteQueenSideCastleWild:
5573       case BlackKingSideCastleWild:
5574       case BlackQueenSideCastleWild:
5575       /* Code added by Tord: */
5576       case WhiteHSideCastleFR:
5577       case WhiteASideCastleFR:
5578       case BlackHSideCastleFR:
5579       case BlackASideCastleFR:
5580       /* End of code added by Tord */
5581       case IllegalMove:         /* bug or odd chess variant */
5582         if(currentMoveString[1] == '@') { // illegal drop
5583           *fromX = WhiteOnMove(moveNum) ?
5584             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5585             (int) CharToPiece(ToLower(currentMoveString[0]));
5586           goto drop;
5587         }
5588         *fromX = currentMoveString[0] - AAA;
5589         *fromY = currentMoveString[1] - ONE;
5590         *toX = currentMoveString[2] - AAA;
5591         *toY = currentMoveString[3] - ONE;
5592         *promoChar = currentMoveString[4];
5593         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5594         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5595             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5596     if (appData.debugMode) {
5597         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5598     }
5599             *fromX = *fromY = *toX = *toY = 0;
5600             return FALSE;
5601         }
5602         if (appData.testLegality) {
5603           return (*moveType != IllegalMove);
5604         } else {
5605           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5606                          // [HGM] lion: if this is a double move we are less critical
5607                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5608         }
5609
5610       case WhiteDrop:
5611       case BlackDrop:
5612         *fromX = *moveType == WhiteDrop ?
5613           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5614           (int) CharToPiece(ToLower(currentMoveString[0]));
5615       drop:
5616         *fromY = DROP_RANK;
5617         *toX = currentMoveString[2] - AAA;
5618         *toY = currentMoveString[3] - ONE;
5619         *promoChar = NULLCHAR;
5620         return TRUE;
5621
5622       case AmbiguousMove:
5623       case ImpossibleMove:
5624       case EndOfFile:
5625       case ElapsedTime:
5626       case Comment:
5627       case PGNTag:
5628       case NAG:
5629       case WhiteWins:
5630       case BlackWins:
5631       case GameIsDrawn:
5632       default:
5633     if (appData.debugMode) {
5634         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5635     }
5636         /* bug? */
5637         *fromX = *fromY = *toX = *toY = 0;
5638         *promoChar = NULLCHAR;
5639         return FALSE;
5640     }
5641 }
5642
5643 Boolean pushed = FALSE;
5644 char *lastParseAttempt;
5645
5646 void
5647 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5648 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5649   int fromX, fromY, toX, toY; char promoChar;
5650   ChessMove moveType;
5651   Boolean valid;
5652   int nr = 0;
5653
5654   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5655   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5656     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5657     pushed = TRUE;
5658   }
5659   endPV = forwardMostMove;
5660   do {
5661     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5662     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5663     lastParseAttempt = pv;
5664     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5665     if(!valid && nr == 0 &&
5666        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5667         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5668         // Hande case where played move is different from leading PV move
5669         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5670         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5671         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5672         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5673           endPV += 2; // if position different, keep this
5674           moveList[endPV-1][0] = fromX + AAA;
5675           moveList[endPV-1][1] = fromY + ONE;
5676           moveList[endPV-1][2] = toX + AAA;
5677           moveList[endPV-1][3] = toY + ONE;
5678           parseList[endPV-1][0] = NULLCHAR;
5679           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5680         }
5681       }
5682     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5683     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5684     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5685     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5686         valid++; // allow comments in PV
5687         continue;
5688     }
5689     nr++;
5690     if(endPV+1 > framePtr) break; // no space, truncate
5691     if(!valid) break;
5692     endPV++;
5693     CopyBoard(boards[endPV], boards[endPV-1]);
5694     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5695     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5696     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5697     CoordsToAlgebraic(boards[endPV - 1],
5698                              PosFlags(endPV - 1),
5699                              fromY, fromX, toY, toX, promoChar,
5700                              parseList[endPV - 1]);
5701   } while(valid);
5702   if(atEnd == 2) return; // used hidden, for PV conversion
5703   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5704   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5705   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5706                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5707   DrawPosition(TRUE, boards[currentMove]);
5708 }
5709
5710 int
5711 MultiPV (ChessProgramState *cps, int kind)
5712 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5713         int i;
5714         for(i=0; i<cps->nrOptions; i++) {
5715             char *s = cps->option[i].name;
5716             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5717             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5718                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5719         }
5720         return -1;
5721 }
5722
5723 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5724 static int multi, pv_margin;
5725 static ChessProgramState *activeCps;
5726
5727 Boolean
5728 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5729 {
5730         int startPV, lineStart, origIndex = index;
5731         char *p, buf2[MSG_SIZ];
5732         ChessProgramState *cps = (pane ? &second : &first);
5733
5734         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5735         lastX = x; lastY = y;
5736         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5737         lineStart = startPV = index;
5738         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5739         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5740         index = startPV;
5741         do{ while(buf[index] && buf[index] != '\n') index++;
5742         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5743         buf[index] = 0;
5744         if(lineStart == 0 && gameMode == AnalyzeMode) {
5745             int n = 0;
5746             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5747             if(n == 0) { // click not on "fewer" or "more"
5748                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5749                     pv_margin = cps->option[multi].value;
5750                     activeCps = cps; // non-null signals margin adjustment
5751                 }
5752             } else if((multi = MultiPV(cps, 1)) >= 0) {
5753                 n += cps->option[multi].value; if(n < 1) n = 1;
5754                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5755                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5756                 cps->option[multi].value = n;
5757                 *start = *end = 0;
5758                 return FALSE;
5759             }
5760         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5761                 ExcludeClick(origIndex - lineStart);
5762                 return FALSE;
5763         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5764                 Collapse(origIndex - lineStart);
5765                 return FALSE;
5766         }
5767         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5768         *start = startPV; *end = index-1;
5769         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5770         return TRUE;
5771 }
5772
5773 char *
5774 PvToSAN (char *pv)
5775 {
5776         static char buf[10*MSG_SIZ];
5777         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5778         *buf = NULLCHAR;
5779         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5780         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5781         for(i = forwardMostMove; i<endPV; i++){
5782             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5783             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5784             k += strlen(buf+k);
5785         }
5786         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5787         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5788         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5789         endPV = savedEnd;
5790         return buf;
5791 }
5792
5793 Boolean
5794 LoadPV (int x, int y)
5795 { // called on right mouse click to load PV
5796   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5797   lastX = x; lastY = y;
5798   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5799   extendGame = FALSE;
5800   return TRUE;
5801 }
5802
5803 void
5804 UnLoadPV ()
5805 {
5806   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5807   if(activeCps) {
5808     if(pv_margin != activeCps->option[multi].value) {
5809       char buf[MSG_SIZ];
5810       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5811       SendToProgram(buf, activeCps);
5812       activeCps->option[multi].value = pv_margin;
5813     }
5814     activeCps = NULL;
5815     return;
5816   }
5817   if(endPV < 0) return;
5818   if(appData.autoCopyPV) CopyFENToClipboard();
5819   endPV = -1;
5820   if(extendGame && currentMove > forwardMostMove) {
5821         Boolean saveAnimate = appData.animate;
5822         if(pushed) {
5823             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5824                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5825             } else storedGames--; // abandon shelved tail of original game
5826         }
5827         pushed = FALSE;
5828         forwardMostMove = currentMove;
5829         currentMove = oldFMM;
5830         appData.animate = FALSE;
5831         ToNrEvent(forwardMostMove);
5832         appData.animate = saveAnimate;
5833   }
5834   currentMove = forwardMostMove;
5835   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5836   ClearPremoveHighlights();
5837   DrawPosition(TRUE, boards[currentMove]);
5838 }
5839
5840 void
5841 MovePV (int x, int y, int h)
5842 { // step through PV based on mouse coordinates (called on mouse move)
5843   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5844
5845   if(activeCps) { // adjusting engine's multi-pv margin
5846     if(x > lastX) pv_margin++; else
5847     if(x < lastX) pv_margin -= (pv_margin > 0);
5848     if(x != lastX) {
5849       char buf[MSG_SIZ];
5850       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5851       DisplayMessage(buf, "");
5852     }
5853     lastX = x;
5854     return;
5855   }
5856   // we must somehow check if right button is still down (might be released off board!)
5857   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5858   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5859   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5860   if(!step) return;
5861   lastX = x; lastY = y;
5862
5863   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5864   if(endPV < 0) return;
5865   if(y < margin) step = 1; else
5866   if(y > h - margin) step = -1;
5867   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5868   currentMove += step;
5869   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5870   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5871                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5872   DrawPosition(FALSE, boards[currentMove]);
5873 }
5874
5875
5876 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5877 // All positions will have equal probability, but the current method will not provide a unique
5878 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5879 #define DARK 1
5880 #define LITE 2
5881 #define ANY 3
5882
5883 int squaresLeft[4];
5884 int piecesLeft[(int)BlackPawn];
5885 int seed, nrOfShuffles;
5886
5887 void
5888 GetPositionNumber ()
5889 {       // sets global variable seed
5890         int i;
5891
5892         seed = appData.defaultFrcPosition;
5893         if(seed < 0) { // randomize based on time for negative FRC position numbers
5894                 for(i=0; i<50; i++) seed += random();
5895                 seed = random() ^ random() >> 8 ^ random() << 8;
5896                 if(seed<0) seed = -seed;
5897         }
5898 }
5899
5900 int
5901 put (Board board, int pieceType, int rank, int n, int shade)
5902 // put the piece on the (n-1)-th empty squares of the given shade
5903 {
5904         int i;
5905
5906         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5907                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5908                         board[rank][i] = (ChessSquare) pieceType;
5909                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5910                         squaresLeft[ANY]--;
5911                         piecesLeft[pieceType]--;
5912                         return i;
5913                 }
5914         }
5915         return -1;
5916 }
5917
5918
5919 void
5920 AddOnePiece (Board board, int pieceType, int rank, int shade)
5921 // calculate where the next piece goes, (any empty square), and put it there
5922 {
5923         int i;
5924
5925         i = seed % squaresLeft[shade];
5926         nrOfShuffles *= squaresLeft[shade];
5927         seed /= squaresLeft[shade];
5928         put(board, pieceType, rank, i, shade);
5929 }
5930
5931 void
5932 AddTwoPieces (Board board, int pieceType, int rank)
5933 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5934 {
5935         int i, n=squaresLeft[ANY], j=n-1, k;
5936
5937         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5938         i = seed % k;  // pick one
5939         nrOfShuffles *= k;
5940         seed /= k;
5941         while(i >= j) i -= j--;
5942         j = n - 1 - j; i += j;
5943         put(board, pieceType, rank, j, ANY);
5944         put(board, pieceType, rank, i, ANY);
5945 }
5946
5947 void
5948 SetUpShuffle (Board board, int number)
5949 {
5950         int i, p, first=1;
5951
5952         GetPositionNumber(); nrOfShuffles = 1;
5953
5954         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5955         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5956         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5957
5958         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5959
5960         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5961             p = (int) board[0][i];
5962             if(p < (int) BlackPawn) piecesLeft[p] ++;
5963             board[0][i] = EmptySquare;
5964         }
5965
5966         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5967             // shuffles restricted to allow normal castling put KRR first
5968             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5969                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5970             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5971                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5972             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5973                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5974             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5975                 put(board, WhiteRook, 0, 0, ANY);
5976             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5977         }
5978
5979         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5980             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5981             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5982                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5983                 while(piecesLeft[p] >= 2) {
5984                     AddOnePiece(board, p, 0, LITE);
5985                     AddOnePiece(board, p, 0, DARK);
5986                 }
5987                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5988             }
5989
5990         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5991             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5992             // but we leave King and Rooks for last, to possibly obey FRC restriction
5993             if(p == (int)WhiteRook) continue;
5994             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5995             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5996         }
5997
5998         // now everything is placed, except perhaps King (Unicorn) and Rooks
5999
6000         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6001             // Last King gets castling rights
6002             while(piecesLeft[(int)WhiteUnicorn]) {
6003                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6004                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6005             }
6006
6007             while(piecesLeft[(int)WhiteKing]) {
6008                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6009                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6010             }
6011
6012
6013         } else {
6014             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6015             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6016         }
6017
6018         // Only Rooks can be left; simply place them all
6019         while(piecesLeft[(int)WhiteRook]) {
6020                 i = put(board, WhiteRook, 0, 0, ANY);
6021                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6022                         if(first) {
6023                                 first=0;
6024                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6025                         }
6026                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6027                 }
6028         }
6029         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6030             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6031         }
6032
6033         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6034 }
6035
6036 int
6037 ptclen (const char *s, char *escapes)
6038 {
6039     int n = 0;
6040     if(!*escapes) return strlen(s);
6041     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6042     return n;
6043 }
6044
6045 int
6046 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6047 /* [HGM] moved here from winboard.c because of its general usefulness */
6048 /*       Basically a safe strcpy that uses the last character as King */
6049 {
6050     int result = FALSE; int NrPieces;
6051     unsigned char partner[EmptySquare];
6052
6053     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6054                     && NrPieces >= 12 && !(NrPieces&1)) {
6055         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6056
6057         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6058         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6059             char *p, c=0;
6060             if(map[j] == '/') offs = WhitePBishop - i, j++;
6061             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6062             table[i+offs] = map[j++];
6063             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6064             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6065             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6066         }
6067         table[(int) WhiteKing]  = map[j++];
6068         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6069             char *p, c=0;
6070             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6071             i = WHITE_TO_BLACK ii;
6072             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6073             table[i+offs] = map[j++];
6074             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6075             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6076             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6077         }
6078         table[(int) BlackKing]  = map[j++];
6079
6080
6081         if(*escapes) { // set up promotion pairing
6082             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6083             // pieceToChar entirely filled, so we can look up specified partners
6084             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6085                 int c = table[i];
6086                 if(c == '^' || c == '-') { // has specified partner
6087                     int p;
6088                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6089                     if(c == '^') table[i] = '+';
6090                     if(p < EmptySquare) {
6091                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6092                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6093                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6094                     }
6095                 } else if(c == '*') {
6096                     table[i] = partner[i];
6097                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6098                 }
6099             }
6100         }
6101
6102         result = TRUE;
6103     }
6104
6105     return result;
6106 }
6107
6108 int
6109 SetCharTable (unsigned char *table, const char * map)
6110 {
6111     return SetCharTableEsc(table, map, "");
6112 }
6113
6114 void
6115 Prelude (Board board)
6116 {       // [HGM] superchess: random selection of exo-pieces
6117         int i, j, k; ChessSquare p;
6118         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6119
6120         GetPositionNumber(); // use FRC position number
6121
6122         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6123             SetCharTable(pieceToChar, appData.pieceToCharTable);
6124             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6125                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6126         }
6127
6128         j = seed%4;                 seed /= 4;
6129         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6131         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6132         j = seed%3 + (seed%3 >= j); seed /= 3;
6133         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6134         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6135         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6136         j = seed%3;                 seed /= 3;
6137         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6139         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6140         j = seed%2 + (seed%2 >= j); seed /= 2;
6141         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6142         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6143         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6144         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6145         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6146         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6147         put(board, exoPieces[0],    0, 0, ANY);
6148         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6149 }
6150
6151 void
6152 InitPosition (int redraw)
6153 {
6154     ChessSquare (* pieces)[BOARD_FILES];
6155     int i, j, pawnRow=1, pieceRows=1, overrule,
6156     oldx = gameInfo.boardWidth,
6157     oldy = gameInfo.boardHeight,
6158     oldh = gameInfo.holdingsWidth;
6159     static int oldv;
6160
6161     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6162
6163     /* [AS] Initialize pv info list [HGM] and game status */
6164     {
6165         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6166             pvInfoList[i].depth = 0;
6167             boards[i][EP_STATUS] = EP_NONE;
6168             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6169         }
6170
6171         initialRulePlies = 0; /* 50-move counter start */
6172
6173         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6174         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6175     }
6176
6177
6178     /* [HGM] logic here is completely changed. In stead of full positions */
6179     /* the initialized data only consist of the two backranks. The switch */
6180     /* selects which one we will use, which is than copied to the Board   */
6181     /* initialPosition, which for the rest is initialized by Pawns and    */
6182     /* empty squares. This initial position is then copied to boards[0],  */
6183     /* possibly after shuffling, so that it remains available.            */
6184
6185     gameInfo.holdingsWidth = 0; /* default board sizes */
6186     gameInfo.boardWidth    = 8;
6187     gameInfo.boardHeight   = 8;
6188     gameInfo.holdingsSize  = 0;
6189     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6190     for(i=0; i<BOARD_FILES-6; i++)
6191       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6192     initialPosition[EP_STATUS] = EP_NONE;
6193     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6194     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6195     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6196          SetCharTable(pieceNickName, appData.pieceNickNames);
6197     else SetCharTable(pieceNickName, "............");
6198     pieces = FIDEArray;
6199
6200     switch (gameInfo.variant) {
6201     case VariantFischeRandom:
6202       shuffleOpenings = TRUE;
6203       appData.fischerCastling = TRUE;
6204     default:
6205       break;
6206     case VariantShatranj:
6207       pieces = ShatranjArray;
6208       nrCastlingRights = 0;
6209       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6210       break;
6211     case VariantMakruk:
6212       pieces = makrukArray;
6213       nrCastlingRights = 0;
6214       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6215       break;
6216     case VariantASEAN:
6217       pieces = aseanArray;
6218       nrCastlingRights = 0;
6219       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6220       break;
6221     case VariantTwoKings:
6222       pieces = twoKingsArray;
6223       break;
6224     case VariantGrand:
6225       pieces = GrandArray;
6226       nrCastlingRights = 0;
6227       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6228       gameInfo.boardWidth = 10;
6229       gameInfo.boardHeight = 10;
6230       gameInfo.holdingsSize = 7;
6231       break;
6232     case VariantCapaRandom:
6233       shuffleOpenings = TRUE;
6234       appData.fischerCastling = TRUE;
6235     case VariantCapablanca:
6236       pieces = CapablancaArray;
6237       gameInfo.boardWidth = 10;
6238       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6239       break;
6240     case VariantGothic:
6241       pieces = GothicArray;
6242       gameInfo.boardWidth = 10;
6243       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6244       break;
6245     case VariantSChess:
6246       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6247       gameInfo.holdingsSize = 7;
6248       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6249       break;
6250     case VariantJanus:
6251       pieces = JanusArray;
6252       gameInfo.boardWidth = 10;
6253       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6254       nrCastlingRights = 6;
6255         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6256         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6257         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6258         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6259         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6260         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6261       break;
6262     case VariantFalcon:
6263       pieces = FalconArray;
6264       gameInfo.boardWidth = 10;
6265       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6266       break;
6267     case VariantXiangqi:
6268       pieces = XiangqiArray;
6269       gameInfo.boardWidth  = 9;
6270       gameInfo.boardHeight = 10;
6271       nrCastlingRights = 0;
6272       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6273       break;
6274     case VariantShogi:
6275       pieces = ShogiArray;
6276       gameInfo.boardWidth  = 9;
6277       gameInfo.boardHeight = 9;
6278       gameInfo.holdingsSize = 7;
6279       nrCastlingRights = 0;
6280       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6281       break;
6282     case VariantChu:
6283       pieces = ChuArray; pieceRows = 3;
6284       gameInfo.boardWidth  = 12;
6285       gameInfo.boardHeight = 12;
6286       nrCastlingRights = 0;
6287 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6288   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6289       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"
6290                                    "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);
6291       break;
6292     case VariantCourier:
6293       pieces = CourierArray;
6294       gameInfo.boardWidth  = 12;
6295       nrCastlingRights = 0;
6296       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6297       break;
6298     case VariantKnightmate:
6299       pieces = KnightmateArray;
6300       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6301       break;
6302     case VariantSpartan:
6303       pieces = SpartanArray;
6304       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6305       break;
6306     case VariantLion:
6307       pieces = lionArray;
6308       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6309       break;
6310     case VariantChuChess:
6311       pieces = ChuChessArray;
6312       gameInfo.boardWidth = 10;
6313       gameInfo.boardHeight = 10;
6314       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6315       break;
6316     case VariantFairy:
6317       pieces = fairyArray;
6318       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6319       break;
6320     case VariantGreat:
6321       pieces = GreatArray;
6322       gameInfo.boardWidth = 10;
6323       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6324       gameInfo.holdingsSize = 8;
6325       break;
6326     case VariantSuper:
6327       pieces = FIDEArray;
6328       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6329       gameInfo.holdingsSize = 8;
6330       startedFromSetupPosition = TRUE;
6331       break;
6332     case VariantCrazyhouse:
6333     case VariantBughouse:
6334       pieces = FIDEArray;
6335       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6336       gameInfo.holdingsSize = 5;
6337       break;
6338     case VariantWildCastle:
6339       pieces = FIDEArray;
6340       /* !!?shuffle with kings guaranteed to be on d or e file */
6341       shuffleOpenings = 1;
6342       break;
6343     case VariantNoCastle:
6344       pieces = FIDEArray;
6345       nrCastlingRights = 0;
6346       /* !!?unconstrained back-rank shuffle */
6347       shuffleOpenings = 1;
6348       break;
6349     }
6350
6351     overrule = 0;
6352     if(appData.NrFiles >= 0) {
6353         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6354         gameInfo.boardWidth = appData.NrFiles;
6355     }
6356     if(appData.NrRanks >= 0) {
6357         gameInfo.boardHeight = appData.NrRanks;
6358     }
6359     if(appData.holdingsSize >= 0) {
6360         i = appData.holdingsSize;
6361         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6362         gameInfo.holdingsSize = i;
6363     }
6364     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6365     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6366         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6367
6368     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6369     if(pawnRow < 1) pawnRow = 1;
6370     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6371        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6372     if(gameInfo.variant == VariantChu) pawnRow = 3;
6373
6374     /* User pieceToChar list overrules defaults */
6375     if(appData.pieceToCharTable != NULL)
6376         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6377
6378     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6379
6380         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6381             s = (ChessSquare) 0; /* account holding counts in guard band */
6382         for( i=0; i<BOARD_HEIGHT; i++ )
6383             initialPosition[i][j] = s;
6384
6385         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6386         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6387         initialPosition[pawnRow][j] = WhitePawn;
6388         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6389         if(gameInfo.variant == VariantXiangqi) {
6390             if(j&1) {
6391                 initialPosition[pawnRow][j] =
6392                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6393                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6394                    initialPosition[2][j] = WhiteCannon;
6395                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6396                 }
6397             }
6398         }
6399         if(gameInfo.variant == VariantChu) {
6400              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6401                initialPosition[pawnRow+1][j] = WhiteCobra,
6402                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6403              for(i=1; i<pieceRows; i++) {
6404                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6405                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6406              }
6407         }
6408         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6409             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6410                initialPosition[0][j] = WhiteRook;
6411                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6412             }
6413         }
6414         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6415     }
6416     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6417     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6418
6419             j=BOARD_LEFT+1;
6420             initialPosition[1][j] = WhiteBishop;
6421             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6422             j=BOARD_RGHT-2;
6423             initialPosition[1][j] = WhiteRook;
6424             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6425     }
6426
6427     if( nrCastlingRights == -1) {
6428         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6429         /*       This sets default castling rights from none to normal corners   */
6430         /* Variants with other castling rights must set them themselves above    */
6431         nrCastlingRights = 6;
6432
6433         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6434         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6435         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6436         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6437         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6438         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6439      }
6440
6441      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6442      if(gameInfo.variant == VariantGreat) { // promotion commoners
6443         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6444         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6445         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6446         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6447      }
6448      if( gameInfo.variant == VariantSChess ) {
6449       initialPosition[1][0] = BlackMarshall;
6450       initialPosition[2][0] = BlackAngel;
6451       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6452       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6453       initialPosition[1][1] = initialPosition[2][1] =
6454       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6455      }
6456   if (appData.debugMode) {
6457     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6458   }
6459     if(shuffleOpenings) {
6460         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6461         startedFromSetupPosition = TRUE;
6462     }
6463     if(startedFromPositionFile) {
6464       /* [HGM] loadPos: use PositionFile for every new game */
6465       CopyBoard(initialPosition, filePosition);
6466       for(i=0; i<nrCastlingRights; i++)
6467           initialRights[i] = filePosition[CASTLING][i];
6468       startedFromSetupPosition = TRUE;
6469     }
6470     if(*appData.men) LoadPieceDesc(appData.men);
6471
6472     CopyBoard(boards[0], initialPosition);
6473
6474     if(oldx != gameInfo.boardWidth ||
6475        oldy != gameInfo.boardHeight ||
6476        oldv != gameInfo.variant ||
6477        oldh != gameInfo.holdingsWidth
6478                                          )
6479             InitDrawingSizes(-2 ,0);
6480
6481     oldv = gameInfo.variant;
6482     if (redraw)
6483       DrawPosition(TRUE, boards[currentMove]);
6484 }
6485
6486 void
6487 SendBoard (ChessProgramState *cps, int moveNum)
6488 {
6489     char message[MSG_SIZ];
6490
6491     if (cps->useSetboard) {
6492       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6493       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6494       SendToProgram(message, cps);
6495       free(fen);
6496
6497     } else {
6498       ChessSquare *bp;
6499       int i, j, left=0, right=BOARD_WIDTH;
6500       /* Kludge to set black to move, avoiding the troublesome and now
6501        * deprecated "black" command.
6502        */
6503       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6504         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6505
6506       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6507
6508       SendToProgram("edit\n", cps);
6509       SendToProgram("#\n", cps);
6510       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6511         bp = &boards[moveNum][i][left];
6512         for (j = left; j < right; j++, bp++) {
6513           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6514           if ((int) *bp < (int) BlackPawn) {
6515             if(j == BOARD_RGHT+1)
6516                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6517             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6518             if(message[0] == '+' || message[0] == '~') {
6519               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6520                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6521                         AAA + j, ONE + i - '0');
6522             }
6523             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6524                 message[1] = BOARD_RGHT   - 1 - j + '1';
6525                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6526             }
6527             SendToProgram(message, cps);
6528           }
6529         }
6530       }
6531
6532       SendToProgram("c\n", cps);
6533       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6534         bp = &boards[moveNum][i][left];
6535         for (j = left; j < right; j++, bp++) {
6536           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6537           if (((int) *bp != (int) EmptySquare)
6538               && ((int) *bp >= (int) BlackPawn)) {
6539             if(j == BOARD_LEFT-2)
6540                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6541             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6542                     AAA + j, ONE + i - '0');
6543             if(message[0] == '+' || message[0] == '~') {
6544               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6545                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6546                         AAA + j, ONE + i - '0');
6547             }
6548             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6549                 message[1] = BOARD_RGHT   - 1 - j + '1';
6550                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6551             }
6552             SendToProgram(message, cps);
6553           }
6554         }
6555       }
6556
6557       SendToProgram(".\n", cps);
6558     }
6559     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6560 }
6561
6562 char exclusionHeader[MSG_SIZ];
6563 int exCnt, excludePtr;
6564 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6565 static Exclusion excluTab[200];
6566 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6567
6568 static void
6569 WriteMap (int s)
6570 {
6571     int j;
6572     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6573     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6574 }
6575
6576 static void
6577 ClearMap ()
6578 {
6579     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6580     excludePtr = 24; exCnt = 0;
6581     WriteMap(0);
6582 }
6583
6584 static void
6585 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6586 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6587     char buf[2*MOVE_LEN], *p;
6588     Exclusion *e = excluTab;
6589     int i;
6590     for(i=0; i<exCnt; i++)
6591         if(e[i].ff == fromX && e[i].fr == fromY &&
6592            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6593     if(i == exCnt) { // was not in exclude list; add it
6594         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6595         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6596             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6597             return; // abort
6598         }
6599         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6600         excludePtr++; e[i].mark = excludePtr++;
6601         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6602         exCnt++;
6603     }
6604     exclusionHeader[e[i].mark] = state;
6605 }
6606
6607 static int
6608 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6609 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6610     char buf[MSG_SIZ];
6611     int j, k;
6612     ChessMove moveType;
6613     if((signed char)promoChar == -1) { // kludge to indicate best move
6614         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6615             return 1; // if unparsable, abort
6616     }
6617     // update exclusion map (resolving toggle by consulting existing state)
6618     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6619     j = k%8; k >>= 3;
6620     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6621     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6622          excludeMap[k] |=   1<<j;
6623     else excludeMap[k] &= ~(1<<j);
6624     // update header
6625     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6626     // inform engine
6627     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6628     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6629     SendToBoth(buf);
6630     return (state == '+');
6631 }
6632
6633 static void
6634 ExcludeClick (int index)
6635 {
6636     int i, j;
6637     Exclusion *e = excluTab;
6638     if(index < 25) { // none, best or tail clicked
6639         if(index < 13) { // none: include all
6640             WriteMap(0); // clear map
6641             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6642             SendToBoth("include all\n"); // and inform engine
6643         } else if(index > 18) { // tail
6644             if(exclusionHeader[19] == '-') { // tail was excluded
6645                 SendToBoth("include all\n");
6646                 WriteMap(0); // clear map completely
6647                 // now re-exclude selected moves
6648                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6649                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6650             } else { // tail was included or in mixed state
6651                 SendToBoth("exclude all\n");
6652                 WriteMap(0xFF); // fill map completely
6653                 // now re-include selected moves
6654                 j = 0; // count them
6655                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6656                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6657                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6658             }
6659         } else { // best
6660             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6661         }
6662     } else {
6663         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6664             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6665             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6666             break;
6667         }
6668     }
6669 }
6670
6671 ChessSquare
6672 DefaultPromoChoice (int white)
6673 {
6674     ChessSquare result;
6675     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6676        gameInfo.variant == VariantMakruk)
6677         result = WhiteFerz; // no choice
6678     else if(gameInfo.variant == VariantASEAN)
6679         result = WhiteRook; // no choice
6680     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6681         result= WhiteKing; // in Suicide Q is the last thing we want
6682     else if(gameInfo.variant == VariantSpartan)
6683         result = white ? WhiteQueen : WhiteAngel;
6684     else result = WhiteQueen;
6685     if(!white) result = WHITE_TO_BLACK result;
6686     return result;
6687 }
6688
6689 static int autoQueen; // [HGM] oneclick
6690
6691 int
6692 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6693 {
6694     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6695     /* [HGM] add Shogi promotions */
6696     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6697     ChessSquare piece, partner;
6698     ChessMove moveType;
6699     Boolean premove;
6700
6701     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6702     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6703
6704     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6705       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6706         return FALSE;
6707
6708     if(legal[toY][toX] == 4) return FALSE;
6709
6710     piece = boards[currentMove][fromY][fromX];
6711     if(gameInfo.variant == VariantChu) {
6712         promotionZoneSize = BOARD_HEIGHT/3;
6713         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6714         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6715     } else if(gameInfo.variant == VariantShogi) {
6716         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6717         highestPromotingPiece = (int)WhiteAlfil;
6718     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6719         promotionZoneSize = 3;
6720     }
6721
6722     // Treat Lance as Pawn when it is not representing Amazon or Lance
6723     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6724         if(piece == WhiteLance) piece = WhitePawn; else
6725         if(piece == BlackLance) piece = BlackPawn;
6726     }
6727
6728     // next weed out all moves that do not touch the promotion zone at all
6729     if((int)piece >= BlackPawn) {
6730         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6731              return FALSE;
6732         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6733         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6734     } else {
6735         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6736            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6737         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6738              return FALSE;
6739     }
6740
6741     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6742
6743     // weed out mandatory Shogi promotions
6744     if(gameInfo.variant == VariantShogi) {
6745         if(piece >= BlackPawn) {
6746             if(toY == 0 && piece == BlackPawn ||
6747                toY == 0 && piece == BlackQueen ||
6748                toY <= 1 && piece == BlackKnight) {
6749                 *promoChoice = '+';
6750                 return FALSE;
6751             }
6752         } else {
6753             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6754                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6755                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6756                 *promoChoice = '+';
6757                 return FALSE;
6758             }
6759         }
6760     }
6761
6762     // weed out obviously illegal Pawn moves
6763     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6764         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6765         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6766         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6767         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6768         // note we are not allowed to test for valid (non-)capture, due to premove
6769     }
6770
6771     // we either have a choice what to promote to, or (in Shogi) whether to promote
6772     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6773        gameInfo.variant == VariantMakruk) {
6774         ChessSquare p=BlackFerz;  // no choice
6775         while(p < EmptySquare) {  //but make sure we use piece that exists
6776             *promoChoice = PieceToChar(p++);
6777             if(*promoChoice != '.') break;
6778         }
6779         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6780     }
6781     // no sense asking what we must promote to if it is going to explode...
6782     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6783         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6784         return FALSE;
6785     }
6786     // give caller the default choice even if we will not make it
6787     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6788     partner = piece; // pieces can promote if the pieceToCharTable says so
6789     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6790     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6791     if(        sweepSelect && gameInfo.variant != VariantGreat
6792                            && gameInfo.variant != VariantGrand
6793                            && gameInfo.variant != VariantSuper) return FALSE;
6794     if(autoQueen) return FALSE; // predetermined
6795
6796     // suppress promotion popup on illegal moves that are not premoves
6797     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6798               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6799     if(appData.testLegality && !premove) {
6800         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6802         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6803         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6804             return FALSE;
6805     }
6806
6807     return TRUE;
6808 }
6809
6810 int
6811 InPalace (int row, int column)
6812 {   /* [HGM] for Xiangqi */
6813     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6814          column < (BOARD_WIDTH + 4)/2 &&
6815          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6816     return FALSE;
6817 }
6818
6819 int
6820 PieceForSquare (int x, int y)
6821 {
6822   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6823      return -1;
6824   else
6825      return boards[currentMove][y][x];
6826 }
6827
6828 int
6829 OKToStartUserMove (int x, int y)
6830 {
6831     ChessSquare from_piece;
6832     int white_piece;
6833
6834     if (matchMode) return FALSE;
6835     if (gameMode == EditPosition) return TRUE;
6836
6837     if (x >= 0 && y >= 0)
6838       from_piece = boards[currentMove][y][x];
6839     else
6840       from_piece = EmptySquare;
6841
6842     if (from_piece == EmptySquare) return FALSE;
6843
6844     white_piece = (int)from_piece >= (int)WhitePawn &&
6845       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6846
6847     switch (gameMode) {
6848       case AnalyzeFile:
6849       case TwoMachinesPlay:
6850       case EndOfGame:
6851         return FALSE;
6852
6853       case IcsObserving:
6854       case IcsIdle:
6855         return FALSE;
6856
6857       case MachinePlaysWhite:
6858       case IcsPlayingBlack:
6859         if (appData.zippyPlay) return FALSE;
6860         if (white_piece) {
6861             DisplayMoveError(_("You are playing Black"));
6862             return FALSE;
6863         }
6864         break;
6865
6866       case MachinePlaysBlack:
6867       case IcsPlayingWhite:
6868         if (appData.zippyPlay) return FALSE;
6869         if (!white_piece) {
6870             DisplayMoveError(_("You are playing White"));
6871             return FALSE;
6872         }
6873         break;
6874
6875       case PlayFromGameFile:
6876             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6877       case EditGame:
6878       case AnalyzeMode:
6879         if (!white_piece && WhiteOnMove(currentMove)) {
6880             DisplayMoveError(_("It is White's turn"));
6881             return FALSE;
6882         }
6883         if (white_piece && !WhiteOnMove(currentMove)) {
6884             DisplayMoveError(_("It is Black's turn"));
6885             return FALSE;
6886         }
6887         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6888             /* Editing correspondence game history */
6889             /* Could disallow this or prompt for confirmation */
6890             cmailOldMove = -1;
6891         }
6892         break;
6893
6894       case BeginningOfGame:
6895         if (appData.icsActive) return FALSE;
6896         if (!appData.noChessProgram) {
6897             if (!white_piece) {
6898                 DisplayMoveError(_("You are playing White"));
6899                 return FALSE;
6900             }
6901         }
6902         break;
6903
6904       case Training:
6905         if (!white_piece && WhiteOnMove(currentMove)) {
6906             DisplayMoveError(_("It is White's turn"));
6907             return FALSE;
6908         }
6909         if (white_piece && !WhiteOnMove(currentMove)) {
6910             DisplayMoveError(_("It is Black's turn"));
6911             return FALSE;
6912         }
6913         break;
6914
6915       default:
6916       case IcsExamining:
6917         break;
6918     }
6919     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6920         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6921         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6922         && gameMode != AnalyzeFile && gameMode != Training) {
6923         DisplayMoveError(_("Displayed position is not current"));
6924         return FALSE;
6925     }
6926     return TRUE;
6927 }
6928
6929 Boolean
6930 OnlyMove (int *x, int *y, Boolean captures)
6931 {
6932     DisambiguateClosure cl;
6933     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6934     switch(gameMode) {
6935       case MachinePlaysBlack:
6936       case IcsPlayingWhite:
6937       case BeginningOfGame:
6938         if(!WhiteOnMove(currentMove)) return FALSE;
6939         break;
6940       case MachinePlaysWhite:
6941       case IcsPlayingBlack:
6942         if(WhiteOnMove(currentMove)) return FALSE;
6943         break;
6944       case EditGame:
6945         break;
6946       default:
6947         return FALSE;
6948     }
6949     cl.pieceIn = EmptySquare;
6950     cl.rfIn = *y;
6951     cl.ffIn = *x;
6952     cl.rtIn = -1;
6953     cl.ftIn = -1;
6954     cl.promoCharIn = NULLCHAR;
6955     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6956     if( cl.kind == NormalMove ||
6957         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6958         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6959         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6960       fromX = cl.ff;
6961       fromY = cl.rf;
6962       *x = cl.ft;
6963       *y = cl.rt;
6964       return TRUE;
6965     }
6966     if(cl.kind != ImpossibleMove) return FALSE;
6967     cl.pieceIn = EmptySquare;
6968     cl.rfIn = -1;
6969     cl.ffIn = -1;
6970     cl.rtIn = *y;
6971     cl.ftIn = *x;
6972     cl.promoCharIn = NULLCHAR;
6973     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6974     if( cl.kind == NormalMove ||
6975         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6976         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6977         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6978       fromX = cl.ff;
6979       fromY = cl.rf;
6980       *x = cl.ft;
6981       *y = cl.rt;
6982       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6983       return TRUE;
6984     }
6985     return FALSE;
6986 }
6987
6988 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6989 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6990 int lastLoadGameUseList = FALSE;
6991 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6992 ChessMove lastLoadGameStart = EndOfFile;
6993 int doubleClick;
6994 Boolean addToBookFlag;
6995 static Board rightsBoard, nullBoard;
6996
6997 void
6998 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6999 {
7000     ChessMove moveType;
7001     ChessSquare pup;
7002     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7003
7004     /* Check if the user is playing in turn.  This is complicated because we
7005        let the user "pick up" a piece before it is his turn.  So the piece he
7006        tried to pick up may have been captured by the time he puts it down!
7007        Therefore we use the color the user is supposed to be playing in this
7008        test, not the color of the piece that is currently on the starting
7009        square---except in EditGame mode, where the user is playing both
7010        sides; fortunately there the capture race can't happen.  (It can
7011        now happen in IcsExamining mode, but that's just too bad.  The user
7012        will get a somewhat confusing message in that case.)
7013        */
7014
7015     switch (gameMode) {
7016       case AnalyzeFile:
7017       case TwoMachinesPlay:
7018       case EndOfGame:
7019       case IcsObserving:
7020       case IcsIdle:
7021         /* We switched into a game mode where moves are not accepted,
7022            perhaps while the mouse button was down. */
7023         return;
7024
7025       case MachinePlaysWhite:
7026         /* User is moving for Black */
7027         if (WhiteOnMove(currentMove)) {
7028             DisplayMoveError(_("It is White's turn"));
7029             return;
7030         }
7031         break;
7032
7033       case MachinePlaysBlack:
7034         /* User is moving for White */
7035         if (!WhiteOnMove(currentMove)) {
7036             DisplayMoveError(_("It is Black's turn"));
7037             return;
7038         }
7039         break;
7040
7041       case PlayFromGameFile:
7042             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7043       case EditGame:
7044       case IcsExamining:
7045       case BeginningOfGame:
7046       case AnalyzeMode:
7047       case Training:
7048         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7049         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7050             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7051             /* User is moving for Black */
7052             if (WhiteOnMove(currentMove)) {
7053                 DisplayMoveError(_("It is White's turn"));
7054                 return;
7055             }
7056         } else {
7057             /* User is moving for White */
7058             if (!WhiteOnMove(currentMove)) {
7059                 DisplayMoveError(_("It is Black's turn"));
7060                 return;
7061             }
7062         }
7063         break;
7064
7065       case IcsPlayingBlack:
7066         /* User is moving for Black */
7067         if (WhiteOnMove(currentMove)) {
7068             if (!appData.premove) {
7069                 DisplayMoveError(_("It is White's turn"));
7070             } else if (toX >= 0 && toY >= 0) {
7071                 premoveToX = toX;
7072                 premoveToY = toY;
7073                 premoveFromX = fromX;
7074                 premoveFromY = fromY;
7075                 premovePromoChar = promoChar;
7076                 gotPremove = 1;
7077                 if (appData.debugMode)
7078                     fprintf(debugFP, "Got premove: fromX %d,"
7079                             "fromY %d, toX %d, toY %d\n",
7080                             fromX, fromY, toX, toY);
7081             }
7082             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7083             return;
7084         }
7085         break;
7086
7087       case IcsPlayingWhite:
7088         /* User is moving for White */
7089         if (!WhiteOnMove(currentMove)) {
7090             if (!appData.premove) {
7091                 DisplayMoveError(_("It is Black's turn"));
7092             } else if (toX >= 0 && toY >= 0) {
7093                 premoveToX = toX;
7094                 premoveToY = toY;
7095                 premoveFromX = fromX;
7096                 premoveFromY = fromY;
7097                 premovePromoChar = promoChar;
7098                 gotPremove = 1;
7099                 if (appData.debugMode)
7100                     fprintf(debugFP, "Got premove: fromX %d,"
7101                             "fromY %d, toX %d, toY %d\n",
7102                             fromX, fromY, toX, toY);
7103             }
7104             DrawPosition(TRUE, boards[currentMove]);
7105             return;
7106         }
7107         break;
7108
7109       default:
7110         break;
7111
7112       case EditPosition:
7113         /* EditPosition, empty square, or different color piece;
7114            click-click move is possible */
7115         if (toX == -2 || toY == -2) {
7116             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7117             DrawPosition(FALSE, boards[currentMove]);
7118             return;
7119         } else if (toX >= 0 && toY >= 0) {
7120             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7121                 ChessSquare p = boards[0][rf][ff];
7122                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7123                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7124                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7125                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7126                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7127                     gatingPiece = p;
7128                 }
7129             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7130             boards[0][toY][toX] = boards[0][fromY][fromX];
7131             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7132                 if(boards[0][fromY][0] != EmptySquare) {
7133                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7134                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7135                 }
7136             } else
7137             if(fromX == BOARD_RGHT+1) {
7138                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7139                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7140                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7141                 }
7142             } else
7143             boards[0][fromY][fromX] = gatingPiece;
7144             ClearHighlights();
7145             DrawPosition(FALSE, boards[currentMove]);
7146             return;
7147         }
7148         return;
7149     }
7150
7151     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7152     pup = boards[currentMove][toY][toX];
7153
7154     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7155     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7156          if( pup != EmptySquare ) return;
7157          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7158            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7159                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7160            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7161            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7162            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7163            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7164          fromY = DROP_RANK;
7165     }
7166
7167     /* [HGM] always test for legality, to get promotion info */
7168     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7169                                          fromY, fromX, toY, toX, promoChar);
7170
7171     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7172
7173     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7174
7175     /* [HGM] but possibly ignore an IllegalMove result */
7176     if (appData.testLegality) {
7177         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7178             DisplayMoveError(_("Illegal move"));
7179             return;
7180         }
7181     }
7182
7183     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7184         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7185              ClearPremoveHighlights(); // was included
7186         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7187         DrawPosition(FALSE, NULL);
7188         return;
7189     }
7190
7191     if(addToBookFlag) { // adding moves to book
7192         char buf[MSG_SIZ], move[MSG_SIZ];
7193         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7194         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7195                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7196         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7197         AddBookMove(buf);
7198         addToBookFlag = FALSE;
7199         ClearHighlights();
7200         return;
7201     }
7202
7203     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7204 }
7205
7206 /* Common tail of UserMoveEvent and DropMenuEvent */
7207 int
7208 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7209 {
7210     char *bookHit = 0;
7211
7212     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7213         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7214         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7215         if(WhiteOnMove(currentMove)) {
7216             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7217         } else {
7218             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7219         }
7220     }
7221
7222     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7223        move type in caller when we know the move is a legal promotion */
7224     if(moveType == NormalMove && promoChar)
7225         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7226
7227     /* [HGM] <popupFix> The following if has been moved here from
7228        UserMoveEvent(). Because it seemed to belong here (why not allow
7229        piece drops in training games?), and because it can only be
7230        performed after it is known to what we promote. */
7231     if (gameMode == Training) {
7232       /* compare the move played on the board to the next move in the
7233        * game. If they match, display the move and the opponent's response.
7234        * If they don't match, display an error message.
7235        */
7236       int saveAnimate;
7237       Board testBoard;
7238       CopyBoard(testBoard, boards[currentMove]);
7239       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7240
7241       if (CompareBoards(testBoard, boards[currentMove+1])) {
7242         ForwardInner(currentMove+1);
7243
7244         /* Autoplay the opponent's response.
7245          * if appData.animate was TRUE when Training mode was entered,
7246          * the response will be animated.
7247          */
7248         saveAnimate = appData.animate;
7249         appData.animate = animateTraining;
7250         ForwardInner(currentMove+1);
7251         appData.animate = saveAnimate;
7252
7253         /* check for the end of the game */
7254         if (currentMove >= forwardMostMove) {
7255           gameMode = PlayFromGameFile;
7256           ModeHighlight();
7257           SetTrainingModeOff();
7258           DisplayInformation(_("End of game"));
7259         }
7260       } else {
7261         DisplayError(_("Incorrect move"), 0);
7262       }
7263       return 1;
7264     }
7265
7266   /* Ok, now we know that the move is good, so we can kill
7267      the previous line in Analysis Mode */
7268   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7269                                 && currentMove < forwardMostMove) {
7270     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7271     else forwardMostMove = currentMove;
7272   }
7273
7274   ClearMap();
7275
7276   /* If we need the chess program but it's dead, restart it */
7277   ResurrectChessProgram();
7278
7279   /* A user move restarts a paused game*/
7280   if (pausing)
7281     PauseEvent();
7282
7283   thinkOutput[0] = NULLCHAR;
7284
7285   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7286
7287   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7288     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7289     return 1;
7290   }
7291
7292   if (gameMode == BeginningOfGame) {
7293     if (appData.noChessProgram) {
7294       gameMode = EditGame;
7295       SetGameInfo();
7296     } else {
7297       char buf[MSG_SIZ];
7298       gameMode = MachinePlaysBlack;
7299       StartClocks();
7300       SetGameInfo();
7301       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7302       DisplayTitle(buf);
7303       if (first.sendName) {
7304         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7305         SendToProgram(buf, &first);
7306       }
7307       StartClocks();
7308     }
7309     ModeHighlight();
7310   }
7311
7312   /* Relay move to ICS or chess engine */
7313   if (appData.icsActive) {
7314     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7315         gameMode == IcsExamining) {
7316       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7317         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7318         SendToICS("draw ");
7319         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7320       }
7321       // also send plain move, in case ICS does not understand atomic claims
7322       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7323       ics_user_moved = 1;
7324     }
7325   } else {
7326     if (first.sendTime && (gameMode == BeginningOfGame ||
7327                            gameMode == MachinePlaysWhite ||
7328                            gameMode == MachinePlaysBlack)) {
7329       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7330     }
7331     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7332          // [HGM] book: if program might be playing, let it use book
7333         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7334         first.maybeThinking = TRUE;
7335     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7336         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7337         SendBoard(&first, currentMove+1);
7338         if(second.analyzing) {
7339             if(!second.useSetboard) SendToProgram("undo\n", &second);
7340             SendBoard(&second, currentMove+1);
7341         }
7342     } else {
7343         SendMoveToProgram(forwardMostMove-1, &first);
7344         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7345     }
7346     if (currentMove == cmailOldMove + 1) {
7347       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7348     }
7349   }
7350
7351   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7352
7353   switch (gameMode) {
7354   case EditGame:
7355     if(appData.testLegality)
7356     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7357     case MT_NONE:
7358     case MT_CHECK:
7359       break;
7360     case MT_CHECKMATE:
7361     case MT_STAINMATE:
7362       if (WhiteOnMove(currentMove)) {
7363         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7364       } else {
7365         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7366       }
7367       break;
7368     case MT_STALEMATE:
7369       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7370       break;
7371     }
7372     break;
7373
7374   case MachinePlaysBlack:
7375   case MachinePlaysWhite:
7376     /* disable certain menu options while machine is thinking */
7377     SetMachineThinkingEnables();
7378     break;
7379
7380   default:
7381     break;
7382   }
7383
7384   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7385   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7386
7387   if(bookHit) { // [HGM] book: simulate book reply
7388         static char bookMove[MSG_SIZ]; // a bit generous?
7389
7390         programStats.nodes = programStats.depth = programStats.time =
7391         programStats.score = programStats.got_only_move = 0;
7392         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7393
7394         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7395         strcat(bookMove, bookHit);
7396         HandleMachineMove(bookMove, &first);
7397   }
7398   return 1;
7399 }
7400
7401 void
7402 MarkByFEN(char *fen)
7403 {
7404         int r, f;
7405         if(!appData.markers || !appData.highlightDragging) return;
7406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7407         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7408         while(*fen) {
7409             int s = 0;
7410             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7411             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7412             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7413             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7414             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7415             if(*fen == 'T') marker[r][f++] = 0; else
7416             if(*fen == 'Y') marker[r][f++] = 1; else
7417             if(*fen == 'G') marker[r][f++] = 3; else
7418             if(*fen == 'B') marker[r][f++] = 4; else
7419             if(*fen == 'C') marker[r][f++] = 5; else
7420             if(*fen == 'M') marker[r][f++] = 6; else
7421             if(*fen == 'W') marker[r][f++] = 7; else
7422             if(*fen == 'D') marker[r][f++] = 8; else
7423             if(*fen == 'R') marker[r][f++] = 2; else {
7424                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7425               f += s; fen -= s>0;
7426             }
7427             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7428             if(r < 0) break;
7429             fen++;
7430         }
7431         DrawPosition(TRUE, NULL);
7432 }
7433
7434 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7435
7436 void
7437 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7438 {
7439     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7440     Markers *m = (Markers *) closure;
7441     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7442                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7443         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7444                          || kind == WhiteCapturesEnPassant
7445                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7446     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7447 }
7448
7449 static int hoverSavedValid;
7450
7451 void
7452 MarkTargetSquares (int clear)
7453 {
7454   int x, y, sum=0;
7455   if(clear) { // no reason to ever suppress clearing
7456     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7457     hoverSavedValid = 0;
7458     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7459   } else {
7460     int capt = 0;
7461     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7462        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7463     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7464     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7465       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7466       if(capt)
7467       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7468     }
7469   }
7470   DrawPosition(FALSE, NULL);
7471 }
7472
7473 int
7474 Explode (Board board, int fromX, int fromY, int toX, int toY)
7475 {
7476     if(gameInfo.variant == VariantAtomic &&
7477        (board[toY][toX] != EmptySquare ||                     // capture?
7478         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7479                          board[fromY][fromX] == BlackPawn   )
7480       )) {
7481         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7482         return TRUE;
7483     }
7484     return FALSE;
7485 }
7486
7487 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7488
7489 int
7490 CanPromote (ChessSquare piece, int y)
7491 {
7492         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7493         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7494         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7495         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7496            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7497           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7498            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7499         return (piece == BlackPawn && y <= zone ||
7500                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7501                 piece == BlackLance && y <= zone ||
7502                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7503 }
7504
7505 void
7506 HoverEvent (int xPix, int yPix, int x, int y)
7507 {
7508         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7509         int r, f;
7510         if(!first.highlight) return;
7511         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7512         if(x == oldX && y == oldY) return; // only do something if we enter new square
7513         oldFromX = fromX; oldFromY = fromY;
7514         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7515           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7516             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7517           hoverSavedValid = 1;
7518         } else if(oldX != x || oldY != y) {
7519           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7520           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7521           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7522             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7523           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7524             char buf[MSG_SIZ];
7525             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7526             SendToProgram(buf, &first);
7527           }
7528           oldX = x; oldY = y;
7529 //        SetHighlights(fromX, fromY, x, y);
7530         }
7531 }
7532
7533 void ReportClick(char *action, int x, int y)
7534 {
7535         char buf[MSG_SIZ]; // Inform engine of what user does
7536         int r, f;
7537         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7538           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7539             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7540         if(!first.highlight || gameMode == EditPosition) return;
7541         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7542         SendToProgram(buf, &first);
7543 }
7544
7545 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7546 Boolean deferChoice;
7547
7548 void
7549 LeftClick (ClickType clickType, int xPix, int yPix)
7550 {
7551     int x, y;
7552     static Boolean saveAnimate;
7553     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7554     char promoChoice = NULLCHAR;
7555     ChessSquare piece;
7556     static TimeMark lastClickTime, prevClickTime;
7557
7558     if(flashing) return;
7559
7560   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7561     x = EventToSquare(xPix, BOARD_WIDTH);
7562     y = EventToSquare(yPix, BOARD_HEIGHT);
7563     if (!flipView && y >= 0) {
7564         y = BOARD_HEIGHT - 1 - y;
7565     }
7566     if (flipView && x >= 0) {
7567         x = BOARD_WIDTH - 1 - x;
7568     }
7569
7570     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7571         static int dummy;
7572         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7573         right = TRUE;
7574         return;
7575     }
7576
7577     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7578
7579     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7580
7581     if (clickType == Press) ErrorPopDown();
7582     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7583
7584     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7585         defaultPromoChoice = promoSweep;
7586         promoSweep = EmptySquare;   // terminate sweep
7587         promoDefaultAltered = TRUE;
7588         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7589     }
7590
7591     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7592         if(clickType == Release) return; // ignore upclick of click-click destination
7593         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7594         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7595         if(gameInfo.holdingsWidth &&
7596                 (WhiteOnMove(currentMove)
7597                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7598                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7599             // click in right holdings, for determining promotion piece
7600             ChessSquare p = boards[currentMove][y][x];
7601             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7602             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7603             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7604                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7605                 fromX = fromY = -1;
7606                 return;
7607             }
7608         }
7609         DrawPosition(FALSE, boards[currentMove]);
7610         return;
7611     }
7612
7613     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7614     if(clickType == Press
7615             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7616               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7617               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7618         return;
7619
7620     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7621         // could be static click on premove from-square: abort premove
7622         gotPremove = 0;
7623         ClearPremoveHighlights();
7624     }
7625
7626     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7627         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7628
7629     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7630         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7631                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7632         defaultPromoChoice = DefaultPromoChoice(side);
7633     }
7634
7635     autoQueen = appData.alwaysPromoteToQueen;
7636
7637     if (fromX == -1) {
7638       int originalY = y;
7639       gatingPiece = EmptySquare;
7640       if (clickType != Press) {
7641         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7642             DragPieceEnd(xPix, yPix); dragging = 0;
7643             DrawPosition(FALSE, NULL);
7644         }
7645         return;
7646       }
7647       doubleClick = FALSE;
7648       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7649         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7650       }
7651       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7652       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7653          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7654          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7655             /* First square */
7656             if (OKToStartUserMove(fromX, fromY)) {
7657                 second = 0;
7658                 ReportClick("lift", x, y);
7659                 MarkTargetSquares(0);
7660                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7661                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7662                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7663                     promoSweep = defaultPromoChoice;
7664                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7665                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7666                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7667                 }
7668                 if (appData.highlightDragging) {
7669                     SetHighlights(fromX, fromY, -1, -1);
7670                 } else {
7671                     ClearHighlights();
7672                 }
7673             } else fromX = fromY = -1;
7674             return;
7675         }
7676     }
7677
7678     /* fromX != -1 */
7679     if (clickType == Press && gameMode != EditPosition) {
7680         ChessSquare fromP;
7681         ChessSquare toP;
7682         int frc;
7683
7684         // ignore off-board to clicks
7685         if(y < 0 || x < 0) return;
7686
7687         /* Check if clicking again on the same color piece */
7688         fromP = boards[currentMove][fromY][fromX];
7689         toP = boards[currentMove][y][x];
7690         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7691         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7692             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7693            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7694              WhitePawn <= toP && toP <= WhiteKing &&
7695              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7696              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7697             (BlackPawn <= fromP && fromP <= BlackKing &&
7698              BlackPawn <= toP && toP <= BlackKing &&
7699              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7700              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7701             /* Clicked again on same color piece -- changed his mind */
7702             second = (x == fromX && y == fromY);
7703             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7704             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7705                 second = FALSE; // first double-click rather than scond click
7706                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7707             }
7708             promoDefaultAltered = FALSE;
7709            if(!second) MarkTargetSquares(1);
7710            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7711             if (appData.highlightDragging) {
7712                 SetHighlights(x, y, -1, -1);
7713             } else {
7714                 ClearHighlights();
7715             }
7716             if (OKToStartUserMove(x, y)) {
7717                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7718                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7719                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7720                  gatingPiece = boards[currentMove][fromY][fromX];
7721                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7722                 fromX = x;
7723                 fromY = y; dragging = 1;
7724                 if(!second) ReportClick("lift", x, y);
7725                 MarkTargetSquares(0);
7726                 DragPieceBegin(xPix, yPix, FALSE);
7727                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7728                     promoSweep = defaultPromoChoice;
7729                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7730                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7731                 }
7732             }
7733            }
7734            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7735            second = FALSE;
7736         }
7737         // ignore clicks on holdings
7738         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7739     }
7740
7741     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7742         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7743         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7744         return;
7745     }
7746
7747     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7748         DragPieceEnd(xPix, yPix); dragging = 0;
7749         if(clearFlag) {
7750             // a deferred attempt to click-click move an empty square on top of a piece
7751             boards[currentMove][y][x] = EmptySquare;
7752             ClearHighlights();
7753             DrawPosition(FALSE, boards[currentMove]);
7754             fromX = fromY = -1; clearFlag = 0;
7755             return;
7756         }
7757         if (appData.animateDragging) {
7758             /* Undo animation damage if any */
7759             DrawPosition(FALSE, NULL);
7760         }
7761         if (second) {
7762             /* Second up/down in same square; just abort move */
7763             second = 0;
7764             fromX = fromY = -1;
7765             gatingPiece = EmptySquare;
7766             ClearHighlights();
7767             gotPremove = 0;
7768             ClearPremoveHighlights();
7769             MarkTargetSquares(-1);
7770             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7771         } else {
7772             /* First upclick in same square; start click-click mode */
7773             SetHighlights(x, y, -1, -1);
7774         }
7775         return;
7776     }
7777
7778     clearFlag = 0;
7779
7780     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7781        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7782         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7783         DisplayMessage(_("only marked squares are legal"),"");
7784         DrawPosition(TRUE, NULL);
7785         return; // ignore to-click
7786     }
7787
7788     /* we now have a different from- and (possibly off-board) to-square */
7789     /* Completed move */
7790     if(!sweepSelecting) {
7791         toX = x;
7792         toY = y;
7793     }
7794
7795     piece = boards[currentMove][fromY][fromX];
7796
7797     saveAnimate = appData.animate;
7798     if (clickType == Press) {
7799         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7800         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7801             // must be Edit Position mode with empty-square selected
7802             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7803             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7804             return;
7805         }
7806         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7807             return;
7808         }
7809         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7810             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7811         } else
7812         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7813         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7814           if(appData.sweepSelect) {
7815             promoSweep = defaultPromoChoice;
7816             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7817             selectFlag = 0; lastX = xPix; lastY = yPix;
7818             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7819             saveFlash = appData.flashCount; appData.flashCount = 0;
7820             Sweep(0); // Pawn that is going to promote: preview promotion piece
7821             sweepSelecting = 1;
7822             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7823             MarkTargetSquares(1);
7824           }
7825           return; // promo popup appears on up-click
7826         }
7827         /* Finish clickclick move */
7828         if (appData.animate || appData.highlightLastMove) {
7829             SetHighlights(fromX, fromY, toX, toY);
7830         } else {
7831             ClearHighlights();
7832         }
7833         MarkTargetSquares(1);
7834     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7835         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7836         *promoRestrict = 0; appData.flashCount = saveFlash;
7837         if (appData.animate || appData.highlightLastMove) {
7838             SetHighlights(fromX, fromY, toX, toY);
7839         } else {
7840             ClearHighlights();
7841         }
7842         MarkTargetSquares(1);
7843     } else {
7844 #if 0
7845 // [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
7846         /* Finish drag move */
7847         if (appData.highlightLastMove) {
7848             SetHighlights(fromX, fromY, toX, toY);
7849         } else {
7850             ClearHighlights();
7851         }
7852 #endif
7853         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7854           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7855         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7856         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7857           dragging *= 2;            // flag button-less dragging if we are dragging
7858           MarkTargetSquares(1);
7859           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7860           else {
7861             kill2X = killX; kill2Y = killY;
7862             killX = x; killY = y;     // remember this square as intermediate
7863             ReportClick("put", x, y); // and inform engine
7864             ReportClick("lift", x, y);
7865             MarkTargetSquares(0);
7866             return;
7867           }
7868         }
7869         DragPieceEnd(xPix, yPix); dragging = 0;
7870         /* Don't animate move and drag both */
7871         appData.animate = FALSE;
7872         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7873     }
7874
7875     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7876     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7877         ChessSquare piece = boards[currentMove][fromY][fromX];
7878         if(gameMode == EditPosition && piece != EmptySquare &&
7879            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7880             int n;
7881
7882             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7883                 n = PieceToNumber(piece - (int)BlackPawn);
7884                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7885                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7886                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7887             } else
7888             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7889                 n = PieceToNumber(piece);
7890                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7891                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7892                 boards[currentMove][n][BOARD_WIDTH-2]++;
7893             }
7894             boards[currentMove][fromY][fromX] = EmptySquare;
7895         }
7896         ClearHighlights();
7897         fromX = fromY = -1;
7898         MarkTargetSquares(1);
7899         DrawPosition(TRUE, boards[currentMove]);
7900         return;
7901     }
7902
7903     // off-board moves should not be highlighted
7904     if(x < 0 || y < 0) {
7905         ClearHighlights();
7906         DrawPosition(FALSE, NULL);
7907     } else ReportClick("put", x, y);
7908
7909     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7910  }
7911
7912     if(legal[toY][toX] == 2) { // highlight-induced promotion
7913         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7914         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7915     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7916       if(!*promoRestrict) {           // but has not done that yet
7917         deferChoice = TRUE;           // set up retry for when it does
7918         return;                       // and wait for that
7919       }
7920       promoChoice = ToLower(*promoRestrict); // force engine's choice
7921       deferChoice = FALSE;
7922     }
7923
7924     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7925         SetHighlights(fromX, fromY, toX, toY);
7926         MarkTargetSquares(1);
7927         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7928             // [HGM] super: promotion to captured piece selected from holdings
7929             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7930             promotionChoice = TRUE;
7931             // kludge follows to temporarily execute move on display, without promoting yet
7932             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7933             boards[currentMove][toY][toX] = p;
7934             DrawPosition(FALSE, boards[currentMove]);
7935             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7936             boards[currentMove][toY][toX] = q;
7937             DisplayMessage("Click in holdings to choose piece", "");
7938             return;
7939         }
7940         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7941         PromotionPopUp(promoChoice);
7942     } else {
7943         int oldMove = currentMove;
7944         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7945         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7946         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7947         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7948         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7949            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7950             DrawPosition(TRUE, boards[currentMove]);
7951         else DrawPosition(FALSE, NULL);
7952         fromX = fromY = -1;
7953         flashing = 0;
7954     }
7955     appData.animate = saveAnimate;
7956     if (appData.animate || appData.animateDragging) {
7957         /* Undo animation damage if needed */
7958 //      DrawPosition(FALSE, NULL);
7959     }
7960 }
7961
7962 int
7963 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7964 {   // front-end-free part taken out of PieceMenuPopup
7965     int whichMenu; int xSqr, ySqr;
7966
7967     if(seekGraphUp) { // [HGM] seekgraph
7968         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7969         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7970         return -2;
7971     }
7972
7973     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7974          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7975         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7976         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7977         if(action == Press)   {
7978             originalFlip = flipView;
7979             flipView = !flipView; // temporarily flip board to see game from partners perspective
7980             DrawPosition(TRUE, partnerBoard);
7981             DisplayMessage(partnerStatus, "");
7982             partnerUp = TRUE;
7983         } else if(action == Release) {
7984             flipView = originalFlip;
7985             DrawPosition(TRUE, boards[currentMove]);
7986             partnerUp = FALSE;
7987         }
7988         return -2;
7989     }
7990
7991     xSqr = EventToSquare(x, BOARD_WIDTH);
7992     ySqr = EventToSquare(y, BOARD_HEIGHT);
7993     if (action == Release) {
7994         if(pieceSweep != EmptySquare) {
7995             EditPositionMenuEvent(pieceSweep, toX, toY);
7996             pieceSweep = EmptySquare;
7997         } else UnLoadPV(); // [HGM] pv
7998     }
7999     if (action != Press) return -2; // return code to be ignored
8000     switch (gameMode) {
8001       case IcsExamining:
8002         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8003       case EditPosition:
8004         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8005         if (xSqr < 0 || ySqr < 0) return -1;
8006         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8007         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8008         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8009         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8010         NextPiece(0);
8011         return 2; // grab
8012       case IcsObserving:
8013         if(!appData.icsEngineAnalyze) return -1;
8014       case IcsPlayingWhite:
8015       case IcsPlayingBlack:
8016         if(!appData.zippyPlay) goto noZip;
8017       case AnalyzeMode:
8018       case AnalyzeFile:
8019       case MachinePlaysWhite:
8020       case MachinePlaysBlack:
8021       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8022         if (!appData.dropMenu) {
8023           LoadPV(x, y);
8024           return 2; // flag front-end to grab mouse events
8025         }
8026         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8027            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8028       case EditGame:
8029       noZip:
8030         if (xSqr < 0 || ySqr < 0) return -1;
8031         if (!appData.dropMenu || appData.testLegality &&
8032             gameInfo.variant != VariantBughouse &&
8033             gameInfo.variant != VariantCrazyhouse) return -1;
8034         whichMenu = 1; // drop menu
8035         break;
8036       default:
8037         return -1;
8038     }
8039
8040     if (((*fromX = xSqr) < 0) ||
8041         ((*fromY = ySqr) < 0)) {
8042         *fromX = *fromY = -1;
8043         return -1;
8044     }
8045     if (flipView)
8046       *fromX = BOARD_WIDTH - 1 - *fromX;
8047     else
8048       *fromY = BOARD_HEIGHT - 1 - *fromY;
8049
8050     return whichMenu;
8051 }
8052
8053 void
8054 Wheel (int dir, int x, int y)
8055 {
8056     if(gameMode == EditPosition) {
8057         int xSqr = EventToSquare(x, BOARD_WIDTH);
8058         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8059         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8060         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8061         do {
8062             boards[currentMove][ySqr][xSqr] += dir;
8063             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8064             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8065         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8066         DrawPosition(FALSE, boards[currentMove]);
8067     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8068 }
8069
8070 void
8071 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8072 {
8073 //    char * hint = lastHint;
8074     FrontEndProgramStats stats;
8075
8076     stats.which = cps == &first ? 0 : 1;
8077     stats.depth = cpstats->depth;
8078     stats.nodes = cpstats->nodes;
8079     stats.score = cpstats->score;
8080     stats.time = cpstats->time;
8081     stats.pv = cpstats->movelist;
8082     stats.hint = lastHint;
8083     stats.an_move_index = 0;
8084     stats.an_move_count = 0;
8085
8086     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8087         stats.hint = cpstats->move_name;
8088         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8089         stats.an_move_count = cpstats->nr_moves;
8090     }
8091
8092     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
8093
8094     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8095         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8096
8097     SetProgramStats( &stats );
8098 }
8099
8100 void
8101 ClearEngineOutputPane (int which)
8102 {
8103     static FrontEndProgramStats dummyStats;
8104     dummyStats.which = which;
8105     dummyStats.pv = "#";
8106     SetProgramStats( &dummyStats );
8107 }
8108
8109 #define MAXPLAYERS 500
8110
8111 char *
8112 TourneyStandings (int display)
8113 {
8114     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8115     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8116     char result, *p, *names[MAXPLAYERS];
8117
8118     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8119         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8120     names[0] = p = strdup(appData.participants);
8121     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8122
8123     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8124
8125     while(result = appData.results[nr]) {
8126         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8127         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8128         wScore = bScore = 0;
8129         switch(result) {
8130           case '+': wScore = 2; break;
8131           case '-': bScore = 2; break;
8132           case '=': wScore = bScore = 1; break;
8133           case ' ':
8134           case '*': return strdup("busy"); // tourney not finished
8135         }
8136         score[w] += wScore;
8137         score[b] += bScore;
8138         games[w]++;
8139         games[b]++;
8140         nr++;
8141     }
8142     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8143     for(w=0; w<nPlayers; w++) {
8144         bScore = -1;
8145         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8146         ranking[w] = b; points[w] = bScore; score[b] = -2;
8147     }
8148     p = malloc(nPlayers*34+1);
8149     for(w=0; w<nPlayers && w<display; w++)
8150         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8151     free(names[0]);
8152     return p;
8153 }
8154
8155 void
8156 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8157 {       // count all piece types
8158         int p, f, r;
8159         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8160         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8161         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8162                 p = board[r][f];
8163                 pCnt[p]++;
8164                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8165                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8166                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8167                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8168                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8169                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8170         }
8171 }
8172
8173 int
8174 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8175 {
8176         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8177         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8178
8179         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8180         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8181         if(myPawns == 2 && nMine == 3) // KPP
8182             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8183         if(myPawns == 1 && nMine == 2) // KP
8184             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8185         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8186             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8187         if(myPawns) return FALSE;
8188         if(pCnt[WhiteRook+side])
8189             return pCnt[BlackRook-side] ||
8190                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8191                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8192                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8193         if(pCnt[WhiteCannon+side]) {
8194             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8195             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8196         }
8197         if(pCnt[WhiteKnight+side])
8198             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8199         return FALSE;
8200 }
8201
8202 int
8203 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8204 {
8205         VariantClass v = gameInfo.variant;
8206
8207         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8208         if(v == VariantShatranj) return TRUE; // always winnable through baring
8209         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8210         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8211
8212         if(v == VariantXiangqi) {
8213                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8214
8215                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8216                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8217                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8218                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8219                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8220                 if(stale) // we have at least one last-rank P plus perhaps C
8221                     return majors // KPKX
8222                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8223                 else // KCA*E*
8224                     return pCnt[WhiteFerz+side] // KCAK
8225                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8226                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8227                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8228
8229         } else if(v == VariantKnightmate) {
8230                 if(nMine == 1) return FALSE;
8231                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8232         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8233                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8234
8235                 if(nMine == 1) return FALSE; // bare King
8236                 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
8237                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8238                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8239                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8240                 if(pCnt[WhiteKnight+side])
8241                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8242                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8243                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8244                 if(nBishops)
8245                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8246                 if(pCnt[WhiteAlfil+side])
8247                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8248                 if(pCnt[WhiteWazir+side])
8249                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8250         }
8251
8252         return TRUE;
8253 }
8254
8255 int
8256 CompareWithRights (Board b1, Board b2)
8257 {
8258     int rights = 0;
8259     if(!CompareBoards(b1, b2)) return FALSE;
8260     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8261     /* compare castling rights */
8262     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8263            rights++; /* King lost rights, while rook still had them */
8264     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8265         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8266            rights++; /* but at least one rook lost them */
8267     }
8268     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8269            rights++;
8270     if( b1[CASTLING][5] != NoRights ) {
8271         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8272            rights++;
8273     }
8274     return rights == 0;
8275 }
8276
8277 int
8278 Adjudicate (ChessProgramState *cps)
8279 {       // [HGM] some adjudications useful with buggy engines
8280         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8281         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8282         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8283         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8284         int k, drop, count = 0; static int bare = 1;
8285         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8286         Boolean canAdjudicate = !appData.icsActive;
8287
8288         // most tests only when we understand the game, i.e. legality-checking on
8289             if( appData.testLegality )
8290             {   /* [HGM] Some more adjudications for obstinate engines */
8291                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8292                 static int moveCount = 6;
8293                 ChessMove result;
8294                 char *reason = NULL;
8295
8296                 /* Count what is on board. */
8297                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8298
8299                 /* Some material-based adjudications that have to be made before stalemate test */
8300                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8301                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8302                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8303                      if(canAdjudicate && appData.checkMates) {
8304                          if(engineOpponent)
8305                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8306                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8307                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8308                          return 1;
8309                      }
8310                 }
8311
8312                 /* Bare King in Shatranj (loses) or Losers (wins) */
8313                 if( nrW == 1 || nrB == 1) {
8314                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8315                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8316                      if(canAdjudicate && appData.checkMates) {
8317                          if(engineOpponent)
8318                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8319                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8320                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8321                          return 1;
8322                      }
8323                   } else
8324                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8325                   {    /* bare King */
8326                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8327                         if(canAdjudicate && appData.checkMates) {
8328                             /* but only adjudicate if adjudication enabled */
8329                             if(engineOpponent)
8330                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8331                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8332                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8333                             return 1;
8334                         }
8335                   }
8336                 } else bare = 1;
8337
8338
8339             // don't wait for engine to announce game end if we can judge ourselves
8340             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8341               case MT_CHECK:
8342                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8343                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8344                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8345                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8346                             checkCnt++;
8347                         if(checkCnt >= 2) {
8348                             reason = "Xboard adjudication: 3rd check";
8349                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8350                             break;
8351                         }
8352                     }
8353                 }
8354               case MT_NONE:
8355               default:
8356                 break;
8357               case MT_STEALMATE:
8358               case MT_STALEMATE:
8359               case MT_STAINMATE:
8360                 reason = "Xboard adjudication: Stalemate";
8361                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8362                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8363                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8364                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8365                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8366                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8367                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8368                                                                         EP_CHECKMATE : EP_WINS);
8369                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8370                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8371                 }
8372                 break;
8373               case MT_CHECKMATE:
8374                 reason = "Xboard adjudication: Checkmate";
8375                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8376                 if(gameInfo.variant == VariantShogi) {
8377                     if(forwardMostMove > backwardMostMove
8378                        && moveList[forwardMostMove-1][1] == '@'
8379                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8380                         reason = "XBoard adjudication: pawn-drop mate";
8381                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8382                     }
8383                 }
8384                 break;
8385             }
8386
8387                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8388                     case EP_STALEMATE:
8389                         result = GameIsDrawn; break;
8390                     case EP_CHECKMATE:
8391                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8392                     case EP_WINS:
8393                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8394                     default:
8395                         result = EndOfFile;
8396                 }
8397                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8398                     if(engineOpponent)
8399                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400                     GameEnds( result, reason, GE_XBOARD );
8401                     return 1;
8402                 }
8403
8404                 /* Next absolutely insufficient mating material. */
8405                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8406                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8407                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8408
8409                      /* always flag draws, for judging claims */
8410                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8411
8412                      if(canAdjudicate && appData.materialDraws) {
8413                          /* but only adjudicate them if adjudication enabled */
8414                          if(engineOpponent) {
8415                            SendToProgram("force\n", engineOpponent); // suppress reply
8416                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8417                          }
8418                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8419                          return 1;
8420                      }
8421                 }
8422
8423                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8424                 if(gameInfo.variant == VariantXiangqi ?
8425                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8426                  : nrW + nrB == 4 &&
8427                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8428                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8429                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8430                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8431                    ) ) {
8432                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8433                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8434                           if(engineOpponent) {
8435                             SendToProgram("force\n", engineOpponent); // suppress reply
8436                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8437                           }
8438                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8439                           return 1;
8440                      }
8441                 } else moveCount = 6;
8442             }
8443
8444         // Repetition draws and 50-move rule can be applied independently of legality testing
8445
8446                 /* Check for rep-draws */
8447                 count = 0;
8448                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8449                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8450                 for(k = forwardMostMove-2;
8451                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8452                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8453                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8454                     k-=2)
8455                 {   int rights=0;
8456                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8457                         /* compare castling rights */
8458                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8459                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8460                                 rights++; /* King lost rights, while rook still had them */
8461                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8462                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8463                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8464                                    rights++; /* but at least one rook lost them */
8465                         }
8466                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8467                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8468                                 rights++;
8469                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8470                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8471                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8472                                    rights++;
8473                         }
8474                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8475                             && appData.drawRepeats > 1) {
8476                              /* adjudicate after user-specified nr of repeats */
8477                              int result = GameIsDrawn;
8478                              char *details = "XBoard adjudication: repetition draw";
8479                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8480                                 // [HGM] xiangqi: check for forbidden perpetuals
8481                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8482                                 for(m=forwardMostMove; m>k; m-=2) {
8483                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8484                                         ourPerpetual = 0; // the current mover did not always check
8485                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8486                                         hisPerpetual = 0; // the opponent did not always check
8487                                 }
8488                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8489                                                                         ourPerpetual, hisPerpetual);
8490                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8491                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8492                                     details = "Xboard adjudication: perpetual checking";
8493                                 } else
8494                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8495                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8496                                 } else
8497                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8498                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8499                                         result = BlackWins;
8500                                         details = "Xboard adjudication: repetition";
8501                                     }
8502                                 } else // it must be XQ
8503                                 // Now check for perpetual chases
8504                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8505                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8506                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8507                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8508                                         static char resdet[MSG_SIZ];
8509                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8510                                         details = resdet;
8511                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8512                                     } else
8513                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8514                                         break; // Abort repetition-checking loop.
8515                                 }
8516                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8517                              }
8518                              if(engineOpponent) {
8519                                SendToProgram("force\n", engineOpponent); // suppress reply
8520                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8521                              }
8522                              GameEnds( result, details, GE_XBOARD );
8523                              return 1;
8524                         }
8525                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8526                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8527                     }
8528                 }
8529
8530                 /* Now we test for 50-move draws. Determine ply count */
8531                 count = forwardMostMove;
8532                 /* look for last irreversble move */
8533                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8534                     count--;
8535                 /* if we hit starting position, add initial plies */
8536                 if( count == backwardMostMove )
8537                     count -= initialRulePlies;
8538                 count = forwardMostMove - count;
8539                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8540                         // adjust reversible move counter for checks in Xiangqi
8541                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8542                         if(i < backwardMostMove) i = backwardMostMove;
8543                         while(i <= forwardMostMove) {
8544                                 lastCheck = inCheck; // check evasion does not count
8545                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8546                                 if(inCheck || lastCheck) count--; // check does not count
8547                                 i++;
8548                         }
8549                 }
8550                 if( count >= 100)
8551                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8552                          /* this is used to judge if draw claims are legal */
8553                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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, "Xboard adjudication: 50-move rule", GE_XBOARD );
8559                          return 1;
8560                 }
8561
8562                 /* if draw offer is pending, treat it as a draw claim
8563                  * when draw condition present, to allow engines a way to
8564                  * claim draws before making their move to avoid a race
8565                  * condition occurring after their move
8566                  */
8567                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8568                          char *p = NULL;
8569                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8570                              p = "Draw claim: 50-move rule";
8571                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8572                              p = "Draw claim: 3-fold repetition";
8573                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8574                              p = "Draw claim: insufficient mating material";
8575                          if( p != NULL && canAdjudicate) {
8576                              if(engineOpponent) {
8577                                SendToProgram("force\n", engineOpponent); // suppress reply
8578                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8579                              }
8580                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8581                              return 1;
8582                          }
8583                 }
8584
8585                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8586                     if(engineOpponent) {
8587                       SendToProgram("force\n", engineOpponent); // suppress reply
8588                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8589                     }
8590                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8591                     return 1;
8592                 }
8593         return 0;
8594 }
8595
8596 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8597 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8598 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8599
8600 static int
8601 BitbaseProbe ()
8602 {
8603     int pieces[10], squares[10], cnt=0, r, f, res;
8604     static int loaded;
8605     static PPROBE_EGBB probeBB;
8606     if(!appData.testLegality) return 10;
8607     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8608     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8609     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8610     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8611         ChessSquare piece = boards[forwardMostMove][r][f];
8612         int black = (piece >= BlackPawn);
8613         int type = piece - black*BlackPawn;
8614         if(piece == EmptySquare) continue;
8615         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8616         if(type == WhiteKing) type = WhiteQueen + 1;
8617         type = egbbCode[type];
8618         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8619         pieces[cnt] = type + black*6;
8620         if(++cnt > 5) return 11;
8621     }
8622     pieces[cnt] = squares[cnt] = 0;
8623     // probe EGBB
8624     if(loaded == 2) return 13; // loading failed before
8625     if(loaded == 0) {
8626         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8627         HMODULE lib;
8628         PLOAD_EGBB loadBB;
8629         loaded = 2; // prepare for failure
8630         if(!path) return 13; // no egbb installed
8631         strncpy(buf, path + 8, MSG_SIZ);
8632         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8633         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8634         lib = LoadLibrary(buf);
8635         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8636         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8637         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8638         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8639         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8640         loaded = 1; // success!
8641     }
8642     res = probeBB(forwardMostMove & 1, pieces, squares);
8643     return res > 0 ? 1 : res < 0 ? -1 : 0;
8644 }
8645
8646 char *
8647 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8648 {   // [HGM] book: this routine intercepts moves to simulate book replies
8649     char *bookHit = NULL;
8650
8651     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8652         char buf[MSG_SIZ];
8653         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8654         SendToProgram(buf, cps);
8655     }
8656     //first determine if the incoming move brings opponent into his book
8657     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8658         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8659     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8660     if(bookHit != NULL && !cps->bookSuspend) {
8661         // make sure opponent is not going to reply after receiving move to book position
8662         SendToProgram("force\n", cps);
8663         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8664     }
8665     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8666     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8667     // now arrange restart after book miss
8668     if(bookHit) {
8669         // after a book hit we never send 'go', and the code after the call to this routine
8670         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8671         char buf[MSG_SIZ], *move = bookHit;
8672         if(cps->useSAN) {
8673             int fromX, fromY, toX, toY;
8674             char promoChar;
8675             ChessMove moveType;
8676             move = buf + 30;
8677             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8678                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8679                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8680                                     PosFlags(forwardMostMove),
8681                                     fromY, fromX, toY, toX, promoChar, move);
8682             } else {
8683                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8684                 bookHit = NULL;
8685             }
8686         }
8687         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8688         SendToProgram(buf, cps);
8689         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8690     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8691         SendToProgram("go\n", cps);
8692         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8693     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8694         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8695             SendToProgram("go\n", cps);
8696         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8697     }
8698     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8699 }
8700
8701 int
8702 LoadError (char *errmess, ChessProgramState *cps)
8703 {   // unloads engine and switches back to -ncp mode if it was first
8704     if(cps->initDone) return FALSE;
8705     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8706     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8707     cps->pr = NoProc;
8708     if(cps == &first) {
8709         appData.noChessProgram = TRUE;
8710         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8711         gameMode = BeginningOfGame; ModeHighlight();
8712         SetNCPMode();
8713     }
8714     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8715     DisplayMessage("", ""); // erase waiting message
8716     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8717     return TRUE;
8718 }
8719
8720 char *savedMessage;
8721 ChessProgramState *savedState;
8722 void
8723 DeferredBookMove (void)
8724 {
8725         if(savedState->lastPing != savedState->lastPong)
8726                     ScheduleDelayedEvent(DeferredBookMove, 10);
8727         else
8728         HandleMachineMove(savedMessage, savedState);
8729 }
8730
8731 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8732 static ChessProgramState *stalledEngine;
8733 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8734
8735 void
8736 HandleMachineMove (char *message, ChessProgramState *cps)
8737 {
8738     static char firstLeg[20], legs;
8739     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8740     char realname[MSG_SIZ];
8741     int fromX, fromY, toX, toY;
8742     ChessMove moveType;
8743     char promoChar, roar;
8744     char *p, *pv=buf1;
8745     int oldError;
8746     char *bookHit;
8747
8748     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8749         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8750         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8751             DisplayError(_("Invalid pairing from pairing engine"), 0);
8752             return;
8753         }
8754         pairingReceived = 1;
8755         NextMatchGame();
8756         return; // Skim the pairing messages here.
8757     }
8758
8759     oldError = cps->userError; cps->userError = 0;
8760
8761 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8762     /*
8763      * Kludge to ignore BEL characters
8764      */
8765     while (*message == '\007') message++;
8766
8767     /*
8768      * [HGM] engine debug message: ignore lines starting with '#' character
8769      */
8770     if(cps->debug && *message == '#') return;
8771
8772     /*
8773      * Look for book output
8774      */
8775     if (cps == &first && bookRequested) {
8776         if (message[0] == '\t' || message[0] == ' ') {
8777             /* Part of the book output is here; append it */
8778             strcat(bookOutput, message);
8779             strcat(bookOutput, "  \n");
8780             return;
8781         } else if (bookOutput[0] != NULLCHAR) {
8782             /* All of book output has arrived; display it */
8783             char *p = bookOutput;
8784             while (*p != NULLCHAR) {
8785                 if (*p == '\t') *p = ' ';
8786                 p++;
8787             }
8788             DisplayInformation(bookOutput);
8789             bookRequested = FALSE;
8790             /* Fall through to parse the current output */
8791         }
8792     }
8793
8794     /*
8795      * Look for machine move.
8796      */
8797     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8798         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8799     {
8800         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8801             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8802             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8803             stalledEngine = cps;
8804             if(appData.ponderNextMove) { // bring opponent out of ponder
8805                 if(gameMode == TwoMachinesPlay) {
8806                     if(cps->other->pause)
8807                         PauseEngine(cps->other);
8808                     else
8809                         SendToProgram("easy\n", cps->other);
8810                 }
8811             }
8812             StopClocks();
8813             return;
8814         }
8815
8816       if(cps->usePing) {
8817
8818         /* This method is only useful on engines that support ping */
8819         if(abortEngineThink) {
8820             if (appData.debugMode) {
8821                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8822             }
8823             SendToProgram("undo\n", cps);
8824             return;
8825         }
8826
8827         if (cps->lastPing != cps->lastPong) {
8828             /* Extra move from before last new; ignore */
8829             if (appData.debugMode) {
8830                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8831             }
8832           return;
8833         }
8834
8835       } else {
8836
8837         int machineWhite = FALSE;
8838
8839         switch (gameMode) {
8840           case BeginningOfGame:
8841             /* Extra move from before last reset; ignore */
8842             if (appData.debugMode) {
8843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8844             }
8845             return;
8846
8847           case EndOfGame:
8848           case IcsIdle:
8849           default:
8850             /* Extra move after we tried to stop.  The mode test is
8851                not a reliable way of detecting this problem, but it's
8852                the best we can do on engines that don't support ping.
8853             */
8854             if (appData.debugMode) {
8855                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8856                         cps->which, gameMode);
8857             }
8858             SendToProgram("undo\n", cps);
8859             return;
8860
8861           case MachinePlaysWhite:
8862           case IcsPlayingWhite:
8863             machineWhite = TRUE;
8864             break;
8865
8866           case MachinePlaysBlack:
8867           case IcsPlayingBlack:
8868             machineWhite = FALSE;
8869             break;
8870
8871           case TwoMachinesPlay:
8872             machineWhite = (cps->twoMachinesColor[0] == 'w');
8873             break;
8874         }
8875         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8876             if (appData.debugMode) {
8877                 fprintf(debugFP,
8878                         "Ignoring move out of turn by %s, gameMode %d"
8879                         ", forwardMost %d\n",
8880                         cps->which, gameMode, forwardMostMove);
8881             }
8882             return;
8883         }
8884       }
8885
8886         if(cps->alphaRank) AlphaRank(machineMove, 4);
8887
8888         // [HGM] lion: (some very limited) support for Alien protocol
8889         killX = killY = kill2X = kill2Y = -1;
8890         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8891             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8892             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8893             return;
8894         }
8895         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8896             char *q = strchr(p+1, ',');            // second comma?
8897             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8898             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8899             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8900         }
8901         if(firstLeg[0]) { // there was a previous leg;
8902             // only support case where same piece makes two step
8903             char buf[20], *p = machineMove+1, *q = buf+1, f;
8904             safeStrCpy(buf, machineMove, 20);
8905             while(isdigit(*q)) q++; // find start of to-square
8906             safeStrCpy(machineMove, firstLeg, 20);
8907             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8908             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
8909             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)
8910             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8911             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8912             firstLeg[0] = NULLCHAR; legs = 0;
8913         }
8914
8915         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8916                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8917             /* Machine move could not be parsed; ignore it. */
8918           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8919                     machineMove, _(cps->which));
8920             DisplayMoveError(buf1);
8921             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8922                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8923             if (gameMode == TwoMachinesPlay) {
8924               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8925                        buf1, GE_XBOARD);
8926             }
8927             return;
8928         }
8929
8930         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8931         /* So we have to redo legality test with true e.p. status here,  */
8932         /* to make sure an illegal e.p. capture does not slip through,   */
8933         /* to cause a forfeit on a justified illegal-move complaint      */
8934         /* of the opponent.                                              */
8935         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8936            ChessMove moveType;
8937            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8938                              fromY, fromX, toY, toX, promoChar);
8939             if(moveType == IllegalMove) {
8940               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8941                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8942                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8943                            buf1, GE_XBOARD);
8944                 return;
8945            } else if(!appData.fischerCastling)
8946            /* [HGM] Kludge to handle engines that send FRC-style castling
8947               when they shouldn't (like TSCP-Gothic) */
8948            switch(moveType) {
8949              case WhiteASideCastleFR:
8950              case BlackASideCastleFR:
8951                toX+=2;
8952                currentMoveString[2]++;
8953                break;
8954              case WhiteHSideCastleFR:
8955              case BlackHSideCastleFR:
8956                toX--;
8957                currentMoveString[2]--;
8958                break;
8959              default: ; // nothing to do, but suppresses warning of pedantic compilers
8960            }
8961         }
8962         hintRequested = FALSE;
8963         lastHint[0] = NULLCHAR;
8964         bookRequested = FALSE;
8965         /* Program may be pondering now */
8966         cps->maybeThinking = TRUE;
8967         if (cps->sendTime == 2) cps->sendTime = 1;
8968         if (cps->offeredDraw) cps->offeredDraw--;
8969
8970         /* [AS] Save move info*/
8971         pvInfoList[ forwardMostMove ].score = programStats.score;
8972         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8973         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8974
8975         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8976
8977         /* Test suites abort the 'game' after one move */
8978         if(*appData.finger) {
8979            static FILE *f;
8980            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8981            if(!f) f = fopen(appData.finger, "w");
8982            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8983            else { DisplayFatalError("Bad output file", errno, 0); return; }
8984            free(fen);
8985            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8986         }
8987         if(appData.epd) {
8988            if(solvingTime >= 0) {
8989               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8990               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8991            } else {
8992               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8993               if(solvingTime == -2) second.matchWins++;
8994            }
8995            OutputKibitz(2, buf1);
8996            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8997         }
8998
8999         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9000         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9001             int count = 0;
9002
9003             while( count < adjudicateLossPlies ) {
9004                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9005
9006                 if( count & 1 ) {
9007                     score = -score; /* Flip score for winning side */
9008                 }
9009
9010                 if( score > appData.adjudicateLossThreshold ) {
9011                     break;
9012                 }
9013
9014                 count++;
9015             }
9016
9017             if( count >= adjudicateLossPlies ) {
9018                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9019
9020                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9021                     "Xboard adjudication",
9022                     GE_XBOARD );
9023
9024                 return;
9025             }
9026         }
9027
9028         if(Adjudicate(cps)) {
9029             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9030             return; // [HGM] adjudicate: for all automatic game ends
9031         }
9032
9033 #if ZIPPY
9034         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9035             first.initDone) {
9036           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9037                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9038                 SendToICS("draw ");
9039                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9040           }
9041           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9042           ics_user_moved = 1;
9043           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9044                 char buf[3*MSG_SIZ];
9045
9046                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9047                         programStats.score / 100.,
9048                         programStats.depth,
9049                         programStats.time / 100.,
9050                         (unsigned int)programStats.nodes,
9051                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9052                         programStats.movelist);
9053                 SendToICS(buf);
9054           }
9055         }
9056 #endif
9057
9058         /* [AS] Clear stats for next move */
9059         ClearProgramStats();
9060         thinkOutput[0] = NULLCHAR;
9061         hiddenThinkOutputState = 0;
9062
9063         bookHit = NULL;
9064         if (gameMode == TwoMachinesPlay) {
9065             /* [HGM] relaying draw offers moved to after reception of move */
9066             /* and interpreting offer as claim if it brings draw condition */
9067             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9068                 SendToProgram("draw\n", cps->other);
9069             }
9070             if (cps->other->sendTime) {
9071                 SendTimeRemaining(cps->other,
9072                                   cps->other->twoMachinesColor[0] == 'w');
9073             }
9074             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9075             if (firstMove && !bookHit) {
9076                 firstMove = FALSE;
9077                 if (cps->other->useColors) {
9078                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9079                 }
9080                 SendToProgram("go\n", cps->other);
9081             }
9082             cps->other->maybeThinking = TRUE;
9083         }
9084
9085         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9086
9087         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9088
9089         if (!pausing && appData.ringBellAfterMoves) {
9090             if(!roar) RingBell();
9091         }
9092
9093         /*
9094          * Reenable menu items that were disabled while
9095          * machine was thinking
9096          */
9097         if (gameMode != TwoMachinesPlay)
9098             SetUserThinkingEnables();
9099
9100         // [HGM] book: after book hit opponent has received move and is now in force mode
9101         // force the book reply into it, and then fake that it outputted this move by jumping
9102         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9103         if(bookHit) {
9104                 static char bookMove[MSG_SIZ]; // a bit generous?
9105
9106                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9107                 strcat(bookMove, bookHit);
9108                 message = bookMove;
9109                 cps = cps->other;
9110                 programStats.nodes = programStats.depth = programStats.time =
9111                 programStats.score = programStats.got_only_move = 0;
9112                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9113
9114                 if(cps->lastPing != cps->lastPong) {
9115                     savedMessage = message; // args for deferred call
9116                     savedState = cps;
9117                     ScheduleDelayedEvent(DeferredBookMove, 10);
9118                     return;
9119                 }
9120                 goto FakeBookMove;
9121         }
9122
9123         return;
9124     }
9125
9126     /* Set special modes for chess engines.  Later something general
9127      *  could be added here; for now there is just one kludge feature,
9128      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9129      *  when "xboard" is given as an interactive command.
9130      */
9131     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9132         cps->useSigint = FALSE;
9133         cps->useSigterm = FALSE;
9134     }
9135     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9136       ParseFeatures(message+8, cps);
9137       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9138     }
9139
9140     if (!strncmp(message, "setup ", 6) && 
9141         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9142           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9143                                         ) { // [HGM] allow first engine to define opening position
9144       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9145       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9146       *buf = NULLCHAR;
9147       if(sscanf(message, "setup (%s", buf) == 1) {
9148         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9149         ASSIGN(appData.pieceToCharTable, buf);
9150       }
9151       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9152       if(dummy >= 3) {
9153         while(message[s] && message[s++] != ' ');
9154         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9155            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9156             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9157             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9158             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9159           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9160           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9161           startedFromSetupPosition = FALSE;
9162         }
9163       }
9164       if(startedFromSetupPosition) return;
9165       ParseFEN(boards[0], &dummy, message+s, FALSE);
9166       DrawPosition(TRUE, boards[0]);
9167       CopyBoard(initialPosition, boards[0]);
9168       startedFromSetupPosition = TRUE;
9169       return;
9170     }
9171     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9172       ChessSquare piece = WhitePawn;
9173       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9174       if(*p == '+') promoted++, ID = *++p;
9175       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9176       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9177       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9178       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9179       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9180       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9181       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9182       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9183                                                && gameInfo.variant != VariantGreat
9184                                                && gameInfo.variant != VariantFairy    ) return;
9185       if(piece < EmptySquare) {
9186         pieceDefs = TRUE;
9187         ASSIGN(pieceDesc[piece], buf1);
9188         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9189       }
9190       return;
9191     }
9192     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9193       if(deferChoice) {
9194         LeftClick(Press, 0, 0); // finish the click that was interrupted
9195       } else if(promoSweep != EmptySquare) {
9196         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9197         if(strlen(promoRestrict) > 1) Sweep(0);
9198       }
9199       return;
9200     }
9201     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9202      * want this, I was asked to put it in, and obliged.
9203      */
9204     if (!strncmp(message, "setboard ", 9)) {
9205         Board initial_position;
9206
9207         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9208
9209         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9210             DisplayError(_("Bad FEN received from engine"), 0);
9211             return ;
9212         } else {
9213            Reset(TRUE, FALSE);
9214            CopyBoard(boards[0], initial_position);
9215            initialRulePlies = FENrulePlies;
9216            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9217            else gameMode = MachinePlaysBlack;
9218            DrawPosition(FALSE, boards[currentMove]);
9219         }
9220         return;
9221     }
9222
9223     /*
9224      * Look for communication commands
9225      */
9226     if (!strncmp(message, "telluser ", 9)) {
9227         if(message[9] == '\\' && message[10] == '\\')
9228             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9229         PlayTellSound();
9230         DisplayNote(message + 9);
9231         return;
9232     }
9233     if (!strncmp(message, "tellusererror ", 14)) {
9234         cps->userError = 1;
9235         if(message[14] == '\\' && message[15] == '\\')
9236             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9237         PlayTellSound();
9238         DisplayError(message + 14, 0);
9239         return;
9240     }
9241     if (!strncmp(message, "tellopponent ", 13)) {
9242       if (appData.icsActive) {
9243         if (loggedOn) {
9244           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9245           SendToICS(buf1);
9246         }
9247       } else {
9248         DisplayNote(message + 13);
9249       }
9250       return;
9251     }
9252     if (!strncmp(message, "tellothers ", 11)) {
9253       if (appData.icsActive) {
9254         if (loggedOn) {
9255           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9256           SendToICS(buf1);
9257         }
9258       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9259       return;
9260     }
9261     if (!strncmp(message, "tellall ", 8)) {
9262       if (appData.icsActive) {
9263         if (loggedOn) {
9264           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9265           SendToICS(buf1);
9266         }
9267       } else {
9268         DisplayNote(message + 8);
9269       }
9270       return;
9271     }
9272     if (strncmp(message, "warning", 7) == 0) {
9273         /* Undocumented feature, use tellusererror in new code */
9274         DisplayError(message, 0);
9275         return;
9276     }
9277     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9278         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9279         strcat(realname, " query");
9280         AskQuestion(realname, buf2, buf1, cps->pr);
9281         return;
9282     }
9283     /* Commands from the engine directly to ICS.  We don't allow these to be
9284      *  sent until we are logged on. Crafty kibitzes have been known to
9285      *  interfere with the login process.
9286      */
9287     if (loggedOn) {
9288         if (!strncmp(message, "tellics ", 8)) {
9289             SendToICS(message + 8);
9290             SendToICS("\n");
9291             return;
9292         }
9293         if (!strncmp(message, "tellicsnoalias ", 15)) {
9294             SendToICS(ics_prefix);
9295             SendToICS(message + 15);
9296             SendToICS("\n");
9297             return;
9298         }
9299         /* The following are for backward compatibility only */
9300         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9301             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9302             SendToICS(ics_prefix);
9303             SendToICS(message);
9304             SendToICS("\n");
9305             return;
9306         }
9307     }
9308     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9309         if(initPing == cps->lastPong) {
9310             if(gameInfo.variant == VariantUnknown) {
9311                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9312                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9313                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9314             }
9315             initPing = -1;
9316         }
9317         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9318             abortEngineThink = FALSE;
9319             DisplayMessage("", "");
9320             ThawUI();
9321         }
9322         return;
9323     }
9324     if(!strncmp(message, "highlight ", 10)) {
9325         if(appData.testLegality && !*engineVariant && appData.markers) return;
9326         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9327         return;
9328     }
9329     if(!strncmp(message, "click ", 6)) {
9330         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9331         if(appData.testLegality || !appData.oneClick) return;
9332         sscanf(message+6, "%c%d%c", &f, &y, &c);
9333         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9334         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9335         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9336         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9337         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9338         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9339             LeftClick(Release, lastLeftX, lastLeftY);
9340         controlKey  = (c == ',');
9341         LeftClick(Press, x, y);
9342         LeftClick(Release, x, y);
9343         first.highlight = f;
9344         return;
9345     }
9346     /*
9347      * If the move is illegal, cancel it and redraw the board.
9348      * Also deal with other error cases.  Matching is rather loose
9349      * here to accommodate engines written before the spec.
9350      */
9351     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9352         strncmp(message, "Error", 5) == 0) {
9353         if (StrStr(message, "name") ||
9354             StrStr(message, "rating") || StrStr(message, "?") ||
9355             StrStr(message, "result") || StrStr(message, "board") ||
9356             StrStr(message, "bk") || StrStr(message, "computer") ||
9357             StrStr(message, "variant") || StrStr(message, "hint") ||
9358             StrStr(message, "random") || StrStr(message, "depth") ||
9359             StrStr(message, "accepted")) {
9360             return;
9361         }
9362         if (StrStr(message, "protover")) {
9363           /* Program is responding to input, so it's apparently done
9364              initializing, and this error message indicates it is
9365              protocol version 1.  So we don't need to wait any longer
9366              for it to initialize and send feature commands. */
9367           FeatureDone(cps, 1);
9368           cps->protocolVersion = 1;
9369           return;
9370         }
9371         cps->maybeThinking = FALSE;
9372
9373         if (StrStr(message, "draw")) {
9374             /* Program doesn't have "draw" command */
9375             cps->sendDrawOffers = 0;
9376             return;
9377         }
9378         if (cps->sendTime != 1 &&
9379             (StrStr(message, "time") || StrStr(message, "otim"))) {
9380           /* Program apparently doesn't have "time" or "otim" command */
9381           cps->sendTime = 0;
9382           return;
9383         }
9384         if (StrStr(message, "analyze")) {
9385             cps->analysisSupport = FALSE;
9386             cps->analyzing = FALSE;
9387 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9388             EditGameEvent(); // [HGM] try to preserve loaded game
9389             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9390             DisplayError(buf2, 0);
9391             return;
9392         }
9393         if (StrStr(message, "(no matching move)st")) {
9394           /* Special kludge for GNU Chess 4 only */
9395           cps->stKludge = TRUE;
9396           SendTimeControl(cps, movesPerSession, timeControl,
9397                           timeIncrement, appData.searchDepth,
9398                           searchTime);
9399           return;
9400         }
9401         if (StrStr(message, "(no matching move)sd")) {
9402           /* Special kludge for GNU Chess 4 only */
9403           cps->sdKludge = TRUE;
9404           SendTimeControl(cps, movesPerSession, timeControl,
9405                           timeIncrement, appData.searchDepth,
9406                           searchTime);
9407           return;
9408         }
9409         if (!StrStr(message, "llegal")) {
9410             return;
9411         }
9412         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9413             gameMode == IcsIdle) return;
9414         if (forwardMostMove <= backwardMostMove) return;
9415         if (pausing) PauseEvent();
9416       if(appData.forceIllegal) {
9417             // [HGM] illegal: machine refused move; force position after move into it
9418           SendToProgram("force\n", cps);
9419           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9420                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9421                 // when black is to move, while there might be nothing on a2 or black
9422                 // might already have the move. So send the board as if white has the move.
9423                 // But first we must change the stm of the engine, as it refused the last move
9424                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9425                 if(WhiteOnMove(forwardMostMove)) {
9426                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9427                     SendBoard(cps, forwardMostMove); // kludgeless board
9428                 } else {
9429                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9430                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9431                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9432                 }
9433           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9434             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9435                  gameMode == TwoMachinesPlay)
9436               SendToProgram("go\n", cps);
9437             return;
9438       } else
9439         if (gameMode == PlayFromGameFile) {
9440             /* Stop reading this game file */
9441             gameMode = EditGame;
9442             ModeHighlight();
9443         }
9444         /* [HGM] illegal-move claim should forfeit game when Xboard */
9445         /* only passes fully legal moves                            */
9446         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9447             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9448                                 "False illegal-move claim", GE_XBOARD );
9449             return; // do not take back move we tested as valid
9450         }
9451         currentMove = forwardMostMove-1;
9452         DisplayMove(currentMove-1); /* before DisplayMoveError */
9453         SwitchClocks(forwardMostMove-1); // [HGM] race
9454         DisplayBothClocks();
9455         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9456                 parseList[currentMove], _(cps->which));
9457         DisplayMoveError(buf1);
9458         DrawPosition(FALSE, boards[currentMove]);
9459
9460         SetUserThinkingEnables();
9461         return;
9462     }
9463     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9464         /* Program has a broken "time" command that
9465            outputs a string not ending in newline.
9466            Don't use it. */
9467         cps->sendTime = 0;
9468     }
9469     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9470         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9471             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9472     }
9473
9474     /*
9475      * If chess program startup fails, exit with an error message.
9476      * Attempts to recover here are futile. [HGM] Well, we try anyway
9477      */
9478     if ((StrStr(message, "unknown host") != NULL)
9479         || (StrStr(message, "No remote directory") != NULL)
9480         || (StrStr(message, "not found") != NULL)
9481         || (StrStr(message, "No such file") != NULL)
9482         || (StrStr(message, "can't alloc") != NULL)
9483         || (StrStr(message, "Permission denied") != NULL)) {
9484
9485         cps->maybeThinking = FALSE;
9486         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9487                 _(cps->which), cps->program, cps->host, message);
9488         RemoveInputSource(cps->isr);
9489         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9490             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9491             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9492         }
9493         return;
9494     }
9495
9496     /*
9497      * Look for hint output
9498      */
9499     if (sscanf(message, "Hint: %s", buf1) == 1) {
9500         if (cps == &first && hintRequested) {
9501             hintRequested = FALSE;
9502             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9503                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9504                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9505                                     PosFlags(forwardMostMove),
9506                                     fromY, fromX, toY, toX, promoChar, buf1);
9507                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9508                 DisplayInformation(buf2);
9509             } else {
9510                 /* Hint move could not be parsed!? */
9511               snprintf(buf2, sizeof(buf2),
9512                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9513                         buf1, _(cps->which));
9514                 DisplayError(buf2, 0);
9515             }
9516         } else {
9517           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9518         }
9519         return;
9520     }
9521
9522     /*
9523      * Ignore other messages if game is not in progress
9524      */
9525     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9526         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9527
9528     /*
9529      * look for win, lose, draw, or draw offer
9530      */
9531     if (strncmp(message, "1-0", 3) == 0) {
9532         char *p, *q, *r = "";
9533         p = strchr(message, '{');
9534         if (p) {
9535             q = strchr(p, '}');
9536             if (q) {
9537                 *q = NULLCHAR;
9538                 r = p + 1;
9539             }
9540         }
9541         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9542         return;
9543     } else if (strncmp(message, "0-1", 3) == 0) {
9544         char *p, *q, *r = "";
9545         p = strchr(message, '{');
9546         if (p) {
9547             q = strchr(p, '}');
9548             if (q) {
9549                 *q = NULLCHAR;
9550                 r = p + 1;
9551             }
9552         }
9553         /* Kludge for Arasan 4.1 bug */
9554         if (strcmp(r, "Black resigns") == 0) {
9555             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9556             return;
9557         }
9558         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9559         return;
9560     } else if (strncmp(message, "1/2", 3) == 0) {
9561         char *p, *q, *r = "";
9562         p = strchr(message, '{');
9563         if (p) {
9564             q = strchr(p, '}');
9565             if (q) {
9566                 *q = NULLCHAR;
9567                 r = p + 1;
9568             }
9569         }
9570
9571         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9572         return;
9573
9574     } else if (strncmp(message, "White resign", 12) == 0) {
9575         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9576         return;
9577     } else if (strncmp(message, "Black resign", 12) == 0) {
9578         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9579         return;
9580     } else if (strncmp(message, "White matches", 13) == 0 ||
9581                strncmp(message, "Black matches", 13) == 0   ) {
9582         /* [HGM] ignore GNUShogi noises */
9583         return;
9584     } else if (strncmp(message, "White", 5) == 0 &&
9585                message[5] != '(' &&
9586                StrStr(message, "Black") == NULL) {
9587         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9588         return;
9589     } else if (strncmp(message, "Black", 5) == 0 &&
9590                message[5] != '(') {
9591         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9592         return;
9593     } else if (strcmp(message, "resign") == 0 ||
9594                strcmp(message, "computer resigns") == 0) {
9595         switch (gameMode) {
9596           case MachinePlaysBlack:
9597           case IcsPlayingBlack:
9598             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9599             break;
9600           case MachinePlaysWhite:
9601           case IcsPlayingWhite:
9602             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9603             break;
9604           case TwoMachinesPlay:
9605             if (cps->twoMachinesColor[0] == 'w')
9606               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9607             else
9608               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9609             break;
9610           default:
9611             /* can't happen */
9612             break;
9613         }
9614         return;
9615     } else if (strncmp(message, "opponent mates", 14) == 0) {
9616         switch (gameMode) {
9617           case MachinePlaysBlack:
9618           case IcsPlayingBlack:
9619             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9620             break;
9621           case MachinePlaysWhite:
9622           case IcsPlayingWhite:
9623             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9624             break;
9625           case TwoMachinesPlay:
9626             if (cps->twoMachinesColor[0] == 'w')
9627               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9628             else
9629               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9630             break;
9631           default:
9632             /* can't happen */
9633             break;
9634         }
9635         return;
9636     } else if (strncmp(message, "computer mates", 14) == 0) {
9637         switch (gameMode) {
9638           case MachinePlaysBlack:
9639           case IcsPlayingBlack:
9640             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9641             break;
9642           case MachinePlaysWhite:
9643           case IcsPlayingWhite:
9644             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9645             break;
9646           case TwoMachinesPlay:
9647             if (cps->twoMachinesColor[0] == 'w')
9648               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9649             else
9650               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9651             break;
9652           default:
9653             /* can't happen */
9654             break;
9655         }
9656         return;
9657     } else if (strncmp(message, "checkmate", 9) == 0) {
9658         if (WhiteOnMove(forwardMostMove)) {
9659             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9660         } else {
9661             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9662         }
9663         return;
9664     } else if (strstr(message, "Draw") != NULL ||
9665                strstr(message, "game is a draw") != NULL) {
9666         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9667         return;
9668     } else if (strstr(message, "offer") != NULL &&
9669                strstr(message, "draw") != NULL) {
9670 #if ZIPPY
9671         if (appData.zippyPlay && first.initDone) {
9672             /* Relay offer to ICS */
9673             SendToICS(ics_prefix);
9674             SendToICS("draw\n");
9675         }
9676 #endif
9677         cps->offeredDraw = 2; /* valid until this engine moves twice */
9678         if (gameMode == TwoMachinesPlay) {
9679             if (cps->other->offeredDraw) {
9680                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9681             /* [HGM] in two-machine mode we delay relaying draw offer      */
9682             /* until after we also have move, to see if it is really claim */
9683             }
9684         } else if (gameMode == MachinePlaysWhite ||
9685                    gameMode == MachinePlaysBlack) {
9686           if (userOfferedDraw) {
9687             DisplayInformation(_("Machine accepts your draw offer"));
9688             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9689           } else {
9690             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9691           }
9692         }
9693     }
9694
9695
9696     /*
9697      * Look for thinking output
9698      */
9699     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9700           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9701                                 ) {
9702         int plylev, mvleft, mvtot, curscore, time;
9703         char mvname[MOVE_LEN];
9704         u64 nodes; // [DM]
9705         char plyext;
9706         int ignore = FALSE;
9707         int prefixHint = FALSE;
9708         mvname[0] = NULLCHAR;
9709
9710         switch (gameMode) {
9711           case MachinePlaysBlack:
9712           case IcsPlayingBlack:
9713             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9714             break;
9715           case MachinePlaysWhite:
9716           case IcsPlayingWhite:
9717             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9718             break;
9719           case AnalyzeMode:
9720           case AnalyzeFile:
9721             break;
9722           case IcsObserving: /* [DM] icsEngineAnalyze */
9723             if (!appData.icsEngineAnalyze) ignore = TRUE;
9724             break;
9725           case TwoMachinesPlay:
9726             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9727                 ignore = TRUE;
9728             }
9729             break;
9730           default:
9731             ignore = TRUE;
9732             break;
9733         }
9734
9735         if (!ignore) {
9736             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9737             int solved = 0;
9738             buf1[0] = NULLCHAR;
9739             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9740                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9741                 char score_buf[MSG_SIZ];
9742
9743                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9744                     nodes += u64Const(0x100000000);
9745
9746                 if (plyext != ' ' && plyext != '\t') {
9747                     time *= 100;
9748                 }
9749
9750                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9751                 if( cps->scoreIsAbsolute &&
9752                     ( gameMode == MachinePlaysBlack ||
9753                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9754                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9755                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9756                      !WhiteOnMove(currentMove)
9757                     ) )
9758                 {
9759                     curscore = -curscore;
9760                 }
9761
9762                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9763
9764                 if(*bestMove) { // rememer time best EPD move was first found
9765                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9766                     ChessMove mt; char *p = bestMove;
9767                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9768                     solved = 0;
9769                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9770                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9771                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9772                             solved = 1;
9773                             break;
9774                         }
9775                         while(*p && *p != ' ') p++;
9776                         while(*p == ' ') p++;
9777                     }
9778                     if(!solved) solvingTime = -1;
9779                 }
9780                 if(*avoidMove && !solved) {
9781                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9782                     ChessMove mt; char *p = avoidMove, solved = 1;
9783                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9784                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9785                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9786                             solved = 0; solvingTime = -2;
9787                             break;
9788                         }
9789                         while(*p && *p != ' ') p++;
9790                         while(*p == ' ') p++;
9791                     }
9792                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9793                 }
9794
9795                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9796                         char buf[MSG_SIZ];
9797                         FILE *f;
9798                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9799                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9800                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9801                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9802                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9803                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9804                                 fclose(f);
9805                         }
9806                         else
9807                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9808                           DisplayError(_("failed writing PV"), 0);
9809                 }
9810
9811                 tempStats.depth = plylev;
9812                 tempStats.nodes = nodes;
9813                 tempStats.time = time;
9814                 tempStats.score = curscore;
9815                 tempStats.got_only_move = 0;
9816
9817                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9818                         int ticklen;
9819
9820                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9821                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9822                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9823                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9824                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9825                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9826                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9827                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9828                 }
9829
9830                 /* Buffer overflow protection */
9831                 if (pv[0] != NULLCHAR) {
9832                     if (strlen(pv) >= sizeof(tempStats.movelist)
9833                         && appData.debugMode) {
9834                         fprintf(debugFP,
9835                                 "PV is too long; using the first %u bytes.\n",
9836                                 (unsigned) sizeof(tempStats.movelist) - 1);
9837                     }
9838
9839                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9840                 } else {
9841                     sprintf(tempStats.movelist, " no PV\n");
9842                 }
9843
9844                 if (tempStats.seen_stat) {
9845                     tempStats.ok_to_send = 1;
9846                 }
9847
9848                 if (strchr(tempStats.movelist, '(') != NULL) {
9849                     tempStats.line_is_book = 1;
9850                     tempStats.nr_moves = 0;
9851                     tempStats.moves_left = 0;
9852                 } else {
9853                     tempStats.line_is_book = 0;
9854                 }
9855
9856                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9857                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9858
9859                 SendProgramStatsToFrontend( cps, &tempStats );
9860
9861                 /*
9862                     [AS] Protect the thinkOutput buffer from overflow... this
9863                     is only useful if buf1 hasn't overflowed first!
9864                 */
9865                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9866                 if(curscore >= MATE_SCORE) 
9867                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9868                 else if(curscore <= -MATE_SCORE) 
9869                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9870                 else
9871                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9872                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9873                          plylev,
9874                          (gameMode == TwoMachinesPlay ?
9875                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9876                          score_buf,
9877                          prefixHint ? lastHint : "",
9878                          prefixHint ? " " : "" );
9879
9880                 if( buf1[0] != NULLCHAR ) {
9881                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9882
9883                     if( strlen(pv) > max_len ) {
9884                         if( appData.debugMode) {
9885                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9886                         }
9887                         pv[max_len+1] = '\0';
9888                     }
9889
9890                     strcat( thinkOutput, pv);
9891                 }
9892
9893                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9894                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9895                     DisplayMove(currentMove - 1);
9896                 }
9897                 return;
9898
9899             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9900                 /* crafty (9.25+) says "(only move) <move>"
9901                  * if there is only 1 legal move
9902                  */
9903                 sscanf(p, "(only move) %s", buf1);
9904                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9905                 sprintf(programStats.movelist, "%s (only move)", buf1);
9906                 programStats.depth = 1;
9907                 programStats.nr_moves = 1;
9908                 programStats.moves_left = 1;
9909                 programStats.nodes = 1;
9910                 programStats.time = 1;
9911                 programStats.got_only_move = 1;
9912
9913                 /* Not really, but we also use this member to
9914                    mean "line isn't going to change" (Crafty
9915                    isn't searching, so stats won't change) */
9916                 programStats.line_is_book = 1;
9917
9918                 SendProgramStatsToFrontend( cps, &programStats );
9919
9920                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9921                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9922                     DisplayMove(currentMove - 1);
9923                 }
9924                 return;
9925             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9926                               &time, &nodes, &plylev, &mvleft,
9927                               &mvtot, mvname) >= 5) {
9928                 /* The stat01: line is from Crafty (9.29+) in response
9929                    to the "." command */
9930                 programStats.seen_stat = 1;
9931                 cps->maybeThinking = TRUE;
9932
9933                 if (programStats.got_only_move || !appData.periodicUpdates)
9934                   return;
9935
9936                 programStats.depth = plylev;
9937                 programStats.time = time;
9938                 programStats.nodes = nodes;
9939                 programStats.moves_left = mvleft;
9940                 programStats.nr_moves = mvtot;
9941                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9942                 programStats.ok_to_send = 1;
9943                 programStats.movelist[0] = '\0';
9944
9945                 SendProgramStatsToFrontend( cps, &programStats );
9946
9947                 return;
9948
9949             } else if (strncmp(message,"++",2) == 0) {
9950                 /* Crafty 9.29+ outputs this */
9951                 programStats.got_fail = 2;
9952                 return;
9953
9954             } else if (strncmp(message,"--",2) == 0) {
9955                 /* Crafty 9.29+ outputs this */
9956                 programStats.got_fail = 1;
9957                 return;
9958
9959             } else if (thinkOutput[0] != NULLCHAR &&
9960                        strncmp(message, "    ", 4) == 0) {
9961                 unsigned message_len;
9962
9963                 p = message;
9964                 while (*p && *p == ' ') p++;
9965
9966                 message_len = strlen( p );
9967
9968                 /* [AS] Avoid buffer overflow */
9969                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9970                     strcat(thinkOutput, " ");
9971                     strcat(thinkOutput, p);
9972                 }
9973
9974                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9975                     strcat(programStats.movelist, " ");
9976                     strcat(programStats.movelist, p);
9977                 }
9978
9979                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9980                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9981                     DisplayMove(currentMove - 1);
9982                 }
9983                 return;
9984             }
9985         }
9986         else {
9987             buf1[0] = NULLCHAR;
9988
9989             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9990                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9991             {
9992                 ChessProgramStats cpstats;
9993
9994                 if (plyext != ' ' && plyext != '\t') {
9995                     time *= 100;
9996                 }
9997
9998                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9999                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10000                     curscore = -curscore;
10001                 }
10002
10003                 cpstats.depth = plylev;
10004                 cpstats.nodes = nodes;
10005                 cpstats.time = time;
10006                 cpstats.score = curscore;
10007                 cpstats.got_only_move = 0;
10008                 cpstats.movelist[0] = '\0';
10009
10010                 if (buf1[0] != NULLCHAR) {
10011                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10012                 }
10013
10014                 cpstats.ok_to_send = 0;
10015                 cpstats.line_is_book = 0;
10016                 cpstats.nr_moves = 0;
10017                 cpstats.moves_left = 0;
10018
10019                 SendProgramStatsToFrontend( cps, &cpstats );
10020             }
10021         }
10022     }
10023 }
10024
10025
10026 /* Parse a game score from the character string "game", and
10027    record it as the history of the current game.  The game
10028    score is NOT assumed to start from the standard position.
10029    The display is not updated in any way.
10030    */
10031 void
10032 ParseGameHistory (char *game)
10033 {
10034     ChessMove moveType;
10035     int fromX, fromY, toX, toY, boardIndex;
10036     char promoChar;
10037     char *p, *q;
10038     char buf[MSG_SIZ];
10039
10040     if (appData.debugMode)
10041       fprintf(debugFP, "Parsing game history: %s\n", game);
10042
10043     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10044     gameInfo.site = StrSave(appData.icsHost);
10045     gameInfo.date = PGNDate();
10046     gameInfo.round = StrSave("-");
10047
10048     /* Parse out names of players */
10049     while (*game == ' ') game++;
10050     p = buf;
10051     while (*game != ' ') *p++ = *game++;
10052     *p = NULLCHAR;
10053     gameInfo.white = StrSave(buf);
10054     while (*game == ' ') game++;
10055     p = buf;
10056     while (*game != ' ' && *game != '\n') *p++ = *game++;
10057     *p = NULLCHAR;
10058     gameInfo.black = StrSave(buf);
10059
10060     /* Parse moves */
10061     boardIndex = blackPlaysFirst ? 1 : 0;
10062     yynewstr(game);
10063     for (;;) {
10064         yyboardindex = boardIndex;
10065         moveType = (ChessMove) Myylex();
10066         switch (moveType) {
10067           case IllegalMove:             /* maybe suicide chess, etc. */
10068   if (appData.debugMode) {
10069     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10070     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10071     setbuf(debugFP, NULL);
10072   }
10073           case WhitePromotion:
10074           case BlackPromotion:
10075           case WhiteNonPromotion:
10076           case BlackNonPromotion:
10077           case NormalMove:
10078           case FirstLeg:
10079           case WhiteCapturesEnPassant:
10080           case BlackCapturesEnPassant:
10081           case WhiteKingSideCastle:
10082           case WhiteQueenSideCastle:
10083           case BlackKingSideCastle:
10084           case BlackQueenSideCastle:
10085           case WhiteKingSideCastleWild:
10086           case WhiteQueenSideCastleWild:
10087           case BlackKingSideCastleWild:
10088           case BlackQueenSideCastleWild:
10089           /* PUSH Fabien */
10090           case WhiteHSideCastleFR:
10091           case WhiteASideCastleFR:
10092           case BlackHSideCastleFR:
10093           case BlackASideCastleFR:
10094           /* POP Fabien */
10095             fromX = currentMoveString[0] - AAA;
10096             fromY = currentMoveString[1] - ONE;
10097             toX = currentMoveString[2] - AAA;
10098             toY = currentMoveString[3] - ONE;
10099             promoChar = currentMoveString[4];
10100             break;
10101           case WhiteDrop:
10102           case BlackDrop:
10103             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10104             fromX = moveType == WhiteDrop ?
10105               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10106             (int) CharToPiece(ToLower(currentMoveString[0]));
10107             fromY = DROP_RANK;
10108             toX = currentMoveString[2] - AAA;
10109             toY = currentMoveString[3] - ONE;
10110             promoChar = NULLCHAR;
10111             break;
10112           case AmbiguousMove:
10113             /* bug? */
10114             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10115   if (appData.debugMode) {
10116     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10117     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10118     setbuf(debugFP, NULL);
10119   }
10120             DisplayError(buf, 0);
10121             return;
10122           case ImpossibleMove:
10123             /* bug? */
10124             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10125   if (appData.debugMode) {
10126     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10127     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10128     setbuf(debugFP, NULL);
10129   }
10130             DisplayError(buf, 0);
10131             return;
10132           case EndOfFile:
10133             if (boardIndex < backwardMostMove) {
10134                 /* Oops, gap.  How did that happen? */
10135                 DisplayError(_("Gap in move list"), 0);
10136                 return;
10137             }
10138             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10139             if (boardIndex > forwardMostMove) {
10140                 forwardMostMove = boardIndex;
10141             }
10142             return;
10143           case ElapsedTime:
10144             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10145                 strcat(parseList[boardIndex-1], " ");
10146                 strcat(parseList[boardIndex-1], yy_text);
10147             }
10148             continue;
10149           case Comment:
10150           case PGNTag:
10151           case NAG:
10152           default:
10153             /* ignore */
10154             continue;
10155           case WhiteWins:
10156           case BlackWins:
10157           case GameIsDrawn:
10158           case GameUnfinished:
10159             if (gameMode == IcsExamining) {
10160                 if (boardIndex < backwardMostMove) {
10161                     /* Oops, gap.  How did that happen? */
10162                     return;
10163                 }
10164                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10165                 return;
10166             }
10167             gameInfo.result = moveType;
10168             p = strchr(yy_text, '{');
10169             if (p == NULL) p = strchr(yy_text, '(');
10170             if (p == NULL) {
10171                 p = yy_text;
10172                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10173             } else {
10174                 q = strchr(p, *p == '{' ? '}' : ')');
10175                 if (q != NULL) *q = NULLCHAR;
10176                 p++;
10177             }
10178             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10179             gameInfo.resultDetails = StrSave(p);
10180             continue;
10181         }
10182         if (boardIndex >= forwardMostMove &&
10183             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10184             backwardMostMove = blackPlaysFirst ? 1 : 0;
10185             return;
10186         }
10187         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10188                                  fromY, fromX, toY, toX, promoChar,
10189                                  parseList[boardIndex]);
10190         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10191         /* currentMoveString is set as a side-effect of yylex */
10192         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10193         strcat(moveList[boardIndex], "\n");
10194         boardIndex++;
10195         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10196         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10197           case MT_NONE:
10198           case MT_STALEMATE:
10199           default:
10200             break;
10201           case MT_CHECK:
10202             if(!IS_SHOGI(gameInfo.variant))
10203                 strcat(parseList[boardIndex - 1], "+");
10204             break;
10205           case MT_CHECKMATE:
10206           case MT_STAINMATE:
10207             strcat(parseList[boardIndex - 1], "#");
10208             break;
10209         }
10210     }
10211 }
10212
10213
10214 /* Apply a move to the given board  */
10215 void
10216 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10217 {
10218   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10219   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10220
10221     /* [HGM] compute & store e.p. status and castling rights for new position */
10222     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10223
10224       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10225       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_FILE],lastRank = board[LAST_RANK];
10226       board[EP_STATUS] = EP_NONE;
10227       board[EP_FILE] = board[EP_RANK] = board[LAST_FILE] = board[LAST_RANK] = 100;
10228
10229   if (fromY == DROP_RANK) {
10230         /* must be first */
10231         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10232             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10233             return;
10234         }
10235         piece = board[toY][toX] = (ChessSquare) fromX;
10236   } else {
10237 //      ChessSquare victim;
10238       int i;
10239
10240       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10241 //           victim = board[killY][killX],
10242            killed = board[killY][killX],
10243            board[killY][killX] = EmptySquare,
10244            board[EP_STATUS] = EP_CAPTURE;
10245            if( kill2X >= 0 && kill2Y >= 0)
10246              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10247       }
10248
10249       if( board[toY][toX] != EmptySquare ) {
10250            board[EP_STATUS] = EP_CAPTURE;
10251            if( (fromX != toX || fromY != toY) && // not igui!
10252                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10253                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10254                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10255            }
10256       }
10257
10258       pawn = board[fromY][fromX];
10259       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10260         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10261             captured = board[lastRank][lastFile]; // remove victim
10262             board[lastRank][lastFile] = EmptySquare;
10263             pawn = EmptySquare; // kludge to suppress old e.p. code
10264         }
10265       }
10266       if( pawn == WhiteLance || pawn == BlackLance ) {
10267            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10268                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10269                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10270            }
10271       }
10272       if( pawn == WhitePawn ) {
10273            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10274                board[EP_STATUS] = EP_PAWN_MOVE;
10275            if( toY-fromY>=2) {
10276                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10277                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10278                         gameInfo.variant != VariantBerolina || toX < fromX)
10279                       board[EP_STATUS] = toX | berolina;
10280                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10281                         gameInfo.variant != VariantBerolina || toX > fromX)
10282                       board[EP_STATUS] = toX;
10283                board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10284            }
10285       } else
10286       if( pawn == BlackPawn ) {
10287            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10288                board[EP_STATUS] = EP_PAWN_MOVE;
10289            if( toY-fromY<= -2) {
10290                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10291                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10292                         gameInfo.variant != VariantBerolina || toX < fromX)
10293                       board[EP_STATUS] = toX | berolina;
10294                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10295                         gameInfo.variant != VariantBerolina || toX > fromX)
10296                       board[EP_STATUS] = toX;
10297                board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10298            }
10299        }
10300
10301        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10302        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10303        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10304        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10305
10306        for(i=0; i<nrCastlingRights; i++) {
10307            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10308               board[CASTLING][i] == toX   && castlingRank[i] == toY
10309              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10310        }
10311
10312        if(gameInfo.variant == VariantSChess) { // update virginity
10313            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10314            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10315            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10316            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10317        }
10318
10319      if (fromX == toX && fromY == toY && killX < 0) return;
10320
10321      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10322      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10323      if(gameInfo.variant == VariantKnightmate)
10324          king += (int) WhiteUnicorn - (int) WhiteKing;
10325
10326     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10327        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10328         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10329         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10330         board[EP_STATUS] = EP_NONE; // capture was fake!
10331     } else
10332     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10333         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10334         board[toY][toX] = piece;
10335         board[EP_STATUS] = EP_NONE; // capture was fake!
10336     } else
10337     /* Code added by Tord: */
10338     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10339     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10340         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10341       board[EP_STATUS] = EP_NONE; // capture was fake!
10342       board[fromY][fromX] = EmptySquare;
10343       board[toY][toX] = EmptySquare;
10344       if((toX > fromX) != (piece == WhiteRook)) {
10345         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10346       } else {
10347         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10348       }
10349     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10350                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10351       board[EP_STATUS] = EP_NONE;
10352       board[fromY][fromX] = EmptySquare;
10353       board[toY][toX] = EmptySquare;
10354       if((toX > fromX) != (piece == BlackRook)) {
10355         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10356       } else {
10357         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10358       }
10359     /* End of code added by Tord */
10360
10361     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10362         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10363         board[toY][toX] = piece;
10364     } else if (board[fromY][fromX] == king
10365         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10366         && toY == fromY && toX > fromX+1) {
10367         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10368                                                                                              ; // castle with nearest piece
10369         board[fromY][toX-1] = board[fromY][rookX];
10370         board[fromY][rookX] = EmptySquare;
10371         board[fromY][fromX] = EmptySquare;
10372         board[toY][toX] = king;
10373     } else if (board[fromY][fromX] == king
10374         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10375                && toY == fromY && toX < fromX-1) {
10376         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10377                                                                                   ; // castle with nearest piece
10378         board[fromY][toX+1] = board[fromY][rookX];
10379         board[fromY][rookX] = EmptySquare;
10380         board[fromY][fromX] = EmptySquare;
10381         board[toY][toX] = king;
10382     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10383                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10384                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10385                ) {
10386         /* white pawn promotion */
10387         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10388         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10389             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10390         board[fromY][fromX] = EmptySquare;
10391     } else if ((fromY >= BOARD_HEIGHT>>1)
10392                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10393                && (toX != fromX)
10394                && gameInfo.variant != VariantXiangqi
10395                && gameInfo.variant != VariantBerolina
10396                && (pawn == WhitePawn)
10397                && (board[toY][toX] == EmptySquare)) {
10398         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10399         board[fromY][fromX] = EmptySquare;
10400         board[toY][toX] = piece;
10401         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10402     } else if ((fromY == BOARD_HEIGHT-4)
10403                && (toX == fromX)
10404                && gameInfo.variant == VariantBerolina
10405                && (board[fromY][fromX] == WhitePawn)
10406                && (board[toY][toX] == EmptySquare)) {
10407         board[fromY][fromX] = EmptySquare;
10408         board[toY][toX] = WhitePawn;
10409         if(oldEP & EP_BEROLIN_A) {
10410                 captured = board[fromY][fromX-1];
10411                 board[fromY][fromX-1] = EmptySquare;
10412         }else{  captured = board[fromY][fromX+1];
10413                 board[fromY][fromX+1] = EmptySquare;
10414         }
10415     } else if (board[fromY][fromX] == king
10416         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10417                && toY == fromY && toX > fromX+1) {
10418         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10419                                                                                              ;
10420         board[fromY][toX-1] = board[fromY][rookX];
10421         board[fromY][rookX] = EmptySquare;
10422         board[fromY][fromX] = EmptySquare;
10423         board[toY][toX] = king;
10424     } else if (board[fromY][fromX] == king
10425         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10426                && toY == fromY && toX < fromX-1) {
10427         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10428                                                                                 ;
10429         board[fromY][toX+1] = board[fromY][rookX];
10430         board[fromY][rookX] = EmptySquare;
10431         board[fromY][fromX] = EmptySquare;
10432         board[toY][toX] = king;
10433     } else if (fromY == 7 && fromX == 3
10434                && board[fromY][fromX] == BlackKing
10435                && toY == 7 && toX == 5) {
10436         board[fromY][fromX] = EmptySquare;
10437         board[toY][toX] = BlackKing;
10438         board[fromY][7] = EmptySquare;
10439         board[toY][4] = BlackRook;
10440     } else if (fromY == 7 && fromX == 3
10441                && board[fromY][fromX] == BlackKing
10442                && toY == 7 && toX == 1) {
10443         board[fromY][fromX] = EmptySquare;
10444         board[toY][toX] = BlackKing;
10445         board[fromY][0] = EmptySquare;
10446         board[toY][2] = BlackRook;
10447     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10448                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10449                && toY < promoRank && promoChar
10450                ) {
10451         /* black pawn promotion */
10452         board[toY][toX] = CharToPiece(ToLower(promoChar));
10453         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10454             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10455         board[fromY][fromX] = EmptySquare;
10456     } else if ((fromY < BOARD_HEIGHT>>1)
10457                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10458                && (toX != fromX)
10459                && gameInfo.variant != VariantXiangqi
10460                && gameInfo.variant != VariantBerolina
10461                && (pawn == BlackPawn)
10462                && (board[toY][toX] == EmptySquare)) {
10463         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10464         board[fromY][fromX] = EmptySquare;
10465         board[toY][toX] = piece;
10466         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10467     } else if ((fromY == 3)
10468                && (toX == fromX)
10469                && gameInfo.variant == VariantBerolina
10470                && (board[fromY][fromX] == BlackPawn)
10471                && (board[toY][toX] == EmptySquare)) {
10472         board[fromY][fromX] = EmptySquare;
10473         board[toY][toX] = BlackPawn;
10474         if(oldEP & EP_BEROLIN_A) {
10475                 captured = board[fromY][fromX-1];
10476                 board[fromY][fromX-1] = EmptySquare;
10477         }else{  captured = board[fromY][fromX+1];
10478                 board[fromY][fromX+1] = EmptySquare;
10479         }
10480     } else {
10481         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10482         board[fromY][fromX] = EmptySquare;
10483         board[toY][toX] = piece;
10484     }
10485   }
10486
10487     if (gameInfo.holdingsWidth != 0) {
10488
10489       /* !!A lot more code needs to be written to support holdings  */
10490       /* [HGM] OK, so I have written it. Holdings are stored in the */
10491       /* penultimate board files, so they are automaticlly stored   */
10492       /* in the game history.                                       */
10493       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10494                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10495         /* Delete from holdings, by decreasing count */
10496         /* and erasing image if necessary            */
10497         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10498         if(p < (int) BlackPawn) { /* white drop */
10499              p -= (int)WhitePawn;
10500                  p = PieceToNumber((ChessSquare)p);
10501              if(p >= gameInfo.holdingsSize) p = 0;
10502              if(--board[p][BOARD_WIDTH-2] <= 0)
10503                   board[p][BOARD_WIDTH-1] = EmptySquare;
10504              if((int)board[p][BOARD_WIDTH-2] < 0)
10505                         board[p][BOARD_WIDTH-2] = 0;
10506         } else {                  /* black drop */
10507              p -= (int)BlackPawn;
10508                  p = PieceToNumber((ChessSquare)p);
10509              if(p >= gameInfo.holdingsSize) p = 0;
10510              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10511                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10512              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10513                         board[BOARD_HEIGHT-1-p][1] = 0;
10514         }
10515       }
10516       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10517           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10518         /* [HGM] holdings: Add to holdings, if holdings exist */
10519         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10520                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10521                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10522         }
10523         p = (int) captured;
10524         if (p >= (int) BlackPawn) {
10525           p -= (int)BlackPawn;
10526           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10527                   /* Restore shogi-promoted piece to its original  first */
10528                   captured = (ChessSquare) (DEMOTED(captured));
10529                   p = DEMOTED(p);
10530           }
10531           p = PieceToNumber((ChessSquare)p);
10532           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10533           board[p][BOARD_WIDTH-2]++;
10534           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10535         } else {
10536           p -= (int)WhitePawn;
10537           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10538                   captured = (ChessSquare) (DEMOTED(captured));
10539                   p = DEMOTED(p);
10540           }
10541           p = PieceToNumber((ChessSquare)p);
10542           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10543           board[BOARD_HEIGHT-1-p][1]++;
10544           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10545         }
10546       }
10547     } else if (gameInfo.variant == VariantAtomic) {
10548       if (captured != EmptySquare) {
10549         int y, x;
10550         for (y = toY-1; y <= toY+1; y++) {
10551           for (x = toX-1; x <= toX+1; x++) {
10552             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10553                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10554               board[y][x] = EmptySquare;
10555             }
10556           }
10557         }
10558         board[toY][toX] = EmptySquare;
10559       }
10560     }
10561
10562     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10563         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10564     } else
10565     if(promoChar == '+') {
10566         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10567         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10568         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10569           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10570     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10571         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10572         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10573            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10574         board[toY][toX] = newPiece;
10575     }
10576     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10577                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10578         // [HGM] superchess: take promotion piece out of holdings
10579         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10580         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10581             if(!--board[k][BOARD_WIDTH-2])
10582                 board[k][BOARD_WIDTH-1] = EmptySquare;
10583         } else {
10584             if(!--board[BOARD_HEIGHT-1-k][1])
10585                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10586         }
10587     }
10588 }
10589
10590 /* Updates forwardMostMove */
10591 void
10592 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10593 {
10594     int x = toX, y = toY;
10595     char *s = parseList[forwardMostMove];
10596     ChessSquare p = boards[forwardMostMove][toY][toX];
10597 //    forwardMostMove++; // [HGM] bare: moved downstream
10598
10599     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10600     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10601     (void) CoordsToAlgebraic(boards[forwardMostMove],
10602                              PosFlags(forwardMostMove),
10603                              fromY, fromX, y, x, (killX < 0)*promoChar,
10604                              s);
10605     if(kill2X >= 0 && kill2Y >= 0)
10606         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10607     if(killX >= 0 && killY >= 0)
10608         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10609                                            toX + AAA, toY + ONE - '0', promoChar);
10610
10611     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10612         int timeLeft; static int lastLoadFlag=0; int king, piece;
10613         piece = boards[forwardMostMove][fromY][fromX];
10614         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10615         if(gameInfo.variant == VariantKnightmate)
10616             king += (int) WhiteUnicorn - (int) WhiteKing;
10617         if(forwardMostMove == 0) {
10618             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10619                 fprintf(serverMoves, "%s;", UserName());
10620             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10621                 fprintf(serverMoves, "%s;", second.tidy);
10622             fprintf(serverMoves, "%s;", first.tidy);
10623             if(gameMode == MachinePlaysWhite)
10624                 fprintf(serverMoves, "%s;", UserName());
10625             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10626                 fprintf(serverMoves, "%s;", second.tidy);
10627         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10628         lastLoadFlag = loadFlag;
10629         // print base move
10630         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10631         // print castling suffix
10632         if( toY == fromY && piece == king ) {
10633             if(toX-fromX > 1)
10634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10635             if(fromX-toX >1)
10636                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10637         }
10638         // e.p. suffix
10639         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10640              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10641              boards[forwardMostMove][toY][toX] == EmptySquare
10642              && fromX != toX && fromY != toY)
10643                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10644         // promotion suffix
10645         if(promoChar != NULLCHAR) {
10646             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10647                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10648                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10649             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10650         }
10651         if(!loadFlag) {
10652                 char buf[MOVE_LEN*2], *p; int len;
10653             fprintf(serverMoves, "/%d/%d",
10654                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10655             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10656             else                      timeLeft = blackTimeRemaining/1000;
10657             fprintf(serverMoves, "/%d", timeLeft);
10658                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10659                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10660                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10661                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10662             fprintf(serverMoves, "/%s", buf);
10663         }
10664         fflush(serverMoves);
10665     }
10666
10667     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10668         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10669       return;
10670     }
10671     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10672     if (commentList[forwardMostMove+1] != NULL) {
10673         free(commentList[forwardMostMove+1]);
10674         commentList[forwardMostMove+1] = NULL;
10675     }
10676     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10677     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10678     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10679     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10680     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10681     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10682     adjustedClock = FALSE;
10683     gameInfo.result = GameUnfinished;
10684     if (gameInfo.resultDetails != NULL) {
10685         free(gameInfo.resultDetails);
10686         gameInfo.resultDetails = NULL;
10687     }
10688     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10689                               moveList[forwardMostMove - 1]);
10690     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10691       case MT_NONE:
10692       case MT_STALEMATE:
10693       default:
10694         break;
10695       case MT_CHECK:
10696         if(!IS_SHOGI(gameInfo.variant))
10697             strcat(parseList[forwardMostMove - 1], "+");
10698         break;
10699       case MT_CHECKMATE:
10700       case MT_STAINMATE:
10701         strcat(parseList[forwardMostMove - 1], "#");
10702         break;
10703     }
10704 }
10705
10706 /* Updates currentMove if not pausing */
10707 void
10708 ShowMove (int fromX, int fromY, int toX, int toY)
10709 {
10710     int instant = (gameMode == PlayFromGameFile) ?
10711         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10712     if(appData.noGUI) return;
10713     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10714         if (!instant) {
10715             if (forwardMostMove == currentMove + 1) {
10716                 AnimateMove(boards[forwardMostMove - 1],
10717                             fromX, fromY, toX, toY);
10718             }
10719         }
10720         currentMove = forwardMostMove;
10721     }
10722
10723     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10724
10725     if (instant) return;
10726
10727     DisplayMove(currentMove - 1);
10728     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10729             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10730                 SetHighlights(fromX, fromY, toX, toY);
10731             }
10732     }
10733     DrawPosition(FALSE, boards[currentMove]);
10734     DisplayBothClocks();
10735     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10736 }
10737
10738 void
10739 SendEgtPath (ChessProgramState *cps)
10740 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10741         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10742
10743         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10744
10745         while(*p) {
10746             char c, *q = name+1, *r, *s;
10747
10748             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10749             while(*p && *p != ',') *q++ = *p++;
10750             *q++ = ':'; *q = 0;
10751             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10752                 strcmp(name, ",nalimov:") == 0 ) {
10753                 // take nalimov path from the menu-changeable option first, if it is defined
10754               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10755                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10756             } else
10757             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10758                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10759                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10760                 s = r = StrStr(s, ":") + 1; // beginning of path info
10761                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10762                 c = *r; *r = 0;             // temporarily null-terminate path info
10763                     *--q = 0;               // strip of trailig ':' from name
10764                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10765                 *r = c;
10766                 SendToProgram(buf,cps);     // send egtbpath command for this format
10767             }
10768             if(*p == ',') p++; // read away comma to position for next format name
10769         }
10770 }
10771
10772 static int
10773 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10774 {
10775       int width = 8, height = 8, holdings = 0;             // most common sizes
10776       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10777       // correct the deviations default for each variant
10778       if( v == VariantXiangqi ) width = 9,  height = 10;
10779       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10780       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10781       if( v == VariantCapablanca || v == VariantCapaRandom ||
10782           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10783                                 width = 10;
10784       if( v == VariantCourier ) width = 12;
10785       if( v == VariantSuper )                            holdings = 8;
10786       if( v == VariantGreat )   width = 10,              holdings = 8;
10787       if( v == VariantSChess )                           holdings = 7;
10788       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10789       if( v == VariantChuChess) width = 10, height = 10;
10790       if( v == VariantChu )     width = 12, height = 12;
10791       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10792              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10793              holdingsSize >= 0 && holdingsSize != holdings;
10794 }
10795
10796 char variantError[MSG_SIZ];
10797
10798 char *
10799 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10800 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10801       char *p, *variant = VariantName(v);
10802       static char b[MSG_SIZ];
10803       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10804            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10805                                                holdingsSize, variant); // cook up sized variant name
10806            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10807            if(StrStr(list, b) == NULL) {
10808                // specific sized variant not known, check if general sizing allowed
10809                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10810                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10811                             boardWidth, boardHeight, holdingsSize, engine);
10812                    return NULL;
10813                }
10814                /* [HGM] here we really should compare with the maximum supported board size */
10815            }
10816       } else snprintf(b, MSG_SIZ,"%s", variant);
10817       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10818       p = StrStr(list, b);
10819       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10820       if(p == NULL) {
10821           // occurs not at all in list, or only as sub-string
10822           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10823           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10824               int l = strlen(variantError);
10825               char *q;
10826               while(p != list && p[-1] != ',') p--;
10827               q = strchr(p, ',');
10828               if(q) *q = NULLCHAR;
10829               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10830               if(q) *q= ',';
10831           }
10832           return NULL;
10833       }
10834       return b;
10835 }
10836
10837 void
10838 InitChessProgram (ChessProgramState *cps, int setup)
10839 /* setup needed to setup FRC opening position */
10840 {
10841     char buf[MSG_SIZ], *b;
10842     if (appData.noChessProgram) return;
10843     hintRequested = FALSE;
10844     bookRequested = FALSE;
10845
10846     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10847     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10848     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10849     if(cps->memSize) { /* [HGM] memory */
10850       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10851         SendToProgram(buf, cps);
10852     }
10853     SendEgtPath(cps); /* [HGM] EGT */
10854     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10855       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10856         SendToProgram(buf, cps);
10857     }
10858
10859     setboardSpoiledMachineBlack = FALSE;
10860     SendToProgram(cps->initString, cps);
10861     if (gameInfo.variant != VariantNormal &&
10862         gameInfo.variant != VariantLoadable
10863         /* [HGM] also send variant if board size non-standard */
10864         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10865
10866       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10867                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10868
10869       if (b == NULL) {
10870         VariantClass v;
10871         char c, *q = cps->variants, *p = strchr(q, ',');
10872         if(p) *p = NULLCHAR;
10873         v = StringToVariant(q);
10874         DisplayError(variantError, 0);
10875         if(v != VariantUnknown && cps == &first) {
10876             int w, h, s;
10877             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10878                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10879             ASSIGN(appData.variant, q);
10880             Reset(TRUE, FALSE);
10881         }
10882         if(p) *p = ',';
10883         return;
10884       }
10885
10886       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10887       SendToProgram(buf, cps);
10888     }
10889     currentlyInitializedVariant = gameInfo.variant;
10890
10891     /* [HGM] send opening position in FRC to first engine */
10892     if(setup) {
10893           SendToProgram("force\n", cps);
10894           SendBoard(cps, 0);
10895           /* engine is now in force mode! Set flag to wake it up after first move. */
10896           setboardSpoiledMachineBlack = 1;
10897     }
10898
10899     if (cps->sendICS) {
10900       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10901       SendToProgram(buf, cps);
10902     }
10903     cps->maybeThinking = FALSE;
10904     cps->offeredDraw = 0;
10905     if (!appData.icsActive) {
10906         SendTimeControl(cps, movesPerSession, timeControl,
10907                         timeIncrement, appData.searchDepth,
10908                         searchTime);
10909     }
10910     if (appData.showThinking
10911         // [HGM] thinking: four options require thinking output to be sent
10912         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10913                                 ) {
10914         SendToProgram("post\n", cps);
10915     }
10916     SendToProgram("hard\n", cps);
10917     if (!appData.ponderNextMove) {
10918         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10919            it without being sure what state we are in first.  "hard"
10920            is not a toggle, so that one is OK.
10921          */
10922         SendToProgram("easy\n", cps);
10923     }
10924     if (cps->usePing) {
10925       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10926       SendToProgram(buf, cps);
10927     }
10928     cps->initDone = TRUE;
10929     ClearEngineOutputPane(cps == &second);
10930 }
10931
10932
10933 char *
10934 ResendOptions (ChessProgramState *cps, int toEngine)
10935 { // send the stored value of the options
10936   int i;
10937   static char buf2[MSG_SIZ*10];
10938   char buf[MSG_SIZ], *p = buf2;
10939   Option *opt = cps->option;
10940   *p = NULLCHAR;
10941   for(i=0; i<cps->nrOptions; i++, opt++) {
10942       *buf = NULLCHAR;
10943       switch(opt->type) {
10944         case Spin:
10945         case Slider:
10946         case CheckBox:
10947             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10948             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10949           break;
10950         case ComboBox:
10951             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10952             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10953           break;
10954         default:
10955             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10956             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10957           break;
10958         case Button:
10959         case SaveButton:
10960           continue;
10961       }
10962       if(*buf) {
10963         if(toEngine) {
10964           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10965           SendToProgram(buf2, cps);
10966         } else {
10967           if(p != buf2) *p++ = ',';
10968           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10969           while(*p) p++;
10970         }
10971       }
10972   }
10973   return buf2;
10974 }
10975
10976 void
10977 StartChessProgram (ChessProgramState *cps)
10978 {
10979     char buf[MSG_SIZ];
10980     int err;
10981
10982     if (appData.noChessProgram) return;
10983     cps->initDone = FALSE;
10984
10985     if (strcmp(cps->host, "localhost") == 0) {
10986         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10987     } else if (*appData.remoteShell == NULLCHAR) {
10988         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10989     } else {
10990         if (*appData.remoteUser == NULLCHAR) {
10991           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10992                     cps->program);
10993         } else {
10994           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10995                     cps->host, appData.remoteUser, cps->program);
10996         }
10997         err = StartChildProcess(buf, "", &cps->pr);
10998     }
10999
11000     if (err != 0) {
11001       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11002         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11003         if(cps != &first) return;
11004         appData.noChessProgram = TRUE;
11005         ThawUI();
11006         SetNCPMode();
11007 //      DisplayFatalError(buf, err, 1);
11008 //      cps->pr = NoProc;
11009 //      cps->isr = NULL;
11010         return;
11011     }
11012
11013     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11014     if (cps->protocolVersion > 1) {
11015       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11016       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11017         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11018         cps->comboCnt = 0;  //                and values of combo boxes
11019       }
11020       SendToProgram(buf, cps);
11021       if(cps->reload) ResendOptions(cps, TRUE);
11022     } else {
11023       SendToProgram("xboard\n", cps);
11024     }
11025 }
11026
11027 void
11028 TwoMachinesEventIfReady P((void))
11029 {
11030   static int curMess = 0;
11031   if (first.lastPing != first.lastPong) {
11032     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11033     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11034     return;
11035   }
11036   if (second.lastPing != second.lastPong) {
11037     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11038     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11039     return;
11040   }
11041   DisplayMessage("", ""); curMess = 0;
11042   TwoMachinesEvent();
11043 }
11044
11045 char *
11046 MakeName (char *template)
11047 {
11048     time_t clock;
11049     struct tm *tm;
11050     static char buf[MSG_SIZ];
11051     char *p = buf;
11052     int i;
11053
11054     clock = time((time_t *)NULL);
11055     tm = localtime(&clock);
11056
11057     while(*p++ = *template++) if(p[-1] == '%') {
11058         switch(*template++) {
11059           case 0:   *p = 0; return buf;
11060           case 'Y': i = tm->tm_year+1900; break;
11061           case 'y': i = tm->tm_year-100; break;
11062           case 'M': i = tm->tm_mon+1; break;
11063           case 'd': i = tm->tm_mday; break;
11064           case 'h': i = tm->tm_hour; break;
11065           case 'm': i = tm->tm_min; break;
11066           case 's': i = tm->tm_sec; break;
11067           default:  i = 0;
11068         }
11069         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11070     }
11071     return buf;
11072 }
11073
11074 int
11075 CountPlayers (char *p)
11076 {
11077     int n = 0;
11078     while(p = strchr(p, '\n')) p++, n++; // count participants
11079     return n;
11080 }
11081
11082 FILE *
11083 WriteTourneyFile (char *results, FILE *f)
11084 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11085     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11086     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11087         // create a file with tournament description
11088         fprintf(f, "-participants {%s}\n", appData.participants);
11089         fprintf(f, "-seedBase %d\n", appData.seedBase);
11090         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11091         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11092         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11093         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11094         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11095         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11096         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11097         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11098         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11099         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11100         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11101         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11102         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11103         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11104         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11105         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11106         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11107         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11108         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11109         fprintf(f, "-smpCores %d\n", appData.smpCores);
11110         if(searchTime > 0)
11111                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11112         else {
11113                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11114                 fprintf(f, "-tc %s\n", appData.timeControl);
11115                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11116         }
11117         fprintf(f, "-results \"%s\"\n", results);
11118     }
11119     return f;
11120 }
11121
11122 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11123
11124 void
11125 Substitute (char *participants, int expunge)
11126 {
11127     int i, changed, changes=0, nPlayers=0;
11128     char *p, *q, *r, buf[MSG_SIZ];
11129     if(participants == NULL) return;
11130     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11131     r = p = participants; q = appData.participants;
11132     while(*p && *p == *q) {
11133         if(*p == '\n') r = p+1, nPlayers++;
11134         p++; q++;
11135     }
11136     if(*p) { // difference
11137         while(*p && *p++ != '\n')
11138                                  ;
11139         while(*q && *q++ != '\n')
11140                                  ;
11141       changed = nPlayers;
11142         changes = 1 + (strcmp(p, q) != 0);
11143     }
11144     if(changes == 1) { // a single engine mnemonic was changed
11145         q = r; while(*q) nPlayers += (*q++ == '\n');
11146         p = buf; while(*r && (*p = *r++) != '\n') p++;
11147         *p = NULLCHAR;
11148         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11149         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11150         if(mnemonic[i]) { // The substitute is valid
11151             FILE *f;
11152             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11153                 flock(fileno(f), LOCK_EX);
11154                 ParseArgsFromFile(f);
11155                 fseek(f, 0, SEEK_SET);
11156                 FREE(appData.participants); appData.participants = participants;
11157                 if(expunge) { // erase results of replaced engine
11158                     int len = strlen(appData.results), w, b, dummy;
11159                     for(i=0; i<len; i++) {
11160                         Pairing(i, nPlayers, &w, &b, &dummy);
11161                         if((w == changed || b == changed) && appData.results[i] == '*') {
11162                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11163                             fclose(f);
11164                             return;
11165                         }
11166                     }
11167                     for(i=0; i<len; i++) {
11168                         Pairing(i, nPlayers, &w, &b, &dummy);
11169                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11170                     }
11171                 }
11172                 WriteTourneyFile(appData.results, f);
11173                 fclose(f); // release lock
11174                 return;
11175             }
11176         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11177     }
11178     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11179     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11180     free(participants);
11181     return;
11182 }
11183
11184 int
11185 CheckPlayers (char *participants)
11186 {
11187         int i;
11188         char buf[MSG_SIZ], *p;
11189         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11190         while(p = strchr(participants, '\n')) {
11191             *p = NULLCHAR;
11192             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11193             if(!mnemonic[i]) {
11194                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11195                 *p = '\n';
11196                 DisplayError(buf, 0);
11197                 return 1;
11198             }
11199             *p = '\n';
11200             participants = p + 1;
11201         }
11202         return 0;
11203 }
11204
11205 int
11206 CreateTourney (char *name)
11207 {
11208         FILE *f;
11209         if(matchMode && strcmp(name, appData.tourneyFile)) {
11210              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11211         }
11212         if(name[0] == NULLCHAR) {
11213             if(appData.participants[0])
11214                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11215             return 0;
11216         }
11217         f = fopen(name, "r");
11218         if(f) { // file exists
11219             ASSIGN(appData.tourneyFile, name);
11220             ParseArgsFromFile(f); // parse it
11221         } else {
11222             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11223             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11224                 DisplayError(_("Not enough participants"), 0);
11225                 return 0;
11226             }
11227             if(CheckPlayers(appData.participants)) return 0;
11228             ASSIGN(appData.tourneyFile, name);
11229             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11230             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11231         }
11232         fclose(f);
11233         appData.noChessProgram = FALSE;
11234         appData.clockMode = TRUE;
11235         SetGNUMode();
11236         return 1;
11237 }
11238
11239 int
11240 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11241 {
11242     char buf[2*MSG_SIZ], *p, *q;
11243     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11244     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11245     skip = !all && group[0]; // if group requested, we start in skip mode
11246     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11247         p = names; q = buf; header = 0;
11248         while(*p && *p != '\n') *q++ = *p++;
11249         *q = 0;
11250         if(*p == '\n') p++;
11251         if(buf[0] == '#') {
11252             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11253             depth++; // we must be entering a new group
11254             if(all) continue; // suppress printing group headers when complete list requested
11255             header = 1;
11256             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11257         }
11258         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11259         if(engineList[i]) free(engineList[i]);
11260         engineList[i] = strdup(buf);
11261         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11262         if(engineMnemonic[i]) free(engineMnemonic[i]);
11263         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11264             strcat(buf, " (");
11265             sscanf(q + 8, "%s", buf + strlen(buf));
11266             strcat(buf, ")");
11267         }
11268         engineMnemonic[i] = strdup(buf);
11269         i++;
11270     }
11271     engineList[i] = engineMnemonic[i] = NULL;
11272     return i;
11273 }
11274
11275 void
11276 SaveEngineSettings (int n)
11277 {
11278     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11279     if(!currentEngine[n] || !currentEngine[n][0]) return; // no engine from list is loaded
11280     p = strstr(firstChessProgramNames, currentEngine[n]);
11281     if(!p) return; // sanity check; engine could be deleted from list after loading
11282     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11283     len = strlen(currentEngine[n]);
11284     q = p + len; *p = 0; // cut list into head and tail piece
11285     s = strstr(currentEngine[n], "firstOptions");
11286     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11287         char *r = s + 14;
11288         while(*r && *r != s[13]) r++;
11289         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11290         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11291     } else if(*optionSettings) {
11292         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11293     }
11294     ASSIGN(currentEngine[n], buf); // updated engine line
11295     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11296     s = malloc(len);
11297     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11298     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11299 }
11300
11301 // following implemented as macro to avoid type limitations
11302 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11303
11304 void
11305 SwapEngines (int n)
11306 {   // swap settings for first engine and other engine (so far only some selected options)
11307     int h;
11308     char *p;
11309     if(n == 0) return;
11310     SWAP(directory, p)
11311     SWAP(chessProgram, p)
11312     SWAP(isUCI, h)
11313     SWAP(hasOwnBookUCI, h)
11314     SWAP(protocolVersion, h)
11315     SWAP(reuse, h)
11316     SWAP(scoreIsAbsolute, h)
11317     SWAP(timeOdds, h)
11318     SWAP(logo, p)
11319     SWAP(pgnName, p)
11320     SWAP(pvSAN, h)
11321     SWAP(engOptions, p)
11322     SWAP(engInitString, p)
11323     SWAP(computerString, p)
11324     SWAP(features, p)
11325     SWAP(fenOverride, p)
11326     SWAP(NPS, h)
11327     SWAP(accumulateTC, h)
11328     SWAP(drawDepth, h)
11329     SWAP(host, p)
11330     SWAP(pseudo, h)
11331 }
11332
11333 int
11334 GetEngineLine (char *s, int n)
11335 {
11336     int i;
11337     char buf[MSG_SIZ];
11338     extern char *icsNames;
11339     if(!s || !*s) return 0;
11340     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11341     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11342     if(!mnemonic[i]) return 0;
11343     if(n == 11) return 1; // just testing if there was a match
11344     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11345     if(n == 1) SwapEngines(n);
11346     ParseArgsFromString(buf);
11347     if(n == 1) SwapEngines(n);
11348     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11349         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11350         ParseArgsFromString(buf);
11351     }
11352     return 1;
11353 }
11354
11355 int
11356 SetPlayer (int player, char *p)
11357 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11358     int i;
11359     char buf[MSG_SIZ], *engineName;
11360     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11361     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11362     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11363     if(mnemonic[i]) {
11364         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11365         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11366         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11367         ParseArgsFromString(buf);
11368     } else { // no engine with this nickname is installed!
11369         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11370         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11371         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11372         ModeHighlight();
11373         DisplayError(buf, 0);
11374         return 0;
11375     }
11376     free(engineName);
11377     return i;
11378 }
11379
11380 char *recentEngines;
11381
11382 void
11383 RecentEngineEvent (int nr)
11384 {
11385     int n;
11386 //    SwapEngines(1); // bump first to second
11387 //    ReplaceEngine(&second, 1); // and load it there
11388     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11389     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11390     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11391         ReplaceEngine(&first, 0);
11392         FloatToFront(&appData.recentEngineList, command[n]);
11393     }
11394 }
11395
11396 int
11397 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11398 {   // determine players from game number
11399     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11400
11401     if(appData.tourneyType == 0) {
11402         roundsPerCycle = (nPlayers - 1) | 1;
11403         pairingsPerRound = nPlayers / 2;
11404     } else if(appData.tourneyType > 0) {
11405         roundsPerCycle = nPlayers - appData.tourneyType;
11406         pairingsPerRound = appData.tourneyType;
11407     }
11408     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11409     gamesPerCycle = gamesPerRound * roundsPerCycle;
11410     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11411     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11412     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11413     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11414     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11415     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11416
11417     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11418     if(appData.roundSync) *syncInterval = gamesPerRound;
11419
11420     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11421
11422     if(appData.tourneyType == 0) {
11423         if(curPairing == (nPlayers-1)/2 ) {
11424             *whitePlayer = curRound;
11425             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11426         } else {
11427             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11428             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11429             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11430             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11431         }
11432     } else if(appData.tourneyType > 1) {
11433         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11434         *whitePlayer = curRound + appData.tourneyType;
11435     } else if(appData.tourneyType > 0) {
11436         *whitePlayer = curPairing;
11437         *blackPlayer = curRound + appData.tourneyType;
11438     }
11439
11440     // take care of white/black alternation per round.
11441     // For cycles and games this is already taken care of by default, derived from matchGame!
11442     return curRound & 1;
11443 }
11444
11445 int
11446 NextTourneyGame (int nr, int *swapColors)
11447 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11448     char *p, *q;
11449     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11450     FILE *tf;
11451     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11452     tf = fopen(appData.tourneyFile, "r");
11453     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11454     ParseArgsFromFile(tf); fclose(tf);
11455     InitTimeControls(); // TC might be altered from tourney file
11456
11457     nPlayers = CountPlayers(appData.participants); // count participants
11458     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11459     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11460
11461     if(syncInterval) {
11462         p = q = appData.results;
11463         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11464         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11465             DisplayMessage(_("Waiting for other game(s)"),"");
11466             waitingForGame = TRUE;
11467             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11468             return 0;
11469         }
11470         waitingForGame = FALSE;
11471     }
11472
11473     if(appData.tourneyType < 0) {
11474         if(nr>=0 && !pairingReceived) {
11475             char buf[1<<16];
11476             if(pairing.pr == NoProc) {
11477                 if(!appData.pairingEngine[0]) {
11478                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11479                     return 0;
11480                 }
11481                 StartChessProgram(&pairing); // starts the pairing engine
11482             }
11483             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11484             SendToProgram(buf, &pairing);
11485             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11486             SendToProgram(buf, &pairing);
11487             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11488         }
11489         pairingReceived = 0;                              // ... so we continue here
11490         *swapColors = 0;
11491         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11492         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11493         matchGame = 1; roundNr = nr / syncInterval + 1;
11494     }
11495
11496     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11497
11498     // redefine engines, engine dir, etc.
11499     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11500     if(first.pr == NoProc) {
11501       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11502       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11503     }
11504     if(second.pr == NoProc) {
11505       SwapEngines(1);
11506       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11507       SwapEngines(1);         // and make that valid for second engine by swapping
11508       InitEngine(&second, 1);
11509     }
11510     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11511     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11512     return OK;
11513 }
11514
11515 void
11516 NextMatchGame ()
11517 {   // performs game initialization that does not invoke engines, and then tries to start the game
11518     int res, firstWhite, swapColors = 0;
11519     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11520     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
11521         char buf[MSG_SIZ];
11522         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11523         if(strcmp(buf, currentDebugFile)) { // name has changed
11524             FILE *f = fopen(buf, "w");
11525             if(f) { // if opening the new file failed, just keep using the old one
11526                 ASSIGN(currentDebugFile, buf);
11527                 fclose(debugFP);
11528                 debugFP = f;
11529             }
11530             if(appData.serverFileName) {
11531                 if(serverFP) fclose(serverFP);
11532                 serverFP = fopen(appData.serverFileName, "w");
11533                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11534                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11535             }
11536         }
11537     }
11538     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11539     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11540     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11541     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11542     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11543     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11544     Reset(FALSE, first.pr != NoProc);
11545     res = LoadGameOrPosition(matchGame); // setup game
11546     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11547     if(!res) return; // abort when bad game/pos file
11548     if(appData.epd) {// in EPD mode we make sure first engine is to move
11549         firstWhite = !(forwardMostMove & 1);
11550         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11551         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11552     }
11553     TwoMachinesEvent();
11554 }
11555
11556 void
11557 UserAdjudicationEvent (int result)
11558 {
11559     ChessMove gameResult = GameIsDrawn;
11560
11561     if( result > 0 ) {
11562         gameResult = WhiteWins;
11563     }
11564     else if( result < 0 ) {
11565         gameResult = BlackWins;
11566     }
11567
11568     if( gameMode == TwoMachinesPlay ) {
11569         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11570     }
11571 }
11572
11573
11574 // [HGM] save: calculate checksum of game to make games easily identifiable
11575 int
11576 StringCheckSum (char *s)
11577 {
11578         int i = 0;
11579         if(s==NULL) return 0;
11580         while(*s) i = i*259 + *s++;
11581         return i;
11582 }
11583
11584 int
11585 GameCheckSum ()
11586 {
11587         int i, sum=0;
11588         for(i=backwardMostMove; i<forwardMostMove; i++) {
11589                 sum += pvInfoList[i].depth;
11590                 sum += StringCheckSum(parseList[i]);
11591                 sum += StringCheckSum(commentList[i]);
11592                 sum *= 261;
11593         }
11594         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11595         return sum + StringCheckSum(commentList[i]);
11596 } // end of save patch
11597
11598 void
11599 GameEnds (ChessMove result, char *resultDetails, int whosays)
11600 {
11601     GameMode nextGameMode;
11602     int isIcsGame;
11603     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11604
11605     if(endingGame) return; /* [HGM] crash: forbid recursion */
11606     endingGame = 1;
11607     if(twoBoards) { // [HGM] dual: switch back to one board
11608         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11609         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11610     }
11611     if (appData.debugMode) {
11612       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11613               result, resultDetails ? resultDetails : "(null)", whosays);
11614     }
11615
11616     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11617
11618     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11619
11620     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11621         /* If we are playing on ICS, the server decides when the
11622            game is over, but the engine can offer to draw, claim
11623            a draw, or resign.
11624          */
11625 #if ZIPPY
11626         if (appData.zippyPlay && first.initDone) {
11627             if (result == GameIsDrawn) {
11628                 /* In case draw still needs to be claimed */
11629                 SendToICS(ics_prefix);
11630                 SendToICS("draw\n");
11631             } else if (StrCaseStr(resultDetails, "resign")) {
11632                 SendToICS(ics_prefix);
11633                 SendToICS("resign\n");
11634             }
11635         }
11636 #endif
11637         endingGame = 0; /* [HGM] crash */
11638         return;
11639     }
11640
11641     /* If we're loading the game from a file, stop */
11642     if (whosays == GE_FILE) {
11643       (void) StopLoadGameTimer();
11644       gameFileFP = NULL;
11645     }
11646
11647     /* Cancel draw offers */
11648     first.offeredDraw = second.offeredDraw = 0;
11649
11650     /* If this is an ICS game, only ICS can really say it's done;
11651        if not, anyone can. */
11652     isIcsGame = (gameMode == IcsPlayingWhite ||
11653                  gameMode == IcsPlayingBlack ||
11654                  gameMode == IcsObserving    ||
11655                  gameMode == IcsExamining);
11656
11657     if (!isIcsGame || whosays == GE_ICS) {
11658         /* OK -- not an ICS game, or ICS said it was done */
11659         StopClocks();
11660         if (!isIcsGame && !appData.noChessProgram)
11661           SetUserThinkingEnables();
11662
11663         /* [HGM] if a machine claims the game end we verify this claim */
11664         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11665             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11666                 char claimer;
11667                 ChessMove trueResult = (ChessMove) -1;
11668
11669                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11670                                             first.twoMachinesColor[0] :
11671                                             second.twoMachinesColor[0] ;
11672
11673                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11674                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11675                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11676                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11677                 } else
11678                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11679                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11680                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11681                 } else
11682                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11683                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11684                 }
11685
11686                 // now verify win claims, but not in drop games, as we don't understand those yet
11687                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11688                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11689                     (result == WhiteWins && claimer == 'w' ||
11690                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11691                       if (appData.debugMode) {
11692                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11693                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11694                       }
11695                       if(result != trueResult) {
11696                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11697                               result = claimer == 'w' ? BlackWins : WhiteWins;
11698                               resultDetails = buf;
11699                       }
11700                 } else
11701                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11702                     && (forwardMostMove <= backwardMostMove ||
11703                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11704                         (claimer=='b')==(forwardMostMove&1))
11705                                                                                   ) {
11706                       /* [HGM] verify: draws that were not flagged are false claims */
11707                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11708                       result = claimer == 'w' ? BlackWins : WhiteWins;
11709                       resultDetails = buf;
11710                 }
11711                 /* (Claiming a loss is accepted no questions asked!) */
11712             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11713                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11714                 result = GameUnfinished;
11715                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11716             }
11717             /* [HGM] bare: don't allow bare King to win */
11718             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11719                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11720                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11721                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11722                && result != GameIsDrawn)
11723             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11724                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11725                         int p = (int)boards[forwardMostMove][i][j] - color;
11726                         if(p >= 0 && p <= (int)WhiteKing) k++;
11727                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11728                 }
11729                 if (appData.debugMode) {
11730                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11731                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11732                 }
11733                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11734                         result = GameIsDrawn;
11735                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11736                         resultDetails = buf;
11737                 }
11738             }
11739         }
11740
11741
11742         if(serverMoves != NULL && !loadFlag) { char c = '=';
11743             if(result==WhiteWins) c = '+';
11744             if(result==BlackWins) c = '-';
11745             if(resultDetails != NULL)
11746                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11747         }
11748         if (resultDetails != NULL) {
11749             gameInfo.result = result;
11750             gameInfo.resultDetails = StrSave(resultDetails);
11751
11752             /* display last move only if game was not loaded from file */
11753             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11754                 DisplayMove(currentMove - 1);
11755
11756             if (forwardMostMove != 0) {
11757                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11758                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11759                                                                 ) {
11760                     if (*appData.saveGameFile != NULLCHAR) {
11761                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11762                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11763                         else
11764                         SaveGameToFile(appData.saveGameFile, TRUE);
11765                     } else if (appData.autoSaveGames) {
11766                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11767                     }
11768                     if (*appData.savePositionFile != NULLCHAR) {
11769                         SavePositionToFile(appData.savePositionFile);
11770                     }
11771                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11772                 }
11773             }
11774
11775             /* Tell program how game ended in case it is learning */
11776             /* [HGM] Moved this to after saving the PGN, just in case */
11777             /* engine died and we got here through time loss. In that */
11778             /* case we will get a fatal error writing the pipe, which */
11779             /* would otherwise lose us the PGN.                       */
11780             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11781             /* output during GameEnds should never be fatal anymore   */
11782             if (gameMode == MachinePlaysWhite ||
11783                 gameMode == MachinePlaysBlack ||
11784                 gameMode == TwoMachinesPlay ||
11785                 gameMode == IcsPlayingWhite ||
11786                 gameMode == IcsPlayingBlack ||
11787                 gameMode == BeginningOfGame) {
11788                 char buf[MSG_SIZ];
11789                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11790                         resultDetails);
11791                 if (first.pr != NoProc) {
11792                     SendToProgram(buf, &first);
11793                 }
11794                 if (second.pr != NoProc &&
11795                     gameMode == TwoMachinesPlay) {
11796                     SendToProgram(buf, &second);
11797                 }
11798             }
11799         }
11800
11801         if (appData.icsActive) {
11802             if (appData.quietPlay &&
11803                 (gameMode == IcsPlayingWhite ||
11804                  gameMode == IcsPlayingBlack)) {
11805                 SendToICS(ics_prefix);
11806                 SendToICS("set shout 1\n");
11807             }
11808             nextGameMode = IcsIdle;
11809             ics_user_moved = FALSE;
11810             /* clean up premove.  It's ugly when the game has ended and the
11811              * premove highlights are still on the board.
11812              */
11813             if (gotPremove) {
11814               gotPremove = FALSE;
11815               ClearPremoveHighlights();
11816               DrawPosition(FALSE, boards[currentMove]);
11817             }
11818             if (whosays == GE_ICS) {
11819                 switch (result) {
11820                 case WhiteWins:
11821                     if (gameMode == IcsPlayingWhite)
11822                         PlayIcsWinSound();
11823                     else if(gameMode == IcsPlayingBlack)
11824                         PlayIcsLossSound();
11825                     break;
11826                 case BlackWins:
11827                     if (gameMode == IcsPlayingBlack)
11828                         PlayIcsWinSound();
11829                     else if(gameMode == IcsPlayingWhite)
11830                         PlayIcsLossSound();
11831                     break;
11832                 case GameIsDrawn:
11833                     PlayIcsDrawSound();
11834                     break;
11835                 default:
11836                     PlayIcsUnfinishedSound();
11837                 }
11838             }
11839             if(appData.quitNext) { ExitEvent(0); return; }
11840         } else if (gameMode == EditGame ||
11841                    gameMode == PlayFromGameFile ||
11842                    gameMode == AnalyzeMode ||
11843                    gameMode == AnalyzeFile) {
11844             nextGameMode = gameMode;
11845         } else {
11846             nextGameMode = EndOfGame;
11847         }
11848         pausing = FALSE;
11849         ModeHighlight();
11850     } else {
11851         nextGameMode = gameMode;
11852     }
11853
11854     if (appData.noChessProgram) {
11855         gameMode = nextGameMode;
11856         ModeHighlight();
11857         endingGame = 0; /* [HGM] crash */
11858         return;
11859     }
11860
11861     if (first.reuse) {
11862         /* Put first chess program into idle state */
11863         if (first.pr != NoProc &&
11864             (gameMode == MachinePlaysWhite ||
11865              gameMode == MachinePlaysBlack ||
11866              gameMode == TwoMachinesPlay ||
11867              gameMode == IcsPlayingWhite ||
11868              gameMode == IcsPlayingBlack ||
11869              gameMode == BeginningOfGame)) {
11870             SendToProgram("force\n", &first);
11871             if (first.usePing) {
11872               char buf[MSG_SIZ];
11873               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11874               SendToProgram(buf, &first);
11875             }
11876         }
11877     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11878         /* Kill off first chess program */
11879         if (first.isr != NULL)
11880           RemoveInputSource(first.isr);
11881         first.isr = NULL;
11882
11883         if (first.pr != NoProc) {
11884             ExitAnalyzeMode();
11885             DoSleep( appData.delayBeforeQuit );
11886             SendToProgram("quit\n", &first);
11887             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11888             first.reload = TRUE;
11889         }
11890         first.pr = NoProc;
11891     }
11892     if (second.reuse) {
11893         /* Put second chess program into idle state */
11894         if (second.pr != NoProc &&
11895             gameMode == TwoMachinesPlay) {
11896             SendToProgram("force\n", &second);
11897             if (second.usePing) {
11898               char buf[MSG_SIZ];
11899               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11900               SendToProgram(buf, &second);
11901             }
11902         }
11903     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11904         /* Kill off second chess program */
11905         if (second.isr != NULL)
11906           RemoveInputSource(second.isr);
11907         second.isr = NULL;
11908
11909         if (second.pr != NoProc) {
11910             DoSleep( appData.delayBeforeQuit );
11911             SendToProgram("quit\n", &second);
11912             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11913             second.reload = TRUE;
11914         }
11915         second.pr = NoProc;
11916     }
11917
11918     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11919         char resChar = '=';
11920         switch (result) {
11921         case WhiteWins:
11922           resChar = '+';
11923           if (first.twoMachinesColor[0] == 'w') {
11924             first.matchWins++;
11925           } else {
11926             second.matchWins++;
11927           }
11928           break;
11929         case BlackWins:
11930           resChar = '-';
11931           if (first.twoMachinesColor[0] == 'b') {
11932             first.matchWins++;
11933           } else {
11934             second.matchWins++;
11935           }
11936           break;
11937         case GameUnfinished:
11938           resChar = ' ';
11939         default:
11940           break;
11941         }
11942
11943         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11944         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11945             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11946             ReserveGame(nextGame, resChar); // sets nextGame
11947             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11948             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11949         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11950
11951         if (nextGame <= appData.matchGames && !abortMatch) {
11952             gameMode = nextGameMode;
11953             matchGame = nextGame; // this will be overruled in tourney mode!
11954             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11955             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11956             endingGame = 0; /* [HGM] crash */
11957             return;
11958         } else {
11959             gameMode = nextGameMode;
11960             if(appData.epd) {
11961                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11962                 OutputKibitz(2, buf);
11963                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11964                 OutputKibitz(2, buf);
11965                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11966                 if(second.matchWins) OutputKibitz(2, buf);
11967                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11968                 OutputKibitz(2, buf);
11969             }
11970             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11971                      first.tidy, second.tidy,
11972                      first.matchWins, second.matchWins,
11973                      appData.matchGames - (first.matchWins + second.matchWins));
11974             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11975             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11976             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11977             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11978                 first.twoMachinesColor = "black\n";
11979                 second.twoMachinesColor = "white\n";
11980             } else {
11981                 first.twoMachinesColor = "white\n";
11982                 second.twoMachinesColor = "black\n";
11983             }
11984         }
11985     }
11986     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11987         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11988       ExitAnalyzeMode();
11989     gameMode = nextGameMode;
11990     ModeHighlight();
11991     endingGame = 0;  /* [HGM] crash */
11992     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11993         if(matchMode == TRUE) { // match through command line: exit with or without popup
11994             if(ranking) {
11995                 ToNrEvent(forwardMostMove);
11996                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11997                 else ExitEvent(0);
11998             } else DisplayFatalError(buf, 0, 0);
11999         } else { // match through menu; just stop, with or without popup
12000             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12001             ModeHighlight();
12002             if(ranking){
12003                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12004             } else DisplayNote(buf);
12005       }
12006       if(ranking) free(ranking);
12007     }
12008 }
12009
12010 /* Assumes program was just initialized (initString sent).
12011    Leaves program in force mode. */
12012 void
12013 FeedMovesToProgram (ChessProgramState *cps, int upto)
12014 {
12015     int i;
12016
12017     if (appData.debugMode)
12018       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12019               startedFromSetupPosition ? "position and " : "",
12020               backwardMostMove, upto, cps->which);
12021     if(currentlyInitializedVariant != gameInfo.variant) {
12022       char buf[MSG_SIZ];
12023         // [HGM] variantswitch: make engine aware of new variant
12024         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12025                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12026                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12027         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12028         SendToProgram(buf, cps);
12029         currentlyInitializedVariant = gameInfo.variant;
12030     }
12031     SendToProgram("force\n", cps);
12032     if (startedFromSetupPosition) {
12033         SendBoard(cps, backwardMostMove);
12034     if (appData.debugMode) {
12035         fprintf(debugFP, "feedMoves\n");
12036     }
12037     }
12038     for (i = backwardMostMove; i < upto; i++) {
12039         SendMoveToProgram(i, cps);
12040     }
12041 }
12042
12043
12044 int
12045 ResurrectChessProgram ()
12046 {
12047      /* The chess program may have exited.
12048         If so, restart it and feed it all the moves made so far. */
12049     static int doInit = 0;
12050
12051     if (appData.noChessProgram) return 1;
12052
12053     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12054         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12055         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12056         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12057     } else {
12058         if (first.pr != NoProc) return 1;
12059         StartChessProgram(&first);
12060     }
12061     InitChessProgram(&first, FALSE);
12062     FeedMovesToProgram(&first, currentMove);
12063
12064     if (!first.sendTime) {
12065         /* can't tell gnuchess what its clock should read,
12066            so we bow to its notion. */
12067         ResetClocks();
12068         timeRemaining[0][currentMove] = whiteTimeRemaining;
12069         timeRemaining[1][currentMove] = blackTimeRemaining;
12070     }
12071
12072     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12073                 appData.icsEngineAnalyze) && first.analysisSupport) {
12074       SendToProgram("analyze\n", &first);
12075       first.analyzing = TRUE;
12076     }
12077     return 1;
12078 }
12079
12080 /*
12081  * Button procedures
12082  */
12083 void
12084 Reset (int redraw, int init)
12085 {
12086     int i;
12087
12088     if (appData.debugMode) {
12089         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12090                 redraw, init, gameMode);
12091     }
12092     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12093     deadRanks = 0; // assume entire board is used
12094     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12095     CleanupTail(); // [HGM] vari: delete any stored variations
12096     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12097     pausing = pauseExamInvalid = FALSE;
12098     startedFromSetupPosition = blackPlaysFirst = FALSE;
12099     firstMove = TRUE;
12100     whiteFlag = blackFlag = FALSE;
12101     userOfferedDraw = FALSE;
12102     hintRequested = bookRequested = FALSE;
12103     first.maybeThinking = FALSE;
12104     second.maybeThinking = FALSE;
12105     first.bookSuspend = FALSE; // [HGM] book
12106     second.bookSuspend = FALSE;
12107     thinkOutput[0] = NULLCHAR;
12108     lastHint[0] = NULLCHAR;
12109     ClearGameInfo(&gameInfo);
12110     gameInfo.variant = StringToVariant(appData.variant);
12111     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12112         gameInfo.variant = VariantUnknown;
12113         strncpy(engineVariant, appData.variant, MSG_SIZ);
12114     }
12115     ics_user_moved = ics_clock_paused = FALSE;
12116     ics_getting_history = H_FALSE;
12117     ics_gamenum = -1;
12118     white_holding[0] = black_holding[0] = NULLCHAR;
12119     ClearProgramStats();
12120     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12121
12122     ResetFrontEnd();
12123     ClearHighlights();
12124     flipView = appData.flipView;
12125     ClearPremoveHighlights();
12126     gotPremove = FALSE;
12127     alarmSounded = FALSE;
12128     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12129
12130     GameEnds(EndOfFile, NULL, GE_PLAYER);
12131     if(appData.serverMovesName != NULL) {
12132         /* [HGM] prepare to make moves file for broadcasting */
12133         clock_t t = clock();
12134         if(serverMoves != NULL) fclose(serverMoves);
12135         serverMoves = fopen(appData.serverMovesName, "r");
12136         if(serverMoves != NULL) {
12137             fclose(serverMoves);
12138             /* delay 15 sec before overwriting, so all clients can see end */
12139             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12140         }
12141         serverMoves = fopen(appData.serverMovesName, "w");
12142     }
12143
12144     ExitAnalyzeMode();
12145     gameMode = BeginningOfGame;
12146     ModeHighlight();
12147     if(appData.icsActive) gameInfo.variant = VariantNormal;
12148     currentMove = forwardMostMove = backwardMostMove = 0;
12149     MarkTargetSquares(1);
12150     InitPosition(redraw);
12151     for (i = 0; i < MAX_MOVES; i++) {
12152         if (commentList[i] != NULL) {
12153             free(commentList[i]);
12154             commentList[i] = NULL;
12155         }
12156     }
12157     ResetClocks();
12158     timeRemaining[0][0] = whiteTimeRemaining;
12159     timeRemaining[1][0] = blackTimeRemaining;
12160
12161     if (first.pr == NoProc) {
12162         StartChessProgram(&first);
12163     }
12164     if (init) {
12165             InitChessProgram(&first, startedFromSetupPosition);
12166     }
12167     DisplayTitle("");
12168     DisplayMessage("", "");
12169     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12170     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12171     ClearMap();        // [HGM] exclude: invalidate map
12172 }
12173
12174 void
12175 AutoPlayGameLoop ()
12176 {
12177     for (;;) {
12178         if (!AutoPlayOneMove())
12179           return;
12180         if (matchMode || appData.timeDelay == 0)
12181           continue;
12182         if (appData.timeDelay < 0)
12183           return;
12184         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12185         break;
12186     }
12187 }
12188
12189 void
12190 AnalyzeNextGame()
12191 {
12192     ReloadGame(1); // next game
12193 }
12194
12195 int
12196 AutoPlayOneMove ()
12197 {
12198     int fromX, fromY, toX, toY;
12199
12200     if (appData.debugMode) {
12201       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12202     }
12203
12204     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12205       return FALSE;
12206
12207     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12208       pvInfoList[currentMove].depth = programStats.depth;
12209       pvInfoList[currentMove].score = programStats.score;
12210       pvInfoList[currentMove].time  = 0;
12211       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12212       else { // append analysis of final position as comment
12213         char buf[MSG_SIZ];
12214         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12215         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12216       }
12217       programStats.depth = 0;
12218     }
12219
12220     if (currentMove >= forwardMostMove) {
12221       if(gameMode == AnalyzeFile) {
12222           if(appData.loadGameIndex == -1) {
12223             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12224           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12225           } else {
12226           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12227         }
12228       }
12229 //      gameMode = EndOfGame;
12230 //      ModeHighlight();
12231
12232       /* [AS] Clear current move marker at the end of a game */
12233       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12234
12235       return FALSE;
12236     }
12237
12238     toX = moveList[currentMove][2] - AAA;
12239     toY = moveList[currentMove][3] - ONE;
12240
12241     if (moveList[currentMove][1] == '@') {
12242         if (appData.highlightLastMove) {
12243             SetHighlights(-1, -1, toX, toY);
12244         }
12245     } else {
12246         fromX = moveList[currentMove][0] - AAA;
12247         fromY = moveList[currentMove][1] - ONE;
12248
12249         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12250
12251         if(moveList[currentMove][4] == ';') { // multi-leg
12252             killX = moveList[currentMove][5] - AAA;
12253             killY = moveList[currentMove][6] - ONE;
12254         }
12255         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12256         killX = killY = -1;
12257
12258         if (appData.highlightLastMove) {
12259             SetHighlights(fromX, fromY, toX, toY);
12260         }
12261     }
12262     DisplayMove(currentMove);
12263     SendMoveToProgram(currentMove++, &first);
12264     DisplayBothClocks();
12265     DrawPosition(FALSE, boards[currentMove]);
12266     // [HGM] PV info: always display, routine tests if empty
12267     DisplayComment(currentMove - 1, commentList[currentMove]);
12268     return TRUE;
12269 }
12270
12271
12272 int
12273 LoadGameOneMove (ChessMove readAhead)
12274 {
12275     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12276     char promoChar = NULLCHAR;
12277     ChessMove moveType;
12278     char move[MSG_SIZ];
12279     char *p, *q;
12280
12281     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12282         gameMode != AnalyzeMode && gameMode != Training) {
12283         gameFileFP = NULL;
12284         return FALSE;
12285     }
12286
12287     yyboardindex = forwardMostMove;
12288     if (readAhead != EndOfFile) {
12289       moveType = readAhead;
12290     } else {
12291       if (gameFileFP == NULL)
12292           return FALSE;
12293       moveType = (ChessMove) Myylex();
12294     }
12295
12296     done = FALSE;
12297     switch (moveType) {
12298       case Comment:
12299         if (appData.debugMode)
12300           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12301         p = yy_text;
12302
12303         /* append the comment but don't display it */
12304         AppendComment(currentMove, p, FALSE);
12305         return TRUE;
12306
12307       case WhiteCapturesEnPassant:
12308       case BlackCapturesEnPassant:
12309       case WhitePromotion:
12310       case BlackPromotion:
12311       case WhiteNonPromotion:
12312       case BlackNonPromotion:
12313       case NormalMove:
12314       case FirstLeg:
12315       case WhiteKingSideCastle:
12316       case WhiteQueenSideCastle:
12317       case BlackKingSideCastle:
12318       case BlackQueenSideCastle:
12319       case WhiteKingSideCastleWild:
12320       case WhiteQueenSideCastleWild:
12321       case BlackKingSideCastleWild:
12322       case BlackQueenSideCastleWild:
12323       /* PUSH Fabien */
12324       case WhiteHSideCastleFR:
12325       case WhiteASideCastleFR:
12326       case BlackHSideCastleFR:
12327       case BlackASideCastleFR:
12328       /* POP Fabien */
12329         if (appData.debugMode)
12330           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12331         fromX = currentMoveString[0] - AAA;
12332         fromY = currentMoveString[1] - ONE;
12333         toX = currentMoveString[2] - AAA;
12334         toY = currentMoveString[3] - ONE;
12335         promoChar = currentMoveString[4];
12336         if(promoChar == ';') promoChar = currentMoveString[7];
12337         break;
12338
12339       case WhiteDrop:
12340       case BlackDrop:
12341         if (appData.debugMode)
12342           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12343         fromX = moveType == WhiteDrop ?
12344           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12345         (int) CharToPiece(ToLower(currentMoveString[0]));
12346         fromY = DROP_RANK;
12347         toX = currentMoveString[2] - AAA;
12348         toY = currentMoveString[3] - ONE;
12349         break;
12350
12351       case WhiteWins:
12352       case BlackWins:
12353       case GameIsDrawn:
12354       case GameUnfinished:
12355         if (appData.debugMode)
12356           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12357         p = strchr(yy_text, '{');
12358         if (p == NULL) p = strchr(yy_text, '(');
12359         if (p == NULL) {
12360             p = yy_text;
12361             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12362         } else {
12363             q = strchr(p, *p == '{' ? '}' : ')');
12364             if (q != NULL) *q = NULLCHAR;
12365             p++;
12366         }
12367         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12368         GameEnds(moveType, p, GE_FILE);
12369         done = TRUE;
12370         if (cmailMsgLoaded) {
12371             ClearHighlights();
12372             flipView = WhiteOnMove(currentMove);
12373             if (moveType == GameUnfinished) flipView = !flipView;
12374             if (appData.debugMode)
12375               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12376         }
12377         break;
12378
12379       case EndOfFile:
12380         if (appData.debugMode)
12381           fprintf(debugFP, "Parser hit end of file\n");
12382         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12383           case MT_NONE:
12384           case MT_CHECK:
12385             break;
12386           case MT_CHECKMATE:
12387           case MT_STAINMATE:
12388             if (WhiteOnMove(currentMove)) {
12389                 GameEnds(BlackWins, "Black mates", GE_FILE);
12390             } else {
12391                 GameEnds(WhiteWins, "White mates", GE_FILE);
12392             }
12393             break;
12394           case MT_STALEMATE:
12395             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12396             break;
12397         }
12398         done = TRUE;
12399         break;
12400
12401       case MoveNumberOne:
12402         if (lastLoadGameStart == GNUChessGame) {
12403             /* GNUChessGames have numbers, but they aren't move numbers */
12404             if (appData.debugMode)
12405               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12406                       yy_text, (int) moveType);
12407             return LoadGameOneMove(EndOfFile); /* tail recursion */
12408         }
12409         /* else fall thru */
12410
12411       case XBoardGame:
12412       case GNUChessGame:
12413       case PGNTag:
12414         /* Reached start of next game in file */
12415         if (appData.debugMode)
12416           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12417         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12418           case MT_NONE:
12419           case MT_CHECK:
12420             break;
12421           case MT_CHECKMATE:
12422           case MT_STAINMATE:
12423             if (WhiteOnMove(currentMove)) {
12424                 GameEnds(BlackWins, "Black mates", GE_FILE);
12425             } else {
12426                 GameEnds(WhiteWins, "White mates", GE_FILE);
12427             }
12428             break;
12429           case MT_STALEMATE:
12430             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12431             break;
12432         }
12433         done = TRUE;
12434         break;
12435
12436       case PositionDiagram:     /* should not happen; ignore */
12437       case ElapsedTime:         /* ignore */
12438       case NAG:                 /* ignore */
12439         if (appData.debugMode)
12440           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12441                   yy_text, (int) moveType);
12442         return LoadGameOneMove(EndOfFile); /* tail recursion */
12443
12444       case IllegalMove:
12445         if (appData.testLegality) {
12446             if (appData.debugMode)
12447               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12448             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12449                     (forwardMostMove / 2) + 1,
12450                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12451             DisplayError(move, 0);
12452             done = TRUE;
12453         } else {
12454             if (appData.debugMode)
12455               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12456                       yy_text, currentMoveString);
12457             if(currentMoveString[1] == '@') {
12458                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12459                 fromY = DROP_RANK;
12460             } else {
12461                 fromX = currentMoveString[0] - AAA;
12462                 fromY = currentMoveString[1] - ONE;
12463             }
12464             toX = currentMoveString[2] - AAA;
12465             toY = currentMoveString[3] - ONE;
12466             promoChar = currentMoveString[4];
12467         }
12468         break;
12469
12470       case AmbiguousMove:
12471         if (appData.debugMode)
12472           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12473         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12474                 (forwardMostMove / 2) + 1,
12475                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12476         DisplayError(move, 0);
12477         done = TRUE;
12478         break;
12479
12480       default:
12481       case ImpossibleMove:
12482         if (appData.debugMode)
12483           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12484         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12485                 (forwardMostMove / 2) + 1,
12486                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12487         DisplayError(move, 0);
12488         done = TRUE;
12489         break;
12490     }
12491
12492     if (done) {
12493         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12494             DrawPosition(FALSE, boards[currentMove]);
12495             DisplayBothClocks();
12496             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12497               DisplayComment(currentMove - 1, commentList[currentMove]);
12498         }
12499         (void) StopLoadGameTimer();
12500         gameFileFP = NULL;
12501         cmailOldMove = forwardMostMove;
12502         return FALSE;
12503     } else {
12504         /* currentMoveString is set as a side-effect of yylex */
12505
12506         thinkOutput[0] = NULLCHAR;
12507         MakeMove(fromX, fromY, toX, toY, promoChar);
12508         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12509         currentMove = forwardMostMove;
12510         return TRUE;
12511     }
12512 }
12513
12514 /* Load the nth game from the given file */
12515 int
12516 LoadGameFromFile (char *filename, int n, char *title, int useList)
12517 {
12518     FILE *f;
12519     char buf[MSG_SIZ];
12520
12521     if (strcmp(filename, "-") == 0) {
12522         f = stdin;
12523         title = "stdin";
12524     } else {
12525         f = fopen(filename, "rb");
12526         if (f == NULL) {
12527           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12528             DisplayError(buf, errno);
12529             return FALSE;
12530         }
12531     }
12532     if (fseek(f, 0, 0) == -1) {
12533         /* f is not seekable; probably a pipe */
12534         useList = FALSE;
12535     }
12536     if (useList && n == 0) {
12537         int error = GameListBuild(f);
12538         if (error) {
12539             DisplayError(_("Cannot build game list"), error);
12540         } else if (!ListEmpty(&gameList) &&
12541                    ((ListGame *) gameList.tailPred)->number > 1) {
12542             GameListPopUp(f, title);
12543             return TRUE;
12544         }
12545         GameListDestroy();
12546         n = 1;
12547     }
12548     if (n == 0) n = 1;
12549     return LoadGame(f, n, title, FALSE);
12550 }
12551
12552
12553 void
12554 MakeRegisteredMove ()
12555 {
12556     int fromX, fromY, toX, toY;
12557     char promoChar;
12558     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12559         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12560           case CMAIL_MOVE:
12561           case CMAIL_DRAW:
12562             if (appData.debugMode)
12563               fprintf(debugFP, "Restoring %s for game %d\n",
12564                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12565
12566             thinkOutput[0] = NULLCHAR;
12567             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12568             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12569             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12570             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12571             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12572             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12573             MakeMove(fromX, fromY, toX, toY, promoChar);
12574             ShowMove(fromX, fromY, toX, toY);
12575
12576             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12577               case MT_NONE:
12578               case MT_CHECK:
12579                 break;
12580
12581               case MT_CHECKMATE:
12582               case MT_STAINMATE:
12583                 if (WhiteOnMove(currentMove)) {
12584                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12585                 } else {
12586                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12587                 }
12588                 break;
12589
12590               case MT_STALEMATE:
12591                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12592                 break;
12593             }
12594
12595             break;
12596
12597           case CMAIL_RESIGN:
12598             if (WhiteOnMove(currentMove)) {
12599                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12600             } else {
12601                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12602             }
12603             break;
12604
12605           case CMAIL_ACCEPT:
12606             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12607             break;
12608
12609           default:
12610             break;
12611         }
12612     }
12613
12614     return;
12615 }
12616
12617 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12618 int
12619 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12620 {
12621     int retVal;
12622
12623     if (gameNumber > nCmailGames) {
12624         DisplayError(_("No more games in this message"), 0);
12625         return FALSE;
12626     }
12627     if (f == lastLoadGameFP) {
12628         int offset = gameNumber - lastLoadGameNumber;
12629         if (offset == 0) {
12630             cmailMsg[0] = NULLCHAR;
12631             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12632                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12633                 nCmailMovesRegistered--;
12634             }
12635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12636             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12637                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12638             }
12639         } else {
12640             if (! RegisterMove()) return FALSE;
12641         }
12642     }
12643
12644     retVal = LoadGame(f, gameNumber, title, useList);
12645
12646     /* Make move registered during previous look at this game, if any */
12647     MakeRegisteredMove();
12648
12649     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12650         commentList[currentMove]
12651           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12652         DisplayComment(currentMove - 1, commentList[currentMove]);
12653     }
12654
12655     return retVal;
12656 }
12657
12658 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12659 int
12660 ReloadGame (int offset)
12661 {
12662     int gameNumber = lastLoadGameNumber + offset;
12663     if (lastLoadGameFP == NULL) {
12664         DisplayError(_("No game has been loaded yet"), 0);
12665         return FALSE;
12666     }
12667     if (gameNumber <= 0) {
12668         DisplayError(_("Can't back up any further"), 0);
12669         return FALSE;
12670     }
12671     if (cmailMsgLoaded) {
12672         return CmailLoadGame(lastLoadGameFP, gameNumber,
12673                              lastLoadGameTitle, lastLoadGameUseList);
12674     } else {
12675         return LoadGame(lastLoadGameFP, gameNumber,
12676                         lastLoadGameTitle, lastLoadGameUseList);
12677     }
12678 }
12679
12680 int keys[EmptySquare+1];
12681
12682 int
12683 PositionMatches (Board b1, Board b2)
12684 {
12685     int r, f, sum=0;
12686     switch(appData.searchMode) {
12687         case 1: return CompareWithRights(b1, b2);
12688         case 2:
12689             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12690                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12691             }
12692             return TRUE;
12693         case 3:
12694             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12695               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12696                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12697             }
12698             return sum==0;
12699         case 4:
12700             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12701                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12702             }
12703             return sum==0;
12704     }
12705     return TRUE;
12706 }
12707
12708 #define Q_PROMO  4
12709 #define Q_EP     3
12710 #define Q_BCASTL 2
12711 #define Q_WCASTL 1
12712
12713 int pieceList[256], quickBoard[256];
12714 ChessSquare pieceType[256] = { EmptySquare };
12715 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12716 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12717 int soughtTotal, turn;
12718 Boolean epOK, flipSearch;
12719
12720 typedef struct {
12721     unsigned char piece, to;
12722 } Move;
12723
12724 #define DSIZE (250000)
12725
12726 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12727 Move *moveDatabase = initialSpace;
12728 unsigned int movePtr, dataSize = DSIZE;
12729
12730 int
12731 MakePieceList (Board board, int *counts)
12732 {
12733     int r, f, n=Q_PROMO, total=0;
12734     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12735     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12736         int sq = f + (r<<4);
12737         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12738             quickBoard[sq] = ++n;
12739             pieceList[n] = sq;
12740             pieceType[n] = board[r][f];
12741             counts[board[r][f]]++;
12742             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12743             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12744             total++;
12745         }
12746     }
12747     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12748     return total;
12749 }
12750
12751 void
12752 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12753 {
12754     int sq = fromX + (fromY<<4);
12755     int piece = quickBoard[sq], rook;
12756     quickBoard[sq] = 0;
12757     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12758     if(piece == pieceList[1] && fromY == toY) {
12759       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12760         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12761         moveDatabase[movePtr++].piece = Q_WCASTL;
12762         quickBoard[sq] = piece;
12763         piece = quickBoard[from]; quickBoard[from] = 0;
12764         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12765       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12766         quickBoard[sq] = 0; // remove Rook
12767         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12768         moveDatabase[movePtr++].piece = Q_WCASTL;
12769         quickBoard[sq] = pieceList[1]; // put King
12770         piece = rook;
12771         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12772       }
12773     } else
12774     if(piece == pieceList[2] && fromY == toY) {
12775       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12776         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12777         moveDatabase[movePtr++].piece = Q_BCASTL;
12778         quickBoard[sq] = piece;
12779         piece = quickBoard[from]; quickBoard[from] = 0;
12780         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12781       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12782         quickBoard[sq] = 0; // remove Rook
12783         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12784         moveDatabase[movePtr++].piece = Q_BCASTL;
12785         quickBoard[sq] = pieceList[2]; // put King
12786         piece = rook;
12787         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12788       }
12789     } else
12790     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12791         quickBoard[(fromY<<4)+toX] = 0;
12792         moveDatabase[movePtr].piece = Q_EP;
12793         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12794         moveDatabase[movePtr].to = sq;
12795     } else
12796     if(promoPiece != pieceType[piece]) {
12797         moveDatabase[movePtr++].piece = Q_PROMO;
12798         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12799     }
12800     moveDatabase[movePtr].piece = piece;
12801     quickBoard[sq] = piece;
12802     movePtr++;
12803 }
12804
12805 int
12806 PackGame (Board board)
12807 {
12808     Move *newSpace = NULL;
12809     moveDatabase[movePtr].piece = 0; // terminate previous game
12810     if(movePtr > dataSize) {
12811         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12812         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12813         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12814         if(newSpace) {
12815             int i;
12816             Move *p = moveDatabase, *q = newSpace;
12817             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12818             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12819             moveDatabase = newSpace;
12820         } else { // calloc failed, we must be out of memory. Too bad...
12821             dataSize = 0; // prevent calloc events for all subsequent games
12822             return 0;     // and signal this one isn't cached
12823         }
12824     }
12825     movePtr++;
12826     MakePieceList(board, counts);
12827     return movePtr;
12828 }
12829
12830 int
12831 QuickCompare (Board board, int *minCounts, int *maxCounts)
12832 {   // compare according to search mode
12833     int r, f;
12834     switch(appData.searchMode)
12835     {
12836       case 1: // exact position match
12837         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12838         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12839             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12840         }
12841         break;
12842       case 2: // can have extra material on empty squares
12843         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12844             if(board[r][f] == EmptySquare) continue;
12845             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12846         }
12847         break;
12848       case 3: // material with exact Pawn structure
12849         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12850             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12851             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12852         } // fall through to material comparison
12853       case 4: // exact material
12854         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12855         break;
12856       case 6: // material range with given imbalance
12857         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12858         // fall through to range comparison
12859       case 5: // material range
12860         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12861     }
12862     return TRUE;
12863 }
12864
12865 int
12866 QuickScan (Board board, Move *move)
12867 {   // reconstruct game,and compare all positions in it
12868     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12869     do {
12870         int piece = move->piece;
12871         int to = move->to, from = pieceList[piece];
12872         if(found < 0) { // if already found just scan to game end for final piece count
12873           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12874            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12875            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12876                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12877             ) {
12878             static int lastCounts[EmptySquare+1];
12879             int i;
12880             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12881             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12882           } else stretch = 0;
12883           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12884           if(found >= 0 && !appData.minPieces) return found;
12885         }
12886         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12887           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12888           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12889             piece = (++move)->piece;
12890             from = pieceList[piece];
12891             counts[pieceType[piece]]--;
12892             pieceType[piece] = (ChessSquare) move->to;
12893             counts[move->to]++;
12894           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12895             counts[pieceType[quickBoard[to]]]--;
12896             quickBoard[to] = 0; total--;
12897             move++;
12898             continue;
12899           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12900             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12901             from  = pieceList[piece]; // so this must be King
12902             quickBoard[from] = 0;
12903             pieceList[piece] = to;
12904             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12905             quickBoard[from] = 0; // rook
12906             quickBoard[to] = piece;
12907             to = move->to; piece = move->piece;
12908             goto aftercastle;
12909           }
12910         }
12911         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12912         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12913         quickBoard[from] = 0;
12914       aftercastle:
12915         quickBoard[to] = piece;
12916         pieceList[piece] = to;
12917         cnt++; turn ^= 3;
12918         move++;
12919     } while(1);
12920 }
12921
12922 void
12923 InitSearch ()
12924 {
12925     int r, f;
12926     flipSearch = FALSE;
12927     CopyBoard(soughtBoard, boards[currentMove]);
12928     soughtTotal = MakePieceList(soughtBoard, maxSought);
12929     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12930     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12931     CopyBoard(reverseBoard, boards[currentMove]);
12932     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12933         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12934         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12935         reverseBoard[r][f] = piece;
12936     }
12937     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12938     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12939     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12940                  || (boards[currentMove][CASTLING][2] == NoRights ||
12941                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12942                  && (boards[currentMove][CASTLING][5] == NoRights ||
12943                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12944       ) {
12945         flipSearch = TRUE;
12946         CopyBoard(flipBoard, soughtBoard);
12947         CopyBoard(rotateBoard, reverseBoard);
12948         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12949             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12950             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12951         }
12952     }
12953     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12954     if(appData.searchMode >= 5) {
12955         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12956         MakePieceList(soughtBoard, minSought);
12957         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12958     }
12959     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12960         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12961 }
12962
12963 GameInfo dummyInfo;
12964 static int creatingBook;
12965
12966 int
12967 GameContainsPosition (FILE *f, ListGame *lg)
12968 {
12969     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12970     int fromX, fromY, toX, toY;
12971     char promoChar;
12972     static int initDone=FALSE;
12973
12974     // weed out games based on numerical tag comparison
12975     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12976     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12977     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12978     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12979     if(!initDone) {
12980         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12981         initDone = TRUE;
12982     }
12983     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12984     else CopyBoard(boards[scratch], initialPosition); // default start position
12985     if(lg->moves) {
12986         turn = btm + 1;
12987         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12988         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12989     }
12990     if(btm) plyNr++;
12991     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12992     fseek(f, lg->offset, 0);
12993     yynewfile(f);
12994     while(1) {
12995         yyboardindex = scratch;
12996         quickFlag = plyNr+1;
12997         next = Myylex();
12998         quickFlag = 0;
12999         switch(next) {
13000             case PGNTag:
13001                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13002             default:
13003                 continue;
13004
13005             case XBoardGame:
13006             case GNUChessGame:
13007                 if(plyNr) return -1; // after we have seen moves, this is for new game
13008               continue;
13009
13010             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13011             case ImpossibleMove:
13012             case WhiteWins: // game ends here with these four
13013             case BlackWins:
13014             case GameIsDrawn:
13015             case GameUnfinished:
13016                 return -1;
13017
13018             case IllegalMove:
13019                 if(appData.testLegality) return -1;
13020             case WhiteCapturesEnPassant:
13021             case BlackCapturesEnPassant:
13022             case WhitePromotion:
13023             case BlackPromotion:
13024             case WhiteNonPromotion:
13025             case BlackNonPromotion:
13026             case NormalMove:
13027             case FirstLeg:
13028             case WhiteKingSideCastle:
13029             case WhiteQueenSideCastle:
13030             case BlackKingSideCastle:
13031             case BlackQueenSideCastle:
13032             case WhiteKingSideCastleWild:
13033             case WhiteQueenSideCastleWild:
13034             case BlackKingSideCastleWild:
13035             case BlackQueenSideCastleWild:
13036             case WhiteHSideCastleFR:
13037             case WhiteASideCastleFR:
13038             case BlackHSideCastleFR:
13039             case BlackASideCastleFR:
13040                 fromX = currentMoveString[0] - AAA;
13041                 fromY = currentMoveString[1] - ONE;
13042                 toX = currentMoveString[2] - AAA;
13043                 toY = currentMoveString[3] - ONE;
13044                 promoChar = currentMoveString[4];
13045                 break;
13046             case WhiteDrop:
13047             case BlackDrop:
13048                 fromX = next == WhiteDrop ?
13049                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13050                   (int) CharToPiece(ToLower(currentMoveString[0]));
13051                 fromY = DROP_RANK;
13052                 toX = currentMoveString[2] - AAA;
13053                 toY = currentMoveString[3] - ONE;
13054                 promoChar = 0;
13055                 break;
13056         }
13057         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13058         plyNr++;
13059         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13060         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13061         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13062         if(appData.findMirror) {
13063             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13064             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13065         }
13066     }
13067 }
13068
13069 /* Load the nth game from open file f */
13070 int
13071 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13072 {
13073     ChessMove cm;
13074     char buf[MSG_SIZ];
13075     int gn = gameNumber;
13076     ListGame *lg = NULL;
13077     int numPGNTags = 0, i;
13078     int err, pos = -1;
13079     GameMode oldGameMode;
13080     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13081     char oldName[MSG_SIZ];
13082
13083     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13084
13085     if (appData.debugMode)
13086         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13087
13088     if (gameMode == Training )
13089         SetTrainingModeOff();
13090
13091     oldGameMode = gameMode;
13092     if (gameMode != BeginningOfGame) {
13093       Reset(FALSE, TRUE);
13094     }
13095     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13096
13097     gameFileFP = f;
13098     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13099         fclose(lastLoadGameFP);
13100     }
13101
13102     if (useList) {
13103         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13104
13105         if (lg) {
13106             fseek(f, lg->offset, 0);
13107             GameListHighlight(gameNumber);
13108             pos = lg->position;
13109             gn = 1;
13110         }
13111         else {
13112             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13113               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13114             else
13115             DisplayError(_("Game number out of range"), 0);
13116             return FALSE;
13117         }
13118     } else {
13119         GameListDestroy();
13120         if (fseek(f, 0, 0) == -1) {
13121             if (f == lastLoadGameFP ?
13122                 gameNumber == lastLoadGameNumber + 1 :
13123                 gameNumber == 1) {
13124                 gn = 1;
13125             } else {
13126                 DisplayError(_("Can't seek on game file"), 0);
13127                 return FALSE;
13128             }
13129         }
13130     }
13131     lastLoadGameFP = f;
13132     lastLoadGameNumber = gameNumber;
13133     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13134     lastLoadGameUseList = useList;
13135
13136     yynewfile(f);
13137
13138     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13139       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13140                 lg->gameInfo.black);
13141             DisplayTitle(buf);
13142     } else if (*title != NULLCHAR) {
13143         if (gameNumber > 1) {
13144           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13145             DisplayTitle(buf);
13146         } else {
13147             DisplayTitle(title);
13148         }
13149     }
13150
13151     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13152         gameMode = PlayFromGameFile;
13153         ModeHighlight();
13154     }
13155
13156     currentMove = forwardMostMove = backwardMostMove = 0;
13157     CopyBoard(boards[0], initialPosition);
13158     StopClocks();
13159
13160     /*
13161      * Skip the first gn-1 games in the file.
13162      * Also skip over anything that precedes an identifiable
13163      * start of game marker, to avoid being confused by
13164      * garbage at the start of the file.  Currently
13165      * recognized start of game markers are the move number "1",
13166      * the pattern "gnuchess .* game", the pattern
13167      * "^[#;%] [^ ]* game file", and a PGN tag block.
13168      * A game that starts with one of the latter two patterns
13169      * will also have a move number 1, possibly
13170      * following a position diagram.
13171      * 5-4-02: Let's try being more lenient and allowing a game to
13172      * start with an unnumbered move.  Does that break anything?
13173      */
13174     cm = lastLoadGameStart = EndOfFile;
13175     while (gn > 0) {
13176         yyboardindex = forwardMostMove;
13177         cm = (ChessMove) Myylex();
13178         switch (cm) {
13179           case EndOfFile:
13180             if (cmailMsgLoaded) {
13181                 nCmailGames = CMAIL_MAX_GAMES - gn;
13182             } else {
13183                 Reset(TRUE, TRUE);
13184                 DisplayError(_("Game not found in file"), 0);
13185             }
13186             return FALSE;
13187
13188           case GNUChessGame:
13189           case XBoardGame:
13190             gn--;
13191             lastLoadGameStart = cm;
13192             break;
13193
13194           case MoveNumberOne:
13195             switch (lastLoadGameStart) {
13196               case GNUChessGame:
13197               case XBoardGame:
13198               case PGNTag:
13199                 break;
13200               case MoveNumberOne:
13201               case EndOfFile:
13202                 gn--;           /* count this game */
13203                 lastLoadGameStart = cm;
13204                 break;
13205               default:
13206                 /* impossible */
13207                 break;
13208             }
13209             break;
13210
13211           case PGNTag:
13212             switch (lastLoadGameStart) {
13213               case GNUChessGame:
13214               case PGNTag:
13215               case MoveNumberOne:
13216               case EndOfFile:
13217                 gn--;           /* count this game */
13218                 lastLoadGameStart = cm;
13219                 break;
13220               case XBoardGame:
13221                 lastLoadGameStart = cm; /* game counted already */
13222                 break;
13223               default:
13224                 /* impossible */
13225                 break;
13226             }
13227             if (gn > 0) {
13228                 do {
13229                     yyboardindex = forwardMostMove;
13230                     cm = (ChessMove) Myylex();
13231                 } while (cm == PGNTag || cm == Comment);
13232             }
13233             break;
13234
13235           case WhiteWins:
13236           case BlackWins:
13237           case GameIsDrawn:
13238             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13239                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13240                     != CMAIL_OLD_RESULT) {
13241                     nCmailResults ++ ;
13242                     cmailResult[  CMAIL_MAX_GAMES
13243                                 - gn - 1] = CMAIL_OLD_RESULT;
13244                 }
13245             }
13246             break;
13247
13248           case NormalMove:
13249           case FirstLeg:
13250             /* Only a NormalMove can be at the start of a game
13251              * without a position diagram. */
13252             if (lastLoadGameStart == EndOfFile ) {
13253               gn--;
13254               lastLoadGameStart = MoveNumberOne;
13255             }
13256             break;
13257
13258           default:
13259             break;
13260         }
13261     }
13262
13263     if (appData.debugMode)
13264       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13265
13266     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13267
13268     if (cm == XBoardGame) {
13269         /* Skip any header junk before position diagram and/or move 1 */
13270         for (;;) {
13271             yyboardindex = forwardMostMove;
13272             cm = (ChessMove) Myylex();
13273
13274             if (cm == EndOfFile ||
13275                 cm == GNUChessGame || cm == XBoardGame) {
13276                 /* Empty game; pretend end-of-file and handle later */
13277                 cm = EndOfFile;
13278                 break;
13279             }
13280
13281             if (cm == MoveNumberOne || cm == PositionDiagram ||
13282                 cm == PGNTag || cm == Comment)
13283               break;
13284         }
13285     } else if (cm == GNUChessGame) {
13286         if (gameInfo.event != NULL) {
13287             free(gameInfo.event);
13288         }
13289         gameInfo.event = StrSave(yy_text);
13290     }
13291
13292     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13293     while (cm == PGNTag) {
13294         if (appData.debugMode)
13295           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13296         err = ParsePGNTag(yy_text, &gameInfo);
13297         if (!err) numPGNTags++;
13298
13299         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13300         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13301             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13302             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13303             InitPosition(TRUE);
13304             oldVariant = gameInfo.variant;
13305             if (appData.debugMode)
13306               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13307         }
13308
13309
13310         if (gameInfo.fen != NULL) {
13311           Board initial_position;
13312           startedFromSetupPosition = TRUE;
13313           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13314             Reset(TRUE, TRUE);
13315             DisplayError(_("Bad FEN position in file"), 0);
13316             return FALSE;
13317           }
13318           CopyBoard(boards[0], initial_position);
13319           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13320             CopyBoard(initialPosition, initial_position);
13321           if (blackPlaysFirst) {
13322             currentMove = forwardMostMove = backwardMostMove = 1;
13323             CopyBoard(boards[1], initial_position);
13324             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13325             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13326             timeRemaining[0][1] = whiteTimeRemaining;
13327             timeRemaining[1][1] = blackTimeRemaining;
13328             if (commentList[0] != NULL) {
13329               commentList[1] = commentList[0];
13330               commentList[0] = NULL;
13331             }
13332           } else {
13333             currentMove = forwardMostMove = backwardMostMove = 0;
13334           }
13335           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13336           {   int i;
13337               initialRulePlies = FENrulePlies;
13338               for( i=0; i< nrCastlingRights; i++ )
13339                   initialRights[i] = initial_position[CASTLING][i];
13340           }
13341           yyboardindex = forwardMostMove;
13342           free(gameInfo.fen);
13343           gameInfo.fen = NULL;
13344         }
13345
13346         yyboardindex = forwardMostMove;
13347         cm = (ChessMove) Myylex();
13348
13349         /* Handle comments interspersed among the tags */
13350         while (cm == Comment) {
13351             char *p;
13352             if (appData.debugMode)
13353               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13354             p = yy_text;
13355             AppendComment(currentMove, p, FALSE);
13356             yyboardindex = forwardMostMove;
13357             cm = (ChessMove) Myylex();
13358         }
13359     }
13360
13361     /* don't rely on existence of Event tag since if game was
13362      * pasted from clipboard the Event tag may not exist
13363      */
13364     if (numPGNTags > 0){
13365         char *tags;
13366         if (gameInfo.variant == VariantNormal) {
13367           VariantClass v = StringToVariant(gameInfo.event);
13368           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13369           if(v < VariantShogi) gameInfo.variant = v;
13370         }
13371         if (!matchMode) {
13372           if( appData.autoDisplayTags ) {
13373             tags = PGNTags(&gameInfo);
13374             TagsPopUp(tags, CmailMsg());
13375             free(tags);
13376           }
13377         }
13378     } else {
13379         /* Make something up, but don't display it now */
13380         SetGameInfo();
13381         TagsPopDown();
13382     }
13383
13384     if (cm == PositionDiagram) {
13385         int i, j;
13386         char *p;
13387         Board initial_position;
13388
13389         if (appData.debugMode)
13390           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13391
13392         if (!startedFromSetupPosition) {
13393             p = yy_text;
13394             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13395               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13396                 switch (*p) {
13397                   case '{':
13398                   case '[':
13399                   case '-':
13400                   case ' ':
13401                   case '\t':
13402                   case '\n':
13403                   case '\r':
13404                     break;
13405                   default:
13406                     initial_position[i][j++] = CharToPiece(*p);
13407                     break;
13408                 }
13409             while (*p == ' ' || *p == '\t' ||
13410                    *p == '\n' || *p == '\r') p++;
13411
13412             if (strncmp(p, "black", strlen("black"))==0)
13413               blackPlaysFirst = TRUE;
13414             else
13415               blackPlaysFirst = FALSE;
13416             startedFromSetupPosition = TRUE;
13417
13418             CopyBoard(boards[0], initial_position);
13419             if (blackPlaysFirst) {
13420                 currentMove = forwardMostMove = backwardMostMove = 1;
13421                 CopyBoard(boards[1], initial_position);
13422                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13423                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13424                 timeRemaining[0][1] = whiteTimeRemaining;
13425                 timeRemaining[1][1] = blackTimeRemaining;
13426                 if (commentList[0] != NULL) {
13427                     commentList[1] = commentList[0];
13428                     commentList[0] = NULL;
13429                 }
13430             } else {
13431                 currentMove = forwardMostMove = backwardMostMove = 0;
13432             }
13433         }
13434         yyboardindex = forwardMostMove;
13435         cm = (ChessMove) Myylex();
13436     }
13437
13438   if(!creatingBook) {
13439     if (first.pr == NoProc) {
13440         StartChessProgram(&first);
13441     }
13442     InitChessProgram(&first, FALSE);
13443     if(gameInfo.variant == VariantUnknown && *oldName) {
13444         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13445         gameInfo.variant = v;
13446     }
13447     SendToProgram("force\n", &first);
13448     if (startedFromSetupPosition) {
13449         SendBoard(&first, forwardMostMove);
13450     if (appData.debugMode) {
13451         fprintf(debugFP, "Load Game\n");
13452     }
13453         DisplayBothClocks();
13454     }
13455   }
13456
13457     /* [HGM] server: flag to write setup moves in broadcast file as one */
13458     loadFlag = appData.suppressLoadMoves;
13459
13460     while (cm == Comment) {
13461         char *p;
13462         if (appData.debugMode)
13463           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13464         p = yy_text;
13465         AppendComment(currentMove, p, FALSE);
13466         yyboardindex = forwardMostMove;
13467         cm = (ChessMove) Myylex();
13468     }
13469
13470     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13471         cm == WhiteWins || cm == BlackWins ||
13472         cm == GameIsDrawn || cm == GameUnfinished) {
13473         DisplayMessage("", _("No moves in game"));
13474         if (cmailMsgLoaded) {
13475             if (appData.debugMode)
13476               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13477             ClearHighlights();
13478             flipView = FALSE;
13479         }
13480         DrawPosition(FALSE, boards[currentMove]);
13481         DisplayBothClocks();
13482         gameMode = EditGame;
13483         ModeHighlight();
13484         gameFileFP = NULL;
13485         cmailOldMove = 0;
13486         return TRUE;
13487     }
13488
13489     // [HGM] PV info: routine tests if comment empty
13490     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13491         DisplayComment(currentMove - 1, commentList[currentMove]);
13492     }
13493     if (!matchMode && appData.timeDelay != 0)
13494       DrawPosition(FALSE, boards[currentMove]);
13495
13496     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13497       programStats.ok_to_send = 1;
13498     }
13499
13500     /* if the first token after the PGN tags is a move
13501      * and not move number 1, retrieve it from the parser
13502      */
13503     if (cm != MoveNumberOne)
13504         LoadGameOneMove(cm);
13505
13506     /* load the remaining moves from the file */
13507     while (LoadGameOneMove(EndOfFile)) {
13508       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13509       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13510     }
13511
13512     /* rewind to the start of the game */
13513     currentMove = backwardMostMove;
13514
13515     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13516
13517     if (oldGameMode == AnalyzeFile) {
13518       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13519       AnalyzeFileEvent();
13520     } else
13521     if (oldGameMode == AnalyzeMode) {
13522       AnalyzeFileEvent();
13523     }
13524
13525     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13526         long int w, b; // [HGM] adjourn: restore saved clock times
13527         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13528         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13529             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13530             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13531         }
13532     }
13533
13534     if(creatingBook) return TRUE;
13535     if (!matchMode && pos > 0) {
13536         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13537     } else
13538     if (matchMode || appData.timeDelay == 0) {
13539       ToEndEvent();
13540     } else if (appData.timeDelay > 0) {
13541       AutoPlayGameLoop();
13542     }
13543
13544     if (appData.debugMode)
13545         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13546
13547     loadFlag = 0; /* [HGM] true game starts */
13548     return TRUE;
13549 }
13550
13551 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13552 int
13553 ReloadPosition (int offset)
13554 {
13555     int positionNumber = lastLoadPositionNumber + offset;
13556     if (lastLoadPositionFP == NULL) {
13557         DisplayError(_("No position has been loaded yet"), 0);
13558         return FALSE;
13559     }
13560     if (positionNumber <= 0) {
13561         DisplayError(_("Can't back up any further"), 0);
13562         return FALSE;
13563     }
13564     return LoadPosition(lastLoadPositionFP, positionNumber,
13565                         lastLoadPositionTitle);
13566 }
13567
13568 /* Load the nth position from the given file */
13569 int
13570 LoadPositionFromFile (char *filename, int n, char *title)
13571 {
13572     FILE *f;
13573     char buf[MSG_SIZ];
13574
13575     if (strcmp(filename, "-") == 0) {
13576         return LoadPosition(stdin, n, "stdin");
13577     } else {
13578         f = fopen(filename, "rb");
13579         if (f == NULL) {
13580             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13581             DisplayError(buf, errno);
13582             return FALSE;
13583         } else {
13584             return LoadPosition(f, n, title);
13585         }
13586     }
13587 }
13588
13589 /* Load the nth position from the given open file, and close it */
13590 int
13591 LoadPosition (FILE *f, int positionNumber, char *title)
13592 {
13593     char *p, line[MSG_SIZ];
13594     Board initial_position;
13595     int i, j, fenMode, pn;
13596
13597     if (gameMode == Training )
13598         SetTrainingModeOff();
13599
13600     if (gameMode != BeginningOfGame) {
13601         Reset(FALSE, TRUE);
13602     }
13603     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13604         fclose(lastLoadPositionFP);
13605     }
13606     if (positionNumber == 0) positionNumber = 1;
13607     lastLoadPositionFP = f;
13608     lastLoadPositionNumber = positionNumber;
13609     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13610     if (first.pr == NoProc && !appData.noChessProgram) {
13611       StartChessProgram(&first);
13612       InitChessProgram(&first, FALSE);
13613     }
13614     pn = positionNumber;
13615     if (positionNumber < 0) {
13616         /* Negative position number means to seek to that byte offset */
13617         if (fseek(f, -positionNumber, 0) == -1) {
13618             DisplayError(_("Can't seek on position file"), 0);
13619             return FALSE;
13620         };
13621         pn = 1;
13622     } else {
13623         if (fseek(f, 0, 0) == -1) {
13624             if (f == lastLoadPositionFP ?
13625                 positionNumber == lastLoadPositionNumber + 1 :
13626                 positionNumber == 1) {
13627                 pn = 1;
13628             } else {
13629                 DisplayError(_("Can't seek on position file"), 0);
13630                 return FALSE;
13631             }
13632         }
13633     }
13634     /* See if this file is FEN or old-style xboard */
13635     if (fgets(line, MSG_SIZ, f) == NULL) {
13636         DisplayError(_("Position not found in file"), 0);
13637         return FALSE;
13638     }
13639     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13640     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13641
13642     if (pn >= 2) {
13643         if (fenMode || line[0] == '#') pn--;
13644         while (pn > 0) {
13645             /* skip positions before number pn */
13646             if (fgets(line, MSG_SIZ, f) == NULL) {
13647                 Reset(TRUE, TRUE);
13648                 DisplayError(_("Position not found in file"), 0);
13649                 return FALSE;
13650             }
13651             if (fenMode || line[0] == '#') pn--;
13652         }
13653     }
13654
13655     if (fenMode) {
13656         char *p;
13657         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13658             DisplayError(_("Bad FEN position in file"), 0);
13659             return FALSE;
13660         }
13661         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13662             sscanf(p+4, "%[^;]", bestMove);
13663         } else *bestMove = NULLCHAR;
13664         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13665             sscanf(p+4, "%[^;]", avoidMove);
13666         } else *avoidMove = NULLCHAR;
13667     } else {
13668         (void) fgets(line, MSG_SIZ, f);
13669         (void) fgets(line, MSG_SIZ, f);
13670
13671         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13672             (void) fgets(line, MSG_SIZ, f);
13673             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13674                 if (*p == ' ')
13675                   continue;
13676                 initial_position[i][j++] = CharToPiece(*p);
13677             }
13678         }
13679
13680         blackPlaysFirst = FALSE;
13681         if (!feof(f)) {
13682             (void) fgets(line, MSG_SIZ, f);
13683             if (strncmp(line, "black", strlen("black"))==0)
13684               blackPlaysFirst = TRUE;
13685         }
13686     }
13687     startedFromSetupPosition = TRUE;
13688
13689     CopyBoard(boards[0], initial_position);
13690     if (blackPlaysFirst) {
13691         currentMove = forwardMostMove = backwardMostMove = 1;
13692         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13693         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13694         CopyBoard(boards[1], initial_position);
13695         DisplayMessage("", _("Black to play"));
13696     } else {
13697         currentMove = forwardMostMove = backwardMostMove = 0;
13698         DisplayMessage("", _("White to play"));
13699     }
13700     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13701     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13702         SendToProgram("force\n", &first);
13703         SendBoard(&first, forwardMostMove);
13704     }
13705     if (appData.debugMode) {
13706 int i, j;
13707   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13708   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13709         fprintf(debugFP, "Load Position\n");
13710     }
13711
13712     if (positionNumber > 1) {
13713       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13714         DisplayTitle(line);
13715     } else {
13716         DisplayTitle(title);
13717     }
13718     gameMode = EditGame;
13719     ModeHighlight();
13720     ResetClocks();
13721     timeRemaining[0][1] = whiteTimeRemaining;
13722     timeRemaining[1][1] = blackTimeRemaining;
13723     DrawPosition(FALSE, boards[currentMove]);
13724
13725     return TRUE;
13726 }
13727
13728
13729 void
13730 CopyPlayerNameIntoFileName (char **dest, char *src)
13731 {
13732     while (*src != NULLCHAR && *src != ',') {
13733         if (*src == ' ') {
13734             *(*dest)++ = '_';
13735             src++;
13736         } else {
13737             *(*dest)++ = *src++;
13738         }
13739     }
13740 }
13741
13742 char *
13743 DefaultFileName (char *ext)
13744 {
13745     static char def[MSG_SIZ];
13746     char *p;
13747
13748     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13749         p = def;
13750         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13751         *p++ = '-';
13752         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13753         *p++ = '.';
13754         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13755     } else {
13756         def[0] = NULLCHAR;
13757     }
13758     return def;
13759 }
13760
13761 /* Save the current game to the given file */
13762 int
13763 SaveGameToFile (char *filename, int append)
13764 {
13765     FILE *f;
13766     char buf[MSG_SIZ];
13767     int result, i, t,tot=0;
13768
13769     if (strcmp(filename, "-") == 0) {
13770         return SaveGame(stdout, 0, NULL);
13771     } else {
13772         for(i=0; i<10; i++) { // upto 10 tries
13773              f = fopen(filename, append ? "a" : "w");
13774              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13775              if(f || errno != 13) break;
13776              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13777              tot += t;
13778         }
13779         if (f == NULL) {
13780             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13781             DisplayError(buf, errno);
13782             return FALSE;
13783         } else {
13784             safeStrCpy(buf, lastMsg, MSG_SIZ);
13785             DisplayMessage(_("Waiting for access to save file"), "");
13786             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13787             DisplayMessage(_("Saving game"), "");
13788             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13789             result = SaveGame(f, 0, NULL);
13790             DisplayMessage(buf, "");
13791             return result;
13792         }
13793     }
13794 }
13795
13796 char *
13797 SavePart (char *str)
13798 {
13799     static char buf[MSG_SIZ];
13800     char *p;
13801
13802     p = strchr(str, ' ');
13803     if (p == NULL) return str;
13804     strncpy(buf, str, p - str);
13805     buf[p - str] = NULLCHAR;
13806     return buf;
13807 }
13808
13809 #define PGN_MAX_LINE 75
13810
13811 #define PGN_SIDE_WHITE  0
13812 #define PGN_SIDE_BLACK  1
13813
13814 static int
13815 FindFirstMoveOutOfBook (int side)
13816 {
13817     int result = -1;
13818
13819     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13820         int index = backwardMostMove;
13821         int has_book_hit = 0;
13822
13823         if( (index % 2) != side ) {
13824             index++;
13825         }
13826
13827         while( index < forwardMostMove ) {
13828             /* Check to see if engine is in book */
13829             int depth = pvInfoList[index].depth;
13830             int score = pvInfoList[index].score;
13831             int in_book = 0;
13832
13833             if( depth <= 2 ) {
13834                 in_book = 1;
13835             }
13836             else if( score == 0 && depth == 63 ) {
13837                 in_book = 1; /* Zappa */
13838             }
13839             else if( score == 2 && depth == 99 ) {
13840                 in_book = 1; /* Abrok */
13841             }
13842
13843             has_book_hit += in_book;
13844
13845             if( ! in_book ) {
13846                 result = index;
13847
13848                 break;
13849             }
13850
13851             index += 2;
13852         }
13853     }
13854
13855     return result;
13856 }
13857
13858 void
13859 GetOutOfBookInfo (char * buf)
13860 {
13861     int oob[2];
13862     int i;
13863     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13864
13865     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13866     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13867
13868     *buf = '\0';
13869
13870     if( oob[0] >= 0 || oob[1] >= 0 ) {
13871         for( i=0; i<2; i++ ) {
13872             int idx = oob[i];
13873
13874             if( idx >= 0 ) {
13875                 if( i > 0 && oob[0] >= 0 ) {
13876                     strcat( buf, "   " );
13877                 }
13878
13879                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13880                 sprintf( buf+strlen(buf), "%s%.2f",
13881                     pvInfoList[idx].score >= 0 ? "+" : "",
13882                     pvInfoList[idx].score / 100.0 );
13883             }
13884         }
13885     }
13886 }
13887
13888 /* Save game in PGN style */
13889 static void
13890 SaveGamePGN2 (FILE *f)
13891 {
13892     int i, offset, linelen, newblock;
13893 //    char *movetext;
13894     char numtext[32];
13895     int movelen, numlen, blank;
13896     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13897
13898     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13899
13900     PrintPGNTags(f, &gameInfo);
13901
13902     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13903
13904     if (backwardMostMove > 0 || startedFromSetupPosition) {
13905         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13906         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13907         fprintf(f, "\n{--------------\n");
13908         PrintPosition(f, backwardMostMove);
13909         fprintf(f, "--------------}\n");
13910         free(fen);
13911     }
13912     else {
13913         /* [AS] Out of book annotation */
13914         if( appData.saveOutOfBookInfo ) {
13915             char buf[64];
13916
13917             GetOutOfBookInfo( buf );
13918
13919             if( buf[0] != '\0' ) {
13920                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13921             }
13922         }
13923
13924         fprintf(f, "\n");
13925     }
13926
13927     i = backwardMostMove;
13928     linelen = 0;
13929     newblock = TRUE;
13930
13931     while (i < forwardMostMove) {
13932         /* Print comments preceding this move */
13933         if (commentList[i] != NULL) {
13934             if (linelen > 0) fprintf(f, "\n");
13935             fprintf(f, "%s", commentList[i]);
13936             linelen = 0;
13937             newblock = TRUE;
13938         }
13939
13940         /* Format move number */
13941         if ((i % 2) == 0)
13942           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13943         else
13944           if (newblock)
13945             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13946           else
13947             numtext[0] = NULLCHAR;
13948
13949         numlen = strlen(numtext);
13950         newblock = FALSE;
13951
13952         /* Print move number */
13953         blank = linelen > 0 && numlen > 0;
13954         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13955             fprintf(f, "\n");
13956             linelen = 0;
13957             blank = 0;
13958         }
13959         if (blank) {
13960             fprintf(f, " ");
13961             linelen++;
13962         }
13963         fprintf(f, "%s", numtext);
13964         linelen += numlen;
13965
13966         /* Get move */
13967         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13968         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13969
13970         /* Print move */
13971         blank = linelen > 0 && movelen > 0;
13972         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13973             fprintf(f, "\n");
13974             linelen = 0;
13975             blank = 0;
13976         }
13977         if (blank) {
13978             fprintf(f, " ");
13979             linelen++;
13980         }
13981         fprintf(f, "%s", move_buffer);
13982         linelen += movelen;
13983
13984         /* [AS] Add PV info if present */
13985         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13986             /* [HGM] add time */
13987             char buf[MSG_SIZ]; int seconds;
13988
13989             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13990
13991             if( seconds <= 0)
13992               buf[0] = 0;
13993             else
13994               if( seconds < 30 )
13995                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13996               else
13997                 {
13998                   seconds = (seconds + 4)/10; // round to full seconds
13999                   if( seconds < 60 )
14000                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14001                   else
14002                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14003                 }
14004
14005             if(appData.cumulativeTimePGN) {
14006                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14007             }
14008
14009             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14010                       pvInfoList[i].score >= 0 ? "+" : "",
14011                       pvInfoList[i].score / 100.0,
14012                       pvInfoList[i].depth,
14013                       buf );
14014
14015             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14016
14017             /* Print score/depth */
14018             blank = linelen > 0 && movelen > 0;
14019             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14020                 fprintf(f, "\n");
14021                 linelen = 0;
14022                 blank = 0;
14023             }
14024             if (blank) {
14025                 fprintf(f, " ");
14026                 linelen++;
14027             }
14028             fprintf(f, "%s", move_buffer);
14029             linelen += movelen;
14030         }
14031
14032         i++;
14033     }
14034
14035     /* Start a new line */
14036     if (linelen > 0) fprintf(f, "\n");
14037
14038     /* Print comments after last move */
14039     if (commentList[i] != NULL) {
14040         fprintf(f, "%s\n", commentList[i]);
14041     }
14042
14043     /* Print result */
14044     if (gameInfo.resultDetails != NULL &&
14045         gameInfo.resultDetails[0] != NULLCHAR) {
14046         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14047         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14048            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14049             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14050         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14051     } else {
14052         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14053     }
14054 }
14055
14056 /* Save game in PGN style and close the file */
14057 int
14058 SaveGamePGN (FILE *f)
14059 {
14060     SaveGamePGN2(f);
14061     fclose(f);
14062     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14063     return TRUE;
14064 }
14065
14066 /* Save game in old style and close the file */
14067 int
14068 SaveGameOldStyle (FILE *f)
14069 {
14070     int i, offset;
14071     time_t tm;
14072
14073     tm = time((time_t *) NULL);
14074
14075     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14076     PrintOpponents(f);
14077
14078     if (backwardMostMove > 0 || startedFromSetupPosition) {
14079         fprintf(f, "\n[--------------\n");
14080         PrintPosition(f, backwardMostMove);
14081         fprintf(f, "--------------]\n");
14082     } else {
14083         fprintf(f, "\n");
14084     }
14085
14086     i = backwardMostMove;
14087     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14088
14089     while (i < forwardMostMove) {
14090         if (commentList[i] != NULL) {
14091             fprintf(f, "[%s]\n", commentList[i]);
14092         }
14093
14094         if ((i % 2) == 1) {
14095             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14096             i++;
14097         } else {
14098             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14099             i++;
14100             if (commentList[i] != NULL) {
14101                 fprintf(f, "\n");
14102                 continue;
14103             }
14104             if (i >= forwardMostMove) {
14105                 fprintf(f, "\n");
14106                 break;
14107             }
14108             fprintf(f, "%s\n", parseList[i]);
14109             i++;
14110         }
14111     }
14112
14113     if (commentList[i] != NULL) {
14114         fprintf(f, "[%s]\n", commentList[i]);
14115     }
14116
14117     /* This isn't really the old style, but it's close enough */
14118     if (gameInfo.resultDetails != NULL &&
14119         gameInfo.resultDetails[0] != NULLCHAR) {
14120         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14121                 gameInfo.resultDetails);
14122     } else {
14123         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14124     }
14125
14126     fclose(f);
14127     return TRUE;
14128 }
14129
14130 /* Save the current game to open file f and close the file */
14131 int
14132 SaveGame (FILE *f, int dummy, char *dummy2)
14133 {
14134     if (gameMode == EditPosition) EditPositionDone(TRUE);
14135     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14136     if (appData.oldSaveStyle)
14137       return SaveGameOldStyle(f);
14138     else
14139       return SaveGamePGN(f);
14140 }
14141
14142 /* Save the current position to the given file */
14143 int
14144 SavePositionToFile (char *filename)
14145 {
14146     FILE *f;
14147     char buf[MSG_SIZ];
14148
14149     if (strcmp(filename, "-") == 0) {
14150         return SavePosition(stdout, 0, NULL);
14151     } else {
14152         f = fopen(filename, "a");
14153         if (f == NULL) {
14154             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14155             DisplayError(buf, errno);
14156             return FALSE;
14157         } else {
14158             safeStrCpy(buf, lastMsg, MSG_SIZ);
14159             DisplayMessage(_("Waiting for access to save file"), "");
14160             flock(fileno(f), LOCK_EX); // [HGM] lock
14161             DisplayMessage(_("Saving position"), "");
14162             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14163             SavePosition(f, 0, NULL);
14164             DisplayMessage(buf, "");
14165             return TRUE;
14166         }
14167     }
14168 }
14169
14170 /* Save the current position to the given open file and close the file */
14171 int
14172 SavePosition (FILE *f, int dummy, char *dummy2)
14173 {
14174     time_t tm;
14175     char *fen;
14176
14177     if (gameMode == EditPosition) EditPositionDone(TRUE);
14178     if (appData.oldSaveStyle) {
14179         tm = time((time_t *) NULL);
14180
14181         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14182         PrintOpponents(f);
14183         fprintf(f, "[--------------\n");
14184         PrintPosition(f, currentMove);
14185         fprintf(f, "--------------]\n");
14186     } else {
14187         fen = PositionToFEN(currentMove, NULL, 1);
14188         fprintf(f, "%s\n", fen);
14189         free(fen);
14190     }
14191     fclose(f);
14192     return TRUE;
14193 }
14194
14195 void
14196 ReloadCmailMsgEvent (int unregister)
14197 {
14198 #if !WIN32
14199     static char *inFilename = NULL;
14200     static char *outFilename;
14201     int i;
14202     struct stat inbuf, outbuf;
14203     int status;
14204
14205     /* Any registered moves are unregistered if unregister is set, */
14206     /* i.e. invoked by the signal handler */
14207     if (unregister) {
14208         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14209             cmailMoveRegistered[i] = FALSE;
14210             if (cmailCommentList[i] != NULL) {
14211                 free(cmailCommentList[i]);
14212                 cmailCommentList[i] = NULL;
14213             }
14214         }
14215         nCmailMovesRegistered = 0;
14216     }
14217
14218     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14219         cmailResult[i] = CMAIL_NOT_RESULT;
14220     }
14221     nCmailResults = 0;
14222
14223     if (inFilename == NULL) {
14224         /* Because the filenames are static they only get malloced once  */
14225         /* and they never get freed                                      */
14226         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14227         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14228
14229         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14230         sprintf(outFilename, "%s.out", appData.cmailGameName);
14231     }
14232
14233     status = stat(outFilename, &outbuf);
14234     if (status < 0) {
14235         cmailMailedMove = FALSE;
14236     } else {
14237         status = stat(inFilename, &inbuf);
14238         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14239     }
14240
14241     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14242        counts the games, notes how each one terminated, etc.
14243
14244        It would be nice to remove this kludge and instead gather all
14245        the information while building the game list.  (And to keep it
14246        in the game list nodes instead of having a bunch of fixed-size
14247        parallel arrays.)  Note this will require getting each game's
14248        termination from the PGN tags, as the game list builder does
14249        not process the game moves.  --mann
14250        */
14251     cmailMsgLoaded = TRUE;
14252     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14253
14254     /* Load first game in the file or popup game menu */
14255     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14256
14257 #endif /* !WIN32 */
14258     return;
14259 }
14260
14261 int
14262 RegisterMove ()
14263 {
14264     FILE *f;
14265     char string[MSG_SIZ];
14266
14267     if (   cmailMailedMove
14268         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14269         return TRUE;            /* Allow free viewing  */
14270     }
14271
14272     /* Unregister move to ensure that we don't leave RegisterMove        */
14273     /* with the move registered when the conditions for registering no   */
14274     /* longer hold                                                       */
14275     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14276         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14277         nCmailMovesRegistered --;
14278
14279         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14280           {
14281               free(cmailCommentList[lastLoadGameNumber - 1]);
14282               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14283           }
14284     }
14285
14286     if (cmailOldMove == -1) {
14287         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14288         return FALSE;
14289     }
14290
14291     if (currentMove > cmailOldMove + 1) {
14292         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14293         return FALSE;
14294     }
14295
14296     if (currentMove < cmailOldMove) {
14297         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14298         return FALSE;
14299     }
14300
14301     if (forwardMostMove > currentMove) {
14302         /* Silently truncate extra moves */
14303         TruncateGame();
14304     }
14305
14306     if (   (currentMove == cmailOldMove + 1)
14307         || (   (currentMove == cmailOldMove)
14308             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14309                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14310         if (gameInfo.result != GameUnfinished) {
14311             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14312         }
14313
14314         if (commentList[currentMove] != NULL) {
14315             cmailCommentList[lastLoadGameNumber - 1]
14316               = StrSave(commentList[currentMove]);
14317         }
14318         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14319
14320         if (appData.debugMode)
14321           fprintf(debugFP, "Saving %s for game %d\n",
14322                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14323
14324         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14325
14326         f = fopen(string, "w");
14327         if (appData.oldSaveStyle) {
14328             SaveGameOldStyle(f); /* also closes the file */
14329
14330             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14331             f = fopen(string, "w");
14332             SavePosition(f, 0, NULL); /* also closes the file */
14333         } else {
14334             fprintf(f, "{--------------\n");
14335             PrintPosition(f, currentMove);
14336             fprintf(f, "--------------}\n\n");
14337
14338             SaveGame(f, 0, NULL); /* also closes the file*/
14339         }
14340
14341         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14342         nCmailMovesRegistered ++;
14343     } else if (nCmailGames == 1) {
14344         DisplayError(_("You have not made a move yet"), 0);
14345         return FALSE;
14346     }
14347
14348     return TRUE;
14349 }
14350
14351 void
14352 MailMoveEvent ()
14353 {
14354 #if !WIN32
14355     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14356     FILE *commandOutput;
14357     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14358     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14359     int nBuffers;
14360     int i;
14361     int archived;
14362     char *arcDir;
14363
14364     if (! cmailMsgLoaded) {
14365         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14366         return;
14367     }
14368
14369     if (nCmailGames == nCmailResults) {
14370         DisplayError(_("No unfinished games"), 0);
14371         return;
14372     }
14373
14374 #if CMAIL_PROHIBIT_REMAIL
14375     if (cmailMailedMove) {
14376       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);
14377         DisplayError(msg, 0);
14378         return;
14379     }
14380 #endif
14381
14382     if (! (cmailMailedMove || RegisterMove())) return;
14383
14384     if (   cmailMailedMove
14385         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14386       snprintf(string, MSG_SIZ, partCommandString,
14387                appData.debugMode ? " -v" : "", appData.cmailGameName);
14388         commandOutput = popen(string, "r");
14389
14390         if (commandOutput == NULL) {
14391             DisplayError(_("Failed to invoke cmail"), 0);
14392         } else {
14393             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14394                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14395             }
14396             if (nBuffers > 1) {
14397                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14398                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14399                 nBytes = MSG_SIZ - 1;
14400             } else {
14401                 (void) memcpy(msg, buffer, nBytes);
14402             }
14403             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14404
14405             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14406                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14407
14408                 archived = TRUE;
14409                 for (i = 0; i < nCmailGames; i ++) {
14410                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14411                         archived = FALSE;
14412                     }
14413                 }
14414                 if (   archived
14415                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14416                         != NULL)) {
14417                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14418                            arcDir,
14419                            appData.cmailGameName,
14420                            gameInfo.date);
14421                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14422                     cmailMsgLoaded = FALSE;
14423                 }
14424             }
14425
14426             DisplayInformation(msg);
14427             pclose(commandOutput);
14428         }
14429     } else {
14430         if ((*cmailMsg) != '\0') {
14431             DisplayInformation(cmailMsg);
14432         }
14433     }
14434
14435     return;
14436 #endif /* !WIN32 */
14437 }
14438
14439 char *
14440 CmailMsg ()
14441 {
14442 #if WIN32
14443     return NULL;
14444 #else
14445     int  prependComma = 0;
14446     char number[5];
14447     char string[MSG_SIZ];       /* Space for game-list */
14448     int  i;
14449
14450     if (!cmailMsgLoaded) return "";
14451
14452     if (cmailMailedMove) {
14453       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14454     } else {
14455         /* Create a list of games left */
14456       snprintf(string, MSG_SIZ, "[");
14457         for (i = 0; i < nCmailGames; i ++) {
14458             if (! (   cmailMoveRegistered[i]
14459                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14460                 if (prependComma) {
14461                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14462                 } else {
14463                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14464                     prependComma = 1;
14465                 }
14466
14467                 strcat(string, number);
14468             }
14469         }
14470         strcat(string, "]");
14471
14472         if (nCmailMovesRegistered + nCmailResults == 0) {
14473             switch (nCmailGames) {
14474               case 1:
14475                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14476                 break;
14477
14478               case 2:
14479                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14480                 break;
14481
14482               default:
14483                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14484                          nCmailGames);
14485                 break;
14486             }
14487         } else {
14488             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14489               case 1:
14490                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14491                          string);
14492                 break;
14493
14494               case 0:
14495                 if (nCmailResults == nCmailGames) {
14496                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14497                 } else {
14498                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14499                 }
14500                 break;
14501
14502               default:
14503                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14504                          string);
14505             }
14506         }
14507     }
14508     return cmailMsg;
14509 #endif /* WIN32 */
14510 }
14511
14512 void
14513 ResetGameEvent ()
14514 {
14515     if (gameMode == Training)
14516       SetTrainingModeOff();
14517
14518     Reset(TRUE, TRUE);
14519     cmailMsgLoaded = FALSE;
14520     if (appData.icsActive) {
14521       SendToICS(ics_prefix);
14522       SendToICS("refresh\n");
14523     }
14524 }
14525
14526 void
14527 ExitEvent (int status)
14528 {
14529     exiting++;
14530     if (exiting > 2) {
14531       /* Give up on clean exit */
14532       exit(status);
14533     }
14534     if (exiting > 1) {
14535       /* Keep trying for clean exit */
14536       return;
14537     }
14538
14539     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14540     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14541
14542     if (telnetISR != NULL) {
14543       RemoveInputSource(telnetISR);
14544     }
14545     if (icsPR != NoProc) {
14546       DestroyChildProcess(icsPR, TRUE);
14547     }
14548
14549     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14550     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14551
14552     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14553     /* make sure this other one finishes before killing it!                  */
14554     if(endingGame) { int count = 0;
14555         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14556         while(endingGame && count++ < 10) DoSleep(1);
14557         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14558     }
14559
14560     /* Kill off chess programs */
14561     if (first.pr != NoProc) {
14562         ExitAnalyzeMode();
14563
14564         DoSleep( appData.delayBeforeQuit );
14565         SendToProgram("quit\n", &first);
14566         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14567     }
14568     if (second.pr != NoProc) {
14569         DoSleep( appData.delayBeforeQuit );
14570         SendToProgram("quit\n", &second);
14571         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14572     }
14573     if (first.isr != NULL) {
14574         RemoveInputSource(first.isr);
14575     }
14576     if (second.isr != NULL) {
14577         RemoveInputSource(second.isr);
14578     }
14579
14580     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14581     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14582
14583     ShutDownFrontEnd();
14584     exit(status);
14585 }
14586
14587 void
14588 PauseEngine (ChessProgramState *cps)
14589 {
14590     SendToProgram("pause\n", cps);
14591     cps->pause = 2;
14592 }
14593
14594 void
14595 UnPauseEngine (ChessProgramState *cps)
14596 {
14597     SendToProgram("resume\n", cps);
14598     cps->pause = 1;
14599 }
14600
14601 void
14602 PauseEvent ()
14603 {
14604     if (appData.debugMode)
14605         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14606     if (pausing) {
14607         pausing = FALSE;
14608         ModeHighlight();
14609         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14610             StartClocks();
14611             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14612                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14613                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14614             }
14615             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14616             HandleMachineMove(stashedInputMove, stalledEngine);
14617             stalledEngine = NULL;
14618             return;
14619         }
14620         if (gameMode == MachinePlaysWhite ||
14621             gameMode == TwoMachinesPlay   ||
14622             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14623             if(first.pause)  UnPauseEngine(&first);
14624             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14625             if(second.pause) UnPauseEngine(&second);
14626             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14627             StartClocks();
14628         } else {
14629             DisplayBothClocks();
14630         }
14631         if (gameMode == PlayFromGameFile) {
14632             if (appData.timeDelay >= 0)
14633                 AutoPlayGameLoop();
14634         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14635             Reset(FALSE, TRUE);
14636             SendToICS(ics_prefix);
14637             SendToICS("refresh\n");
14638         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14639             ForwardInner(forwardMostMove);
14640         }
14641         pauseExamInvalid = FALSE;
14642     } else {
14643         switch (gameMode) {
14644           default:
14645             return;
14646           case IcsExamining:
14647             pauseExamForwardMostMove = forwardMostMove;
14648             pauseExamInvalid = FALSE;
14649             /* fall through */
14650           case IcsObserving:
14651           case IcsPlayingWhite:
14652           case IcsPlayingBlack:
14653             pausing = TRUE;
14654             ModeHighlight();
14655             return;
14656           case PlayFromGameFile:
14657             (void) StopLoadGameTimer();
14658             pausing = TRUE;
14659             ModeHighlight();
14660             break;
14661           case BeginningOfGame:
14662             if (appData.icsActive) return;
14663             /* else fall through */
14664           case MachinePlaysWhite:
14665           case MachinePlaysBlack:
14666           case TwoMachinesPlay:
14667             if (forwardMostMove == 0)
14668               return;           /* don't pause if no one has moved */
14669             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14670                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14671                 if(onMove->pause) {           // thinking engine can be paused
14672                     PauseEngine(onMove);      // do it
14673                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14674                         PauseEngine(onMove->other);
14675                     else
14676                         SendToProgram("easy\n", onMove->other);
14677                     StopClocks();
14678                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14679             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14680                 if(first.pause) {
14681                     PauseEngine(&first);
14682                     StopClocks();
14683                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14684             } else { // human on move, pause pondering by either method
14685                 if(first.pause)
14686                     PauseEngine(&first);
14687                 else if(appData.ponderNextMove)
14688                     SendToProgram("easy\n", &first);
14689                 StopClocks();
14690             }
14691             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14692           case AnalyzeMode:
14693             pausing = TRUE;
14694             ModeHighlight();
14695             break;
14696         }
14697     }
14698 }
14699
14700 void
14701 EditCommentEvent ()
14702 {
14703     char title[MSG_SIZ];
14704
14705     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14706       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14707     } else {
14708       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14709                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14710                parseList[currentMove - 1]);
14711     }
14712
14713     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14714 }
14715
14716
14717 void
14718 EditTagsEvent ()
14719 {
14720     char *tags = PGNTags(&gameInfo);
14721     bookUp = FALSE;
14722     EditTagsPopUp(tags, NULL);
14723     free(tags);
14724 }
14725
14726 void
14727 ToggleSecond ()
14728 {
14729   if(second.analyzing) {
14730     SendToProgram("exit\n", &second);
14731     second.analyzing = FALSE;
14732   } else {
14733     if (second.pr == NoProc) StartChessProgram(&second);
14734     InitChessProgram(&second, FALSE);
14735     FeedMovesToProgram(&second, currentMove);
14736
14737     SendToProgram("analyze\n", &second);
14738     second.analyzing = TRUE;
14739   }
14740 }
14741
14742 /* Toggle ShowThinking */
14743 void
14744 ToggleShowThinking()
14745 {
14746   appData.showThinking = !appData.showThinking;
14747   ShowThinkingEvent();
14748 }
14749
14750 int
14751 AnalyzeModeEvent ()
14752 {
14753     char buf[MSG_SIZ];
14754
14755     if (!first.analysisSupport) {
14756       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14757       DisplayError(buf, 0);
14758       return 0;
14759     }
14760     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14761     if (appData.icsActive) {
14762         if (gameMode != IcsObserving) {
14763           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14764             DisplayError(buf, 0);
14765             /* secure check */
14766             if (appData.icsEngineAnalyze) {
14767                 if (appData.debugMode)
14768                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14769                 ExitAnalyzeMode();
14770                 ModeHighlight();
14771             }
14772             return 0;
14773         }
14774         /* if enable, user wants to disable icsEngineAnalyze */
14775         if (appData.icsEngineAnalyze) {
14776                 ExitAnalyzeMode();
14777                 ModeHighlight();
14778                 return 0;
14779         }
14780         appData.icsEngineAnalyze = TRUE;
14781         if (appData.debugMode)
14782             fprintf(debugFP, "ICS engine analyze starting... \n");
14783     }
14784
14785     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14786     if (appData.noChessProgram || gameMode == AnalyzeMode)
14787       return 0;
14788
14789     if (gameMode != AnalyzeFile) {
14790         if (!appData.icsEngineAnalyze) {
14791                EditGameEvent();
14792                if (gameMode != EditGame) return 0;
14793         }
14794         if (!appData.showThinking) ToggleShowThinking();
14795         ResurrectChessProgram();
14796         SendToProgram("analyze\n", &first);
14797         first.analyzing = TRUE;
14798         /*first.maybeThinking = TRUE;*/
14799         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14800         EngineOutputPopUp();
14801     }
14802     if (!appData.icsEngineAnalyze) {
14803         gameMode = AnalyzeMode;
14804         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14805     }
14806     pausing = FALSE;
14807     ModeHighlight();
14808     SetGameInfo();
14809
14810     StartAnalysisClock();
14811     GetTimeMark(&lastNodeCountTime);
14812     lastNodeCount = 0;
14813     return 1;
14814 }
14815
14816 void
14817 AnalyzeFileEvent ()
14818 {
14819     if (appData.noChessProgram || gameMode == AnalyzeFile)
14820       return;
14821
14822     if (!first.analysisSupport) {
14823       char buf[MSG_SIZ];
14824       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14825       DisplayError(buf, 0);
14826       return;
14827     }
14828
14829     if (gameMode != AnalyzeMode) {
14830         keepInfo = 1; // mere annotating should not alter PGN tags
14831         EditGameEvent();
14832         keepInfo = 0;
14833         if (gameMode != EditGame) return;
14834         if (!appData.showThinking) ToggleShowThinking();
14835         ResurrectChessProgram();
14836         SendToProgram("analyze\n", &first);
14837         first.analyzing = TRUE;
14838         /*first.maybeThinking = TRUE;*/
14839         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14840         EngineOutputPopUp();
14841     }
14842     gameMode = AnalyzeFile;
14843     pausing = FALSE;
14844     ModeHighlight();
14845
14846     StartAnalysisClock();
14847     GetTimeMark(&lastNodeCountTime);
14848     lastNodeCount = 0;
14849     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14850     AnalysisPeriodicEvent(1);
14851 }
14852
14853 void
14854 MachineWhiteEvent ()
14855 {
14856     char buf[MSG_SIZ];
14857     char *bookHit = NULL;
14858
14859     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14860       return;
14861
14862
14863     if (gameMode == PlayFromGameFile ||
14864         gameMode == TwoMachinesPlay  ||
14865         gameMode == Training         ||
14866         gameMode == AnalyzeMode      ||
14867         gameMode == EndOfGame)
14868         EditGameEvent();
14869
14870     if (gameMode == EditPosition)
14871         EditPositionDone(TRUE);
14872
14873     if (!WhiteOnMove(currentMove)) {
14874         DisplayError(_("It is not White's turn"), 0);
14875         return;
14876     }
14877
14878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14879       ExitAnalyzeMode();
14880
14881     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14882         gameMode == AnalyzeFile)
14883         TruncateGame();
14884
14885     ResurrectChessProgram();    /* in case it isn't running */
14886     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14887         gameMode = MachinePlaysWhite;
14888         ResetClocks();
14889     } else
14890     gameMode = MachinePlaysWhite;
14891     pausing = FALSE;
14892     ModeHighlight();
14893     SetGameInfo();
14894     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14895     DisplayTitle(buf);
14896     if (first.sendName) {
14897       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14898       SendToProgram(buf, &first);
14899     }
14900     if (first.sendTime) {
14901       if (first.useColors) {
14902         SendToProgram("black\n", &first); /*gnu kludge*/
14903       }
14904       SendTimeRemaining(&first, TRUE);
14905     }
14906     if (first.useColors) {
14907       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14908     }
14909     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14910     SetMachineThinkingEnables();
14911     first.maybeThinking = TRUE;
14912     StartClocks();
14913     firstMove = FALSE;
14914
14915     if (appData.autoFlipView && !flipView) {
14916       flipView = !flipView;
14917       DrawPosition(FALSE, NULL);
14918       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14919     }
14920
14921     if(bookHit) { // [HGM] book: simulate book reply
14922         static char bookMove[MSG_SIZ]; // a bit generous?
14923
14924         programStats.nodes = programStats.depth = programStats.time =
14925         programStats.score = programStats.got_only_move = 0;
14926         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14927
14928         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14929         strcat(bookMove, bookHit);
14930         savedMessage = bookMove; // args for deferred call
14931         savedState = &first;
14932         ScheduleDelayedEvent(DeferredBookMove, 1);
14933     }
14934 }
14935
14936 void
14937 MachineBlackEvent ()
14938 {
14939   char buf[MSG_SIZ];
14940   char *bookHit = NULL;
14941
14942     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14943         return;
14944
14945
14946     if (gameMode == PlayFromGameFile ||
14947         gameMode == TwoMachinesPlay  ||
14948         gameMode == Training         ||
14949         gameMode == AnalyzeMode      ||
14950         gameMode == EndOfGame)
14951         EditGameEvent();
14952
14953     if (gameMode == EditPosition)
14954         EditPositionDone(TRUE);
14955
14956     if (WhiteOnMove(currentMove)) {
14957         DisplayError(_("It is not Black's turn"), 0);
14958         return;
14959     }
14960
14961     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14962       ExitAnalyzeMode();
14963
14964     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14965         gameMode == AnalyzeFile)
14966         TruncateGame();
14967
14968     ResurrectChessProgram();    /* in case it isn't running */
14969     gameMode = MachinePlaysBlack;
14970     pausing = FALSE;
14971     ModeHighlight();
14972     SetGameInfo();
14973     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14974     DisplayTitle(buf);
14975     if (first.sendName) {
14976       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14977       SendToProgram(buf, &first);
14978     }
14979     if (first.sendTime) {
14980       if (first.useColors) {
14981         SendToProgram("white\n", &first); /*gnu kludge*/
14982       }
14983       SendTimeRemaining(&first, FALSE);
14984     }
14985     if (first.useColors) {
14986       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14987     }
14988     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14989     SetMachineThinkingEnables();
14990     first.maybeThinking = TRUE;
14991     StartClocks();
14992
14993     if (appData.autoFlipView && flipView) {
14994       flipView = !flipView;
14995       DrawPosition(FALSE, NULL);
14996       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14997     }
14998     if(bookHit) { // [HGM] book: simulate book reply
14999         static char bookMove[MSG_SIZ]; // a bit generous?
15000
15001         programStats.nodes = programStats.depth = programStats.time =
15002         programStats.score = programStats.got_only_move = 0;
15003         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15004
15005         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15006         strcat(bookMove, bookHit);
15007         savedMessage = bookMove; // args for deferred call
15008         savedState = &first;
15009         ScheduleDelayedEvent(DeferredBookMove, 1);
15010     }
15011 }
15012
15013
15014 void
15015 DisplayTwoMachinesTitle ()
15016 {
15017     char buf[MSG_SIZ];
15018     if (appData.matchGames > 0) {
15019         if(appData.tourneyFile[0]) {
15020           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15021                    gameInfo.white, _("vs."), gameInfo.black,
15022                    nextGame+1, appData.matchGames+1,
15023                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15024         } else
15025         if (first.twoMachinesColor[0] == 'w') {
15026           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15027                    gameInfo.white, _("vs."),  gameInfo.black,
15028                    first.matchWins, second.matchWins,
15029                    matchGame - 1 - (first.matchWins + second.matchWins));
15030         } else {
15031           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15032                    gameInfo.white, _("vs."), gameInfo.black,
15033                    second.matchWins, first.matchWins,
15034                    matchGame - 1 - (first.matchWins + second.matchWins));
15035         }
15036     } else {
15037       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15038     }
15039     DisplayTitle(buf);
15040 }
15041
15042 void
15043 SettingsMenuIfReady ()
15044 {
15045   if (second.lastPing != second.lastPong) {
15046     DisplayMessage("", _("Waiting for second chess program"));
15047     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15048     return;
15049   }
15050   ThawUI();
15051   DisplayMessage("", "");
15052   SettingsPopUp(&second);
15053 }
15054
15055 int
15056 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15057 {
15058     char buf[MSG_SIZ];
15059     if (cps->pr == NoProc) {
15060         StartChessProgram(cps);
15061         if (cps->protocolVersion == 1) {
15062           retry();
15063           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15064         } else {
15065           /* kludge: allow timeout for initial "feature" command */
15066           if(retry != TwoMachinesEventIfReady) FreezeUI();
15067           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15068           DisplayMessage("", buf);
15069           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15070         }
15071         return 1;
15072     }
15073     return 0;
15074 }
15075
15076 void
15077 TwoMachinesEvent P((void))
15078 {
15079     int i, move = forwardMostMove;
15080     char buf[MSG_SIZ];
15081     ChessProgramState *onmove;
15082     char *bookHit = NULL;
15083     static int stalling = 0;
15084     TimeMark now;
15085     long wait;
15086
15087     if (appData.noChessProgram) return;
15088
15089     switch (gameMode) {
15090       case TwoMachinesPlay:
15091         return;
15092       case MachinePlaysWhite:
15093       case MachinePlaysBlack:
15094         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15095             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15096             return;
15097         }
15098         /* fall through */
15099       case BeginningOfGame:
15100       case PlayFromGameFile:
15101       case EndOfGame:
15102         EditGameEvent();
15103         if (gameMode != EditGame) return;
15104         break;
15105       case EditPosition:
15106         EditPositionDone(TRUE);
15107         break;
15108       case AnalyzeMode:
15109       case AnalyzeFile:
15110         ExitAnalyzeMode();
15111         break;
15112       case EditGame:
15113       default:
15114         break;
15115     }
15116
15117 //    forwardMostMove = currentMove;
15118     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15119     startingEngine = TRUE;
15120
15121     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15122
15123     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15124     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15125       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15126       return;
15127     }
15128   if(!appData.epd) {
15129     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15130
15131     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15132                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15133         startingEngine = matchMode = FALSE;
15134         DisplayError("second engine does not play this", 0);
15135         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15136         EditGameEvent(); // switch back to EditGame mode
15137         return;
15138     }
15139
15140     if(!stalling) {
15141       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15142       SendToProgram("force\n", &second);
15143       stalling = 1;
15144       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15145       return;
15146     }
15147   }
15148     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15149     if(appData.matchPause>10000 || appData.matchPause<10)
15150                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15151     wait = SubtractTimeMarks(&now, &pauseStart);
15152     if(wait < appData.matchPause) {
15153         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15154         return;
15155     }
15156     // we are now committed to starting the game
15157     stalling = 0;
15158     DisplayMessage("", "");
15159   if(!appData.epd) {
15160     if (startedFromSetupPosition) {
15161         SendBoard(&second, backwardMostMove);
15162     if (appData.debugMode) {
15163         fprintf(debugFP, "Two Machines\n");
15164     }
15165     }
15166     for (i = backwardMostMove; i < forwardMostMove; i++) {
15167         SendMoveToProgram(i, &second);
15168     }
15169   }
15170
15171     gameMode = TwoMachinesPlay;
15172     pausing = startingEngine = FALSE;
15173     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15174     SetGameInfo();
15175     DisplayTwoMachinesTitle();
15176     firstMove = TRUE;
15177     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15178         onmove = &first;
15179     } else {
15180         onmove = &second;
15181     }
15182     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15183     SendToProgram(first.computerString, &first);
15184     if (first.sendName) {
15185       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15186       SendToProgram(buf, &first);
15187     }
15188   if(!appData.epd) {
15189     SendToProgram(second.computerString, &second);
15190     if (second.sendName) {
15191       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15192       SendToProgram(buf, &second);
15193     }
15194   }
15195
15196     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15197         ResetClocks();
15198         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15199         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15200     }
15201     if (onmove->sendTime) {
15202       if (onmove->useColors) {
15203         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15204       }
15205       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15206     }
15207     if (onmove->useColors) {
15208       SendToProgram(onmove->twoMachinesColor, onmove);
15209     }
15210     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15211 //    SendToProgram("go\n", onmove);
15212     onmove->maybeThinking = TRUE;
15213     SetMachineThinkingEnables();
15214
15215     StartClocks();
15216
15217     if(bookHit) { // [HGM] book: simulate book reply
15218         static char bookMove[MSG_SIZ]; // a bit generous?
15219
15220         programStats.nodes = programStats.depth = programStats.time =
15221         programStats.score = programStats.got_only_move = 0;
15222         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15223
15224         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15225         strcat(bookMove, bookHit);
15226         savedMessage = bookMove; // args for deferred call
15227         savedState = onmove;
15228         ScheduleDelayedEvent(DeferredBookMove, 1);
15229     }
15230 }
15231
15232 void
15233 TrainingEvent ()
15234 {
15235     if (gameMode == Training) {
15236       SetTrainingModeOff();
15237       gameMode = PlayFromGameFile;
15238       DisplayMessage("", _("Training mode off"));
15239     } else {
15240       gameMode = Training;
15241       animateTraining = appData.animate;
15242
15243       /* make sure we are not already at the end of the game */
15244       if (currentMove < forwardMostMove) {
15245         SetTrainingModeOn();
15246         DisplayMessage("", _("Training mode on"));
15247       } else {
15248         gameMode = PlayFromGameFile;
15249         DisplayError(_("Already at end of game"), 0);
15250       }
15251     }
15252     ModeHighlight();
15253 }
15254
15255 void
15256 IcsClientEvent ()
15257 {
15258     if (!appData.icsActive) return;
15259     switch (gameMode) {
15260       case IcsPlayingWhite:
15261       case IcsPlayingBlack:
15262       case IcsObserving:
15263       case IcsIdle:
15264       case BeginningOfGame:
15265       case IcsExamining:
15266         return;
15267
15268       case EditGame:
15269         break;
15270
15271       case EditPosition:
15272         EditPositionDone(TRUE);
15273         break;
15274
15275       case AnalyzeMode:
15276       case AnalyzeFile:
15277         ExitAnalyzeMode();
15278         break;
15279
15280       default:
15281         EditGameEvent();
15282         break;
15283     }
15284
15285     gameMode = IcsIdle;
15286     ModeHighlight();
15287     return;
15288 }
15289
15290 void
15291 EditGameEvent ()
15292 {
15293     int i;
15294
15295     switch (gameMode) {
15296       case Training:
15297         SetTrainingModeOff();
15298         break;
15299       case MachinePlaysWhite:
15300       case MachinePlaysBlack:
15301       case BeginningOfGame:
15302         SendToProgram("force\n", &first);
15303         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15304             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15305                 char buf[MSG_SIZ];
15306                 abortEngineThink = TRUE;
15307                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15308                 SendToProgram(buf, &first);
15309                 DisplayMessage("Aborting engine think", "");
15310                 FreezeUI();
15311             }
15312         }
15313         SetUserThinkingEnables();
15314         break;
15315       case PlayFromGameFile:
15316         (void) StopLoadGameTimer();
15317         if (gameFileFP != NULL) {
15318             gameFileFP = NULL;
15319         }
15320         break;
15321       case EditPosition:
15322         EditPositionDone(TRUE);
15323         break;
15324       case AnalyzeMode:
15325       case AnalyzeFile:
15326         ExitAnalyzeMode();
15327         SendToProgram("force\n", &first);
15328         break;
15329       case TwoMachinesPlay:
15330         GameEnds(EndOfFile, NULL, GE_PLAYER);
15331         ResurrectChessProgram();
15332         SetUserThinkingEnables();
15333         break;
15334       case EndOfGame:
15335         ResurrectChessProgram();
15336         break;
15337       case IcsPlayingBlack:
15338       case IcsPlayingWhite:
15339         DisplayError(_("Warning: You are still playing a game"), 0);
15340         break;
15341       case IcsObserving:
15342         DisplayError(_("Warning: You are still observing a game"), 0);
15343         break;
15344       case IcsExamining:
15345         DisplayError(_("Warning: You are still examining a game"), 0);
15346         break;
15347       case IcsIdle:
15348         break;
15349       case EditGame:
15350       default:
15351         return;
15352     }
15353
15354     pausing = FALSE;
15355     StopClocks();
15356     first.offeredDraw = second.offeredDraw = 0;
15357
15358     if (gameMode == PlayFromGameFile) {
15359         whiteTimeRemaining = timeRemaining[0][currentMove];
15360         blackTimeRemaining = timeRemaining[1][currentMove];
15361         DisplayTitle("");
15362     }
15363
15364     if (gameMode == MachinePlaysWhite ||
15365         gameMode == MachinePlaysBlack ||
15366         gameMode == TwoMachinesPlay ||
15367         gameMode == EndOfGame) {
15368         i = forwardMostMove;
15369         while (i > currentMove) {
15370             SendToProgram("undo\n", &first);
15371             i--;
15372         }
15373         if(!adjustedClock) {
15374         whiteTimeRemaining = timeRemaining[0][currentMove];
15375         blackTimeRemaining = timeRemaining[1][currentMove];
15376         DisplayBothClocks();
15377         }
15378         if (whiteFlag || blackFlag) {
15379             whiteFlag = blackFlag = 0;
15380         }
15381         DisplayTitle("");
15382     }
15383
15384     gameMode = EditGame;
15385     ModeHighlight();
15386     SetGameInfo();
15387 }
15388
15389 void
15390 EditPositionEvent ()
15391 {
15392     int i;
15393     if (gameMode == EditPosition) {
15394         EditGameEvent();
15395         return;
15396     }
15397
15398     EditGameEvent();
15399     if (gameMode != EditGame) return;
15400
15401     gameMode = EditPosition;
15402     ModeHighlight();
15403     SetGameInfo();
15404     CopyBoard(rightsBoard, nullBoard);
15405     if (currentMove > 0)
15406       CopyBoard(boards[0], boards[currentMove]);
15407     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15408       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15409
15410     blackPlaysFirst = !WhiteOnMove(currentMove);
15411     ResetClocks();
15412     currentMove = forwardMostMove = backwardMostMove = 0;
15413     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15414     DisplayMove(-1);
15415     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15416 }
15417
15418 void
15419 ExitAnalyzeMode ()
15420 {
15421     /* [DM] icsEngineAnalyze - possible call from other functions */
15422     if (appData.icsEngineAnalyze) {
15423         appData.icsEngineAnalyze = FALSE;
15424
15425         DisplayMessage("",_("Close ICS engine analyze..."));
15426     }
15427     if (first.analysisSupport && first.analyzing) {
15428       SendToBoth("exit\n");
15429       first.analyzing = second.analyzing = FALSE;
15430     }
15431     thinkOutput[0] = NULLCHAR;
15432 }
15433
15434 void
15435 EditPositionDone (Boolean fakeRights)
15436 {
15437     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15438
15439     startedFromSetupPosition = TRUE;
15440     InitChessProgram(&first, FALSE);
15441     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15442       int r, f;
15443       boards[0][EP_STATUS] = EP_NONE;
15444       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15445       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15446         if(rightsBoard[r][f]) {
15447           ChessSquare p = boards[0][r][f];
15448           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15449           else if(p == king) boards[0][CASTLING][2] = f;
15450           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15451           else rightsBoard[r][f] = 2; // mark for second pass
15452         }
15453       }
15454       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15455         if(rightsBoard[r][f] == 2) {
15456           ChessSquare p = boards[0][r][f];
15457           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15458           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15459         }
15460       }
15461     }
15462     SendToProgram("force\n", &first);
15463     if (blackPlaysFirst) {
15464         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15465         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15466         currentMove = forwardMostMove = backwardMostMove = 1;
15467         CopyBoard(boards[1], boards[0]);
15468     } else {
15469         currentMove = forwardMostMove = backwardMostMove = 0;
15470     }
15471     SendBoard(&first, forwardMostMove);
15472     if (appData.debugMode) {
15473         fprintf(debugFP, "EditPosDone\n");
15474     }
15475     DisplayTitle("");
15476     DisplayMessage("", "");
15477     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15478     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15479     gameMode = EditGame;
15480     ModeHighlight();
15481     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15482     ClearHighlights(); /* [AS] */
15483 }
15484
15485 /* Pause for `ms' milliseconds */
15486 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15487 void
15488 TimeDelay (long ms)
15489 {
15490     TimeMark m1, m2;
15491
15492     GetTimeMark(&m1);
15493     do {
15494         GetTimeMark(&m2);
15495     } while (SubtractTimeMarks(&m2, &m1) < ms);
15496 }
15497
15498 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15499 void
15500 SendMultiLineToICS (char *buf)
15501 {
15502     char temp[MSG_SIZ+1], *p;
15503     int len;
15504
15505     len = strlen(buf);
15506     if (len > MSG_SIZ)
15507       len = MSG_SIZ;
15508
15509     strncpy(temp, buf, len);
15510     temp[len] = 0;
15511
15512     p = temp;
15513     while (*p) {
15514         if (*p == '\n' || *p == '\r')
15515           *p = ' ';
15516         ++p;
15517     }
15518
15519     strcat(temp, "\n");
15520     SendToICS(temp);
15521     SendToPlayer(temp, strlen(temp));
15522 }
15523
15524 void
15525 SetWhiteToPlayEvent ()
15526 {
15527     if (gameMode == EditPosition) {
15528         blackPlaysFirst = FALSE;
15529         DisplayBothClocks();    /* works because currentMove is 0 */
15530     } else if (gameMode == IcsExamining) {
15531         SendToICS(ics_prefix);
15532         SendToICS("tomove white\n");
15533     }
15534 }
15535
15536 void
15537 SetBlackToPlayEvent ()
15538 {
15539     if (gameMode == EditPosition) {
15540         blackPlaysFirst = TRUE;
15541         currentMove = 1;        /* kludge */
15542         DisplayBothClocks();
15543         currentMove = 0;
15544     } else if (gameMode == IcsExamining) {
15545         SendToICS(ics_prefix);
15546         SendToICS("tomove black\n");
15547     }
15548 }
15549
15550 void
15551 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15552 {
15553     char buf[MSG_SIZ];
15554     ChessSquare piece = boards[0][y][x];
15555     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15556     static int lastVariant;
15557     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15558
15559     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15560
15561     switch (selection) {
15562       case ClearBoard:
15563         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15564         MarkTargetSquares(1);
15565         CopyBoard(currentBoard, boards[0]);
15566         CopyBoard(menuBoard, initialPosition);
15567         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15568             SendToICS(ics_prefix);
15569             SendToICS("bsetup clear\n");
15570         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15571             SendToICS(ics_prefix);
15572             SendToICS("clearboard\n");
15573         } else {
15574             int nonEmpty = 0;
15575             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15576                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15577                 for (y = 0; y < BOARD_HEIGHT; y++) {
15578                     if (gameMode == IcsExamining) {
15579                         if (boards[currentMove][y][x] != EmptySquare) {
15580                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15581                                     AAA + x, ONE + y);
15582                             SendToICS(buf);
15583                         }
15584                     } else if(boards[0][y][x] != DarkSquare) {
15585                         if(boards[0][y][x] != p) nonEmpty++;
15586                         boards[0][y][x] = p;
15587                     }
15588                 }
15589             }
15590             CopyBoard(rightsBoard, nullBoard);
15591             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15592                 int r, i;
15593                 for(r = 0; r < BOARD_HEIGHT; r++) {
15594                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15595                     ChessSquare p = menuBoard[r][x];
15596                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15597                   }
15598                 }
15599                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15600                 DisplayMessage("Clicking clock again restores position", "");
15601                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15602                 if(!nonEmpty) { // asked to clear an empty board
15603                     CopyBoard(boards[0], menuBoard);
15604                 } else
15605                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15606                     CopyBoard(boards[0], initialPosition);
15607                 } else
15608                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15609                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15610                     CopyBoard(boards[0], erasedBoard);
15611                 } else
15612                     CopyBoard(erasedBoard, currentBoard);
15613
15614                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15615                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15616             }
15617         }
15618         if (gameMode == EditPosition) {
15619             DrawPosition(FALSE, boards[0]);
15620         }
15621         break;
15622
15623       case WhitePlay:
15624         SetWhiteToPlayEvent();
15625         break;
15626
15627       case BlackPlay:
15628         SetBlackToPlayEvent();
15629         break;
15630
15631       case EmptySquare:
15632         if (gameMode == IcsExamining) {
15633             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15634             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15635             SendToICS(buf);
15636         } else {
15637             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15638                 if(x == BOARD_LEFT-2) {
15639                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15640                     boards[0][y][1] = 0;
15641                 } else
15642                 if(x == BOARD_RGHT+1) {
15643                     if(y >= gameInfo.holdingsSize) break;
15644                     boards[0][y][BOARD_WIDTH-2] = 0;
15645                 } else break;
15646             }
15647             boards[0][y][x] = EmptySquare;
15648             DrawPosition(FALSE, boards[0]);
15649         }
15650         break;
15651
15652       case PromotePiece:
15653         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15654            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15655             selection = (ChessSquare) (PROMOTED(piece));
15656         } else if(piece == EmptySquare) selection = WhiteSilver;
15657         else selection = (ChessSquare)((int)piece - 1);
15658         goto defaultlabel;
15659
15660       case DemotePiece:
15661         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15662            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15663             selection = (ChessSquare) (DEMOTED(piece));
15664         } else if(piece == EmptySquare) selection = BlackSilver;
15665         else selection = (ChessSquare)((int)piece + 1);
15666         goto defaultlabel;
15667
15668       case WhiteQueen:
15669       case BlackQueen:
15670         if(gameInfo.variant == VariantShatranj ||
15671            gameInfo.variant == VariantXiangqi  ||
15672            gameInfo.variant == VariantCourier  ||
15673            gameInfo.variant == VariantASEAN    ||
15674            gameInfo.variant == VariantMakruk     )
15675             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15676         goto defaultlabel;
15677
15678       case WhiteRook:
15679         baseRank = 0;
15680       case BlackRook:
15681         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15682         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15683         goto defaultlabel;
15684
15685       case WhiteKing:
15686         baseRank = 0;
15687       case BlackKing:
15688         if(gameInfo.variant == VariantXiangqi)
15689             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15690         if(gameInfo.variant == VariantKnightmate)
15691             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15692         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15693       default:
15694         defaultlabel:
15695         if (gameMode == IcsExamining) {
15696             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15697             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15698                      PieceToChar(selection), AAA + x, ONE + y);
15699             SendToICS(buf);
15700         } else {
15701             rightsBoard[y][x] = hasRights;
15702             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15703                 int n;
15704                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15705                     n = PieceToNumber(selection - BlackPawn);
15706                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15707                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15708                     boards[0][BOARD_HEIGHT-1-n][1]++;
15709                 } else
15710                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15711                     n = PieceToNumber(selection);
15712                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15713                     boards[0][n][BOARD_WIDTH-1] = selection;
15714                     boards[0][n][BOARD_WIDTH-2]++;
15715                 }
15716             } else
15717             boards[0][y][x] = selection;
15718             DrawPosition(TRUE, boards[0]);
15719             ClearHighlights();
15720             fromX = fromY = -1;
15721         }
15722         break;
15723     }
15724 }
15725
15726
15727 void
15728 DropMenuEvent (ChessSquare selection, int x, int y)
15729 {
15730     ChessMove moveType;
15731
15732     switch (gameMode) {
15733       case IcsPlayingWhite:
15734       case MachinePlaysBlack:
15735         if (!WhiteOnMove(currentMove)) {
15736             DisplayMoveError(_("It is Black's turn"));
15737             return;
15738         }
15739         moveType = WhiteDrop;
15740         break;
15741       case IcsPlayingBlack:
15742       case MachinePlaysWhite:
15743         if (WhiteOnMove(currentMove)) {
15744             DisplayMoveError(_("It is White's turn"));
15745             return;
15746         }
15747         moveType = BlackDrop;
15748         break;
15749       case EditGame:
15750         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15751         break;
15752       default:
15753         return;
15754     }
15755
15756     if (moveType == BlackDrop && selection < BlackPawn) {
15757       selection = (ChessSquare) ((int) selection
15758                                  + (int) BlackPawn - (int) WhitePawn);
15759     }
15760     if (boards[currentMove][y][x] != EmptySquare) {
15761         DisplayMoveError(_("That square is occupied"));
15762         return;
15763     }
15764
15765     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15766 }
15767
15768 void
15769 AcceptEvent ()
15770 {
15771     /* Accept a pending offer of any kind from opponent */
15772
15773     if (appData.icsActive) {
15774         SendToICS(ics_prefix);
15775         SendToICS("accept\n");
15776     } else if (cmailMsgLoaded) {
15777         if (currentMove == cmailOldMove &&
15778             commentList[cmailOldMove] != NULL &&
15779             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15780                    "Black offers a draw" : "White offers a draw")) {
15781             TruncateGame();
15782             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15783             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15784         } else {
15785             DisplayError(_("There is no pending offer on this move"), 0);
15786             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15787         }
15788     } else {
15789         /* Not used for offers from chess program */
15790     }
15791 }
15792
15793 void
15794 DeclineEvent ()
15795 {
15796     /* Decline a pending offer of any kind from opponent */
15797
15798     if (appData.icsActive) {
15799         SendToICS(ics_prefix);
15800         SendToICS("decline\n");
15801     } else if (cmailMsgLoaded) {
15802         if (currentMove == cmailOldMove &&
15803             commentList[cmailOldMove] != NULL &&
15804             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15805                    "Black offers a draw" : "White offers a draw")) {
15806 #ifdef NOTDEF
15807             AppendComment(cmailOldMove, "Draw declined", TRUE);
15808             DisplayComment(cmailOldMove - 1, "Draw declined");
15809 #endif /*NOTDEF*/
15810         } else {
15811             DisplayError(_("There is no pending offer on this move"), 0);
15812         }
15813     } else {
15814         /* Not used for offers from chess program */
15815     }
15816 }
15817
15818 void
15819 RematchEvent ()
15820 {
15821     /* Issue ICS rematch command */
15822     if (appData.icsActive) {
15823         SendToICS(ics_prefix);
15824         SendToICS("rematch\n");
15825     }
15826 }
15827
15828 void
15829 CallFlagEvent ()
15830 {
15831     /* Call your opponent's flag (claim a win on time) */
15832     if (appData.icsActive) {
15833         SendToICS(ics_prefix);
15834         SendToICS("flag\n");
15835     } else {
15836         switch (gameMode) {
15837           default:
15838             return;
15839           case MachinePlaysWhite:
15840             if (whiteFlag) {
15841                 if (blackFlag)
15842                   GameEnds(GameIsDrawn, "Both players ran out of time",
15843                            GE_PLAYER);
15844                 else
15845                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15846             } else {
15847                 DisplayError(_("Your opponent is not out of time"), 0);
15848             }
15849             break;
15850           case MachinePlaysBlack:
15851             if (blackFlag) {
15852                 if (whiteFlag)
15853                   GameEnds(GameIsDrawn, "Both players ran out of time",
15854                            GE_PLAYER);
15855                 else
15856                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15857             } else {
15858                 DisplayError(_("Your opponent is not out of time"), 0);
15859             }
15860             break;
15861         }
15862     }
15863 }
15864
15865 void
15866 ClockClick (int which)
15867 {       // [HGM] code moved to back-end from winboard.c
15868         if(which) { // black clock
15869           if (gameMode == EditPosition || gameMode == IcsExamining) {
15870             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15871             SetBlackToPlayEvent();
15872           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15873                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15874           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15875           } else if (shiftKey) {
15876             AdjustClock(which, -1);
15877           } else if (gameMode == IcsPlayingWhite ||
15878                      gameMode == MachinePlaysBlack) {
15879             CallFlagEvent();
15880           }
15881         } else { // white clock
15882           if (gameMode == EditPosition || gameMode == IcsExamining) {
15883             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15884             SetWhiteToPlayEvent();
15885           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15886                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15887           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15888           } else if (shiftKey) {
15889             AdjustClock(which, -1);
15890           } else if (gameMode == IcsPlayingBlack ||
15891                    gameMode == MachinePlaysWhite) {
15892             CallFlagEvent();
15893           }
15894         }
15895 }
15896
15897 void
15898 DrawEvent ()
15899 {
15900     /* Offer draw or accept pending draw offer from opponent */
15901
15902     if (appData.icsActive) {
15903         /* Note: tournament rules require draw offers to be
15904            made after you make your move but before you punch
15905            your clock.  Currently ICS doesn't let you do that;
15906            instead, you immediately punch your clock after making
15907            a move, but you can offer a draw at any time. */
15908
15909         SendToICS(ics_prefix);
15910         SendToICS("draw\n");
15911         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15912     } else if (cmailMsgLoaded) {
15913         if (currentMove == cmailOldMove &&
15914             commentList[cmailOldMove] != NULL &&
15915             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15916                    "Black offers a draw" : "White offers a draw")) {
15917             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15918             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15919         } else if (currentMove == cmailOldMove + 1) {
15920             char *offer = WhiteOnMove(cmailOldMove) ?
15921               "White offers a draw" : "Black offers a draw";
15922             AppendComment(currentMove, offer, TRUE);
15923             DisplayComment(currentMove - 1, offer);
15924             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15925         } else {
15926             DisplayError(_("You must make your move before offering a draw"), 0);
15927             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15928         }
15929     } else if (first.offeredDraw) {
15930         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15931     } else {
15932         if (first.sendDrawOffers) {
15933             SendToProgram("draw\n", &first);
15934             userOfferedDraw = TRUE;
15935         }
15936     }
15937 }
15938
15939 void
15940 AdjournEvent ()
15941 {
15942     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15943
15944     if (appData.icsActive) {
15945         SendToICS(ics_prefix);
15946         SendToICS("adjourn\n");
15947     } else {
15948         /* Currently GNU Chess doesn't offer or accept Adjourns */
15949     }
15950 }
15951
15952
15953 void
15954 AbortEvent ()
15955 {
15956     /* Offer Abort or accept pending Abort offer from opponent */
15957
15958     if (appData.icsActive) {
15959         SendToICS(ics_prefix);
15960         SendToICS("abort\n");
15961     } else {
15962         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15963     }
15964 }
15965
15966 void
15967 ResignEvent ()
15968 {
15969     /* Resign.  You can do this even if it's not your turn. */
15970
15971     if (appData.icsActive) {
15972         SendToICS(ics_prefix);
15973         SendToICS("resign\n");
15974     } else {
15975         switch (gameMode) {
15976           case MachinePlaysWhite:
15977             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15978             break;
15979           case MachinePlaysBlack:
15980             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15981             break;
15982           case EditGame:
15983             if (cmailMsgLoaded) {
15984                 TruncateGame();
15985                 if (WhiteOnMove(cmailOldMove)) {
15986                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15987                 } else {
15988                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15989                 }
15990                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15991             }
15992             break;
15993           default:
15994             break;
15995         }
15996     }
15997 }
15998
15999
16000 void
16001 StopObservingEvent ()
16002 {
16003     /* Stop observing current games */
16004     SendToICS(ics_prefix);
16005     SendToICS("unobserve\n");
16006 }
16007
16008 void
16009 StopExaminingEvent ()
16010 {
16011     /* Stop observing current game */
16012     SendToICS(ics_prefix);
16013     SendToICS("unexamine\n");
16014 }
16015
16016 void
16017 ForwardInner (int target)
16018 {
16019     int limit; int oldSeekGraphUp = seekGraphUp;
16020
16021     if (appData.debugMode)
16022         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16023                 target, currentMove, forwardMostMove);
16024
16025     if (gameMode == EditPosition)
16026       return;
16027
16028     seekGraphUp = FALSE;
16029     MarkTargetSquares(1);
16030     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16031
16032     if (gameMode == PlayFromGameFile && !pausing)
16033       PauseEvent();
16034
16035     if (gameMode == IcsExamining && pausing)
16036       limit = pauseExamForwardMostMove;
16037     else
16038       limit = forwardMostMove;
16039
16040     if (target > limit) target = limit;
16041
16042     if (target > 0 && moveList[target - 1][0]) {
16043         int fromX, fromY, toX, toY;
16044         toX = moveList[target - 1][2] - AAA;
16045         toY = moveList[target - 1][3] - ONE;
16046         if (moveList[target - 1][1] == '@') {
16047             if (appData.highlightLastMove) {
16048                 SetHighlights(-1, -1, toX, toY);
16049             }
16050         } else {
16051             fromX = moveList[target - 1][0] - AAA;
16052             fromY = moveList[target - 1][1] - ONE;
16053             if (target == currentMove + 1) {
16054                 if(moveList[target - 1][4] == ';') { // multi-leg
16055                     killX = moveList[target - 1][5] - AAA;
16056                     killY = moveList[target - 1][6] - ONE;
16057                 }
16058                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16059                 killX = killY = -1;
16060             }
16061             if (appData.highlightLastMove) {
16062                 SetHighlights(fromX, fromY, toX, toY);
16063             }
16064         }
16065     }
16066     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16067         gameMode == Training || gameMode == PlayFromGameFile ||
16068         gameMode == AnalyzeFile) {
16069         while (currentMove < target) {
16070             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16071             SendMoveToProgram(currentMove++, &first);
16072         }
16073     } else {
16074         currentMove = target;
16075     }
16076
16077     if (gameMode == EditGame || gameMode == EndOfGame) {
16078         whiteTimeRemaining = timeRemaining[0][currentMove];
16079         blackTimeRemaining = timeRemaining[1][currentMove];
16080     }
16081     DisplayBothClocks();
16082     DisplayMove(currentMove - 1);
16083     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16084     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16085     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16086         DisplayComment(currentMove - 1, commentList[currentMove]);
16087     }
16088     ClearMap(); // [HGM] exclude: invalidate map
16089 }
16090
16091
16092 void
16093 ForwardEvent ()
16094 {
16095     if (gameMode == IcsExamining && !pausing) {
16096         SendToICS(ics_prefix);
16097         SendToICS("forward\n");
16098     } else {
16099         ForwardInner(currentMove + 1);
16100     }
16101 }
16102
16103 void
16104 ToEndEvent ()
16105 {
16106     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16107         /* to optimze, we temporarily turn off analysis mode while we feed
16108          * the remaining moves to the engine. Otherwise we get analysis output
16109          * after each move.
16110          */
16111         if (first.analysisSupport) {
16112           SendToProgram("exit\nforce\n", &first);
16113           first.analyzing = FALSE;
16114         }
16115     }
16116
16117     if (gameMode == IcsExamining && !pausing) {
16118         SendToICS(ics_prefix);
16119         SendToICS("forward 999999\n");
16120     } else {
16121         ForwardInner(forwardMostMove);
16122     }
16123
16124     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16125         /* we have fed all the moves, so reactivate analysis mode */
16126         SendToProgram("analyze\n", &first);
16127         first.analyzing = TRUE;
16128         /*first.maybeThinking = TRUE;*/
16129         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16130     }
16131 }
16132
16133 void
16134 BackwardInner (int target)
16135 {
16136     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16137
16138     if (appData.debugMode)
16139         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16140                 target, currentMove, forwardMostMove);
16141
16142     if (gameMode == EditPosition) return;
16143     seekGraphUp = FALSE;
16144     MarkTargetSquares(1);
16145     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16146     if (currentMove <= backwardMostMove) {
16147         ClearHighlights();
16148         DrawPosition(full_redraw, boards[currentMove]);
16149         return;
16150     }
16151     if (gameMode == PlayFromGameFile && !pausing)
16152       PauseEvent();
16153
16154     if (moveList[target][0]) {
16155         int fromX, fromY, toX, toY;
16156         toX = moveList[target][2] - AAA;
16157         toY = moveList[target][3] - ONE;
16158         if (moveList[target][1] == '@') {
16159             if (appData.highlightLastMove) {
16160                 SetHighlights(-1, -1, toX, toY);
16161             }
16162         } else {
16163             fromX = moveList[target][0] - AAA;
16164             fromY = moveList[target][1] - ONE;
16165             if (target == currentMove - 1) {
16166                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16167             }
16168             if (appData.highlightLastMove) {
16169                 SetHighlights(fromX, fromY, toX, toY);
16170             }
16171         }
16172     }
16173     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16174         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16175         while (currentMove > target) {
16176             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16177                 // null move cannot be undone. Reload program with move history before it.
16178                 int i;
16179                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16180                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16181                 }
16182                 SendBoard(&first, i);
16183               if(second.analyzing) SendBoard(&second, i);
16184                 for(currentMove=i; currentMove<target; currentMove++) {
16185                     SendMoveToProgram(currentMove, &first);
16186                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16187                 }
16188                 break;
16189             }
16190             SendToBoth("undo\n");
16191             currentMove--;
16192         }
16193     } else {
16194         currentMove = target;
16195     }
16196
16197     if (gameMode == EditGame || gameMode == EndOfGame) {
16198         whiteTimeRemaining = timeRemaining[0][currentMove];
16199         blackTimeRemaining = timeRemaining[1][currentMove];
16200     }
16201     DisplayBothClocks();
16202     DisplayMove(currentMove - 1);
16203     DrawPosition(full_redraw, boards[currentMove]);
16204     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16205     // [HGM] PV info: routine tests if comment empty
16206     DisplayComment(currentMove - 1, commentList[currentMove]);
16207     ClearMap(); // [HGM] exclude: invalidate map
16208 }
16209
16210 void
16211 BackwardEvent ()
16212 {
16213     if (gameMode == IcsExamining && !pausing) {
16214         SendToICS(ics_prefix);
16215         SendToICS("backward\n");
16216     } else {
16217         BackwardInner(currentMove - 1);
16218     }
16219 }
16220
16221 void
16222 ToStartEvent ()
16223 {
16224     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16225         /* to optimize, we temporarily turn off analysis mode while we undo
16226          * all the moves. Otherwise we get analysis output after each undo.
16227          */
16228         if (first.analysisSupport) {
16229           SendToProgram("exit\nforce\n", &first);
16230           first.analyzing = FALSE;
16231         }
16232     }
16233
16234     if (gameMode == IcsExamining && !pausing) {
16235         SendToICS(ics_prefix);
16236         SendToICS("backward 999999\n");
16237     } else {
16238         BackwardInner(backwardMostMove);
16239     }
16240
16241     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16242         /* we have fed all the moves, so reactivate analysis mode */
16243         SendToProgram("analyze\n", &first);
16244         first.analyzing = TRUE;
16245         /*first.maybeThinking = TRUE;*/
16246         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16247     }
16248 }
16249
16250 void
16251 ToNrEvent (int to)
16252 {
16253   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16254   if (to >= forwardMostMove) to = forwardMostMove;
16255   if (to <= backwardMostMove) to = backwardMostMove;
16256   if (to < currentMove) {
16257     BackwardInner(to);
16258   } else {
16259     ForwardInner(to);
16260   }
16261 }
16262
16263 void
16264 RevertEvent (Boolean annotate)
16265 {
16266     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16267         return;
16268     }
16269     if (gameMode != IcsExamining) {
16270         DisplayError(_("You are not examining a game"), 0);
16271         return;
16272     }
16273     if (pausing) {
16274         DisplayError(_("You can't revert while pausing"), 0);
16275         return;
16276     }
16277     SendToICS(ics_prefix);
16278     SendToICS("revert\n");
16279 }
16280
16281 void
16282 RetractMoveEvent ()
16283 {
16284     switch (gameMode) {
16285       case MachinePlaysWhite:
16286       case MachinePlaysBlack:
16287         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16288             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16289             return;
16290         }
16291         if (forwardMostMove < 2) return;
16292         currentMove = forwardMostMove = forwardMostMove - 2;
16293         whiteTimeRemaining = timeRemaining[0][currentMove];
16294         blackTimeRemaining = timeRemaining[1][currentMove];
16295         DisplayBothClocks();
16296         DisplayMove(currentMove - 1);
16297         ClearHighlights();/*!! could figure this out*/
16298         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16299         SendToProgram("remove\n", &first);
16300         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16301         break;
16302
16303       case BeginningOfGame:
16304       default:
16305         break;
16306
16307       case IcsPlayingWhite:
16308       case IcsPlayingBlack:
16309         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16310             SendToICS(ics_prefix);
16311             SendToICS("takeback 2\n");
16312         } else {
16313             SendToICS(ics_prefix);
16314             SendToICS("takeback 1\n");
16315         }
16316         break;
16317     }
16318 }
16319
16320 void
16321 MoveNowEvent ()
16322 {
16323     ChessProgramState *cps;
16324
16325     switch (gameMode) {
16326       case MachinePlaysWhite:
16327         if (!WhiteOnMove(forwardMostMove)) {
16328             DisplayError(_("It is your turn"), 0);
16329             return;
16330         }
16331         cps = &first;
16332         break;
16333       case MachinePlaysBlack:
16334         if (WhiteOnMove(forwardMostMove)) {
16335             DisplayError(_("It is your turn"), 0);
16336             return;
16337         }
16338         cps = &first;
16339         break;
16340       case TwoMachinesPlay:
16341         if (WhiteOnMove(forwardMostMove) ==
16342             (first.twoMachinesColor[0] == 'w')) {
16343             cps = &first;
16344         } else {
16345             cps = &second;
16346         }
16347         break;
16348       case BeginningOfGame:
16349       default:
16350         return;
16351     }
16352     SendToProgram("?\n", cps);
16353 }
16354
16355 void
16356 TruncateGameEvent ()
16357 {
16358     EditGameEvent();
16359     if (gameMode != EditGame) return;
16360     TruncateGame();
16361 }
16362
16363 void
16364 TruncateGame ()
16365 {
16366     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16367     if (forwardMostMove > currentMove) {
16368         if (gameInfo.resultDetails != NULL) {
16369             free(gameInfo.resultDetails);
16370             gameInfo.resultDetails = NULL;
16371             gameInfo.result = GameUnfinished;
16372         }
16373         forwardMostMove = currentMove;
16374         HistorySet(parseList, backwardMostMove, forwardMostMove,
16375                    currentMove-1);
16376     }
16377 }
16378
16379 void
16380 HintEvent ()
16381 {
16382     if (appData.noChessProgram) return;
16383     switch (gameMode) {
16384       case MachinePlaysWhite:
16385         if (WhiteOnMove(forwardMostMove)) {
16386             DisplayError(_("Wait until your turn."), 0);
16387             return;
16388         }
16389         break;
16390       case BeginningOfGame:
16391       case MachinePlaysBlack:
16392         if (!WhiteOnMove(forwardMostMove)) {
16393             DisplayError(_("Wait until your turn."), 0);
16394             return;
16395         }
16396         break;
16397       default:
16398         DisplayError(_("No hint available"), 0);
16399         return;
16400     }
16401     SendToProgram("hint\n", &first);
16402     hintRequested = TRUE;
16403 }
16404
16405 int
16406 SaveSelected (FILE *g, int dummy, char *dummy2)
16407 {
16408     ListGame * lg = (ListGame *) gameList.head;
16409     int nItem, cnt=0;
16410     FILE *f;
16411
16412     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16413         DisplayError(_("Game list not loaded or empty"), 0);
16414         return 0;
16415     }
16416
16417     creatingBook = TRUE; // suppresses stuff during load game
16418
16419     /* Get list size */
16420     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16421         if(lg->position >= 0) { // selected?
16422             LoadGame(f, nItem, "", TRUE);
16423             SaveGamePGN2(g); // leaves g open
16424             cnt++; DoEvents();
16425         }
16426         lg = (ListGame *) lg->node.succ;
16427     }
16428
16429     fclose(g);
16430     creatingBook = FALSE;
16431
16432     return cnt;
16433 }
16434
16435 void
16436 CreateBookEvent ()
16437 {
16438     ListGame * lg = (ListGame *) gameList.head;
16439     FILE *f, *g;
16440     int nItem;
16441     static int secondTime = FALSE;
16442
16443     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16444         DisplayError(_("Game list not loaded or empty"), 0);
16445         return;
16446     }
16447
16448     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16449         fclose(g);
16450         secondTime++;
16451         DisplayNote(_("Book file exists! Try again for overwrite."));
16452         return;
16453     }
16454
16455     creatingBook = TRUE;
16456     secondTime = FALSE;
16457
16458     /* Get list size */
16459     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16460         if(lg->position >= 0) {
16461             LoadGame(f, nItem, "", TRUE);
16462             AddGameToBook(TRUE);
16463             DoEvents();
16464         }
16465         lg = (ListGame *) lg->node.succ;
16466     }
16467
16468     creatingBook = FALSE;
16469     FlushBook();
16470 }
16471
16472 void
16473 BookEvent ()
16474 {
16475     if (appData.noChessProgram) return;
16476     switch (gameMode) {
16477       case MachinePlaysWhite:
16478         if (WhiteOnMove(forwardMostMove)) {
16479             DisplayError(_("Wait until your turn."), 0);
16480             return;
16481         }
16482         break;
16483       case BeginningOfGame:
16484       case MachinePlaysBlack:
16485         if (!WhiteOnMove(forwardMostMove)) {
16486             DisplayError(_("Wait until your turn."), 0);
16487             return;
16488         }
16489         break;
16490       case EditPosition:
16491         EditPositionDone(TRUE);
16492         break;
16493       case TwoMachinesPlay:
16494         return;
16495       default:
16496         break;
16497     }
16498     SendToProgram("bk\n", &first);
16499     bookOutput[0] = NULLCHAR;
16500     bookRequested = TRUE;
16501 }
16502
16503 void
16504 AboutGameEvent ()
16505 {
16506     char *tags = PGNTags(&gameInfo);
16507     TagsPopUp(tags, CmailMsg());
16508     free(tags);
16509 }
16510
16511 /* end button procedures */
16512
16513 void
16514 PrintPosition (FILE *fp, int move)
16515 {
16516     int i, j;
16517
16518     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16519         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16520             char c = PieceToChar(boards[move][i][j]);
16521             fputc(c == '?' ? '.' : c, fp);
16522             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16523         }
16524     }
16525     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16526       fprintf(fp, "white to play\n");
16527     else
16528       fprintf(fp, "black to play\n");
16529 }
16530
16531 void
16532 PrintOpponents (FILE *fp)
16533 {
16534     if (gameInfo.white != NULL) {
16535         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16536     } else {
16537         fprintf(fp, "\n");
16538     }
16539 }
16540
16541 /* Find last component of program's own name, using some heuristics */
16542 void
16543 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16544 {
16545     char *p, *q, c;
16546     int local = (strcmp(host, "localhost") == 0);
16547     while (!local && (p = strchr(prog, ';')) != NULL) {
16548         p++;
16549         while (*p == ' ') p++;
16550         prog = p;
16551     }
16552     if (*prog == '"' || *prog == '\'') {
16553         q = strchr(prog + 1, *prog);
16554     } else {
16555         q = strchr(prog, ' ');
16556     }
16557     if (q == NULL) q = prog + strlen(prog);
16558     p = q;
16559     while (p >= prog && *p != '/' && *p != '\\') p--;
16560     p++;
16561     if(p == prog && *p == '"') p++;
16562     c = *q; *q = 0;
16563     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16564     memcpy(buf, p, q - p);
16565     buf[q - p] = NULLCHAR;
16566     if (!local) {
16567         strcat(buf, "@");
16568         strcat(buf, host);
16569     }
16570 }
16571
16572 char *
16573 TimeControlTagValue ()
16574 {
16575     char buf[MSG_SIZ];
16576     if (!appData.clockMode) {
16577       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16578     } else if (movesPerSession > 0) {
16579       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16580     } else if (timeIncrement == 0) {
16581       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16582     } else {
16583       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16584     }
16585     return StrSave(buf);
16586 }
16587
16588 void
16589 SetGameInfo ()
16590 {
16591     /* This routine is used only for certain modes */
16592     VariantClass v = gameInfo.variant;
16593     ChessMove r = GameUnfinished;
16594     char *p = NULL;
16595
16596     if(keepInfo) return;
16597
16598     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16599         r = gameInfo.result;
16600         p = gameInfo.resultDetails;
16601         gameInfo.resultDetails = NULL;
16602     }
16603     ClearGameInfo(&gameInfo);
16604     gameInfo.variant = v;
16605
16606     switch (gameMode) {
16607       case MachinePlaysWhite:
16608         gameInfo.event = StrSave( appData.pgnEventHeader );
16609         gameInfo.site = StrSave(HostName());
16610         gameInfo.date = PGNDate();
16611         gameInfo.round = StrSave("-");
16612         gameInfo.white = StrSave(first.tidy);
16613         gameInfo.black = StrSave(UserName());
16614         gameInfo.timeControl = TimeControlTagValue();
16615         break;
16616
16617       case MachinePlaysBlack:
16618         gameInfo.event = StrSave( appData.pgnEventHeader );
16619         gameInfo.site = StrSave(HostName());
16620         gameInfo.date = PGNDate();
16621         gameInfo.round = StrSave("-");
16622         gameInfo.white = StrSave(UserName());
16623         gameInfo.black = StrSave(first.tidy);
16624         gameInfo.timeControl = TimeControlTagValue();
16625         break;
16626
16627       case TwoMachinesPlay:
16628         gameInfo.event = StrSave( appData.pgnEventHeader );
16629         gameInfo.site = StrSave(HostName());
16630         gameInfo.date = PGNDate();
16631         if (roundNr > 0) {
16632             char buf[MSG_SIZ];
16633             snprintf(buf, MSG_SIZ, "%d", roundNr);
16634             gameInfo.round = StrSave(buf);
16635         } else {
16636             gameInfo.round = StrSave("-");
16637         }
16638         if (first.twoMachinesColor[0] == 'w') {
16639             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16640             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16641         } else {
16642             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16643             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16644         }
16645         gameInfo.timeControl = TimeControlTagValue();
16646         break;
16647
16648       case EditGame:
16649         gameInfo.event = StrSave("Edited game");
16650         gameInfo.site = StrSave(HostName());
16651         gameInfo.date = PGNDate();
16652         gameInfo.round = StrSave("-");
16653         gameInfo.white = StrSave("-");
16654         gameInfo.black = StrSave("-");
16655         gameInfo.result = r;
16656         gameInfo.resultDetails = p;
16657         break;
16658
16659       case EditPosition:
16660         gameInfo.event = StrSave("Edited position");
16661         gameInfo.site = StrSave(HostName());
16662         gameInfo.date = PGNDate();
16663         gameInfo.round = StrSave("-");
16664         gameInfo.white = StrSave("-");
16665         gameInfo.black = StrSave("-");
16666         break;
16667
16668       case IcsPlayingWhite:
16669       case IcsPlayingBlack:
16670       case IcsObserving:
16671       case IcsExamining:
16672         break;
16673
16674       case PlayFromGameFile:
16675         gameInfo.event = StrSave("Game from non-PGN file");
16676         gameInfo.site = StrSave(HostName());
16677         gameInfo.date = PGNDate();
16678         gameInfo.round = StrSave("-");
16679         gameInfo.white = StrSave("?");
16680         gameInfo.black = StrSave("?");
16681         break;
16682
16683       default:
16684         break;
16685     }
16686 }
16687
16688 void
16689 ReplaceComment (int index, char *text)
16690 {
16691     int len;
16692     char *p;
16693     float score;
16694
16695     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16696        pvInfoList[index-1].depth == len &&
16697        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16698        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16699     while (*text == '\n') text++;
16700     len = strlen(text);
16701     while (len > 0 && text[len - 1] == '\n') len--;
16702
16703     if (commentList[index] != NULL)
16704       free(commentList[index]);
16705
16706     if (len == 0) {
16707         commentList[index] = NULL;
16708         return;
16709     }
16710   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16711       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16712       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16713     commentList[index] = (char *) malloc(len + 2);
16714     strncpy(commentList[index], text, len);
16715     commentList[index][len] = '\n';
16716     commentList[index][len + 1] = NULLCHAR;
16717   } else {
16718     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16719     char *p;
16720     commentList[index] = (char *) malloc(len + 7);
16721     safeStrCpy(commentList[index], "{\n", 3);
16722     safeStrCpy(commentList[index]+2, text, len+1);
16723     commentList[index][len+2] = NULLCHAR;
16724     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16725     strcat(commentList[index], "\n}\n");
16726   }
16727 }
16728
16729 void
16730 CrushCRs (char *text)
16731 {
16732   char *p = text;
16733   char *q = text;
16734   char ch;
16735
16736   do {
16737     ch = *p++;
16738     if (ch == '\r') continue;
16739     *q++ = ch;
16740   } while (ch != '\0');
16741 }
16742
16743 void
16744 AppendComment (int index, char *text, Boolean addBraces)
16745 /* addBraces  tells if we should add {} */
16746 {
16747     int oldlen, len;
16748     char *old;
16749
16750 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16751     if(addBraces == 3) addBraces = 0; else // force appending literally
16752     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16753
16754     CrushCRs(text);
16755     while (*text == '\n') text++;
16756     len = strlen(text);
16757     while (len > 0 && text[len - 1] == '\n') len--;
16758     text[len] = NULLCHAR;
16759
16760     if (len == 0) return;
16761
16762     if (commentList[index] != NULL) {
16763       Boolean addClosingBrace = addBraces;
16764         old = commentList[index];
16765         oldlen = strlen(old);
16766         while(commentList[index][oldlen-1] ==  '\n')
16767           commentList[index][--oldlen] = NULLCHAR;
16768         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16769         safeStrCpy(commentList[index], old, oldlen + len + 6);
16770         free(old);
16771         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16772         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16773           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16774           while (*text == '\n') { text++; len--; }
16775           commentList[index][--oldlen] = NULLCHAR;
16776       }
16777         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16778         else          strcat(commentList[index], "\n");
16779         strcat(commentList[index], text);
16780         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16781         else          strcat(commentList[index], "\n");
16782     } else {
16783         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16784         if(addBraces)
16785           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16786         else commentList[index][0] = NULLCHAR;
16787         strcat(commentList[index], text);
16788         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16789         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16790     }
16791 }
16792
16793 static char *
16794 FindStr (char * text, char * sub_text)
16795 {
16796     char * result = strstr( text, sub_text );
16797
16798     if( result != NULL ) {
16799         result += strlen( sub_text );
16800     }
16801
16802     return result;
16803 }
16804
16805 /* [AS] Try to extract PV info from PGN comment */
16806 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16807 char *
16808 GetInfoFromComment (int index, char * text)
16809 {
16810     char * sep = text, *p;
16811
16812     if( text != NULL && index > 0 ) {
16813         int score = 0;
16814         int depth = 0;
16815         int time = -1, sec = 0, deci;
16816         char * s_eval = FindStr( text, "[%eval " );
16817         char * s_emt = FindStr( text, "[%emt " );
16818 #if 0
16819         if( s_eval != NULL || s_emt != NULL ) {
16820 #else
16821         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16822 #endif
16823             /* New style */
16824             char delim;
16825
16826             if( s_eval != NULL ) {
16827                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16828                     return text;
16829                 }
16830
16831                 if( delim != ']' ) {
16832                     return text;
16833                 }
16834             }
16835
16836             if( s_emt != NULL ) {
16837             }
16838                 return text;
16839         }
16840         else {
16841             /* We expect something like: [+|-]nnn.nn/dd */
16842             int score_lo = 0;
16843
16844             if(*text != '{') return text; // [HGM] braces: must be normal comment
16845
16846             sep = strchr( text, '/' );
16847             if( sep == NULL || sep < (text+4) ) {
16848                 return text;
16849             }
16850
16851             p = text;
16852             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16853             if(p[1] == '(') { // comment starts with PV
16854                p = strchr(p, ')'); // locate end of PV
16855                if(p == NULL || sep < p+5) return text;
16856                // at this point we have something like "{(.*) +0.23/6 ..."
16857                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16858                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16859                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16860             }
16861             time = -1; sec = -1; deci = -1;
16862             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16863                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16864                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16865                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16866                 return text;
16867             }
16868
16869             if( score_lo < 0 || score_lo >= 100 ) {
16870                 return text;
16871             }
16872
16873             if(sec >= 0) time = 600*time + 10*sec; else
16874             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16875
16876             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16877
16878             /* [HGM] PV time: now locate end of PV info */
16879             while( *++sep >= '0' && *sep <= '9'); // strip depth
16880             if(time >= 0)
16881             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16882             if(sec >= 0)
16883             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16884             if(deci >= 0)
16885             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16886             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16887         }
16888
16889         if( depth <= 0 ) {
16890             return text;
16891         }
16892
16893         if( time < 0 ) {
16894             time = -1;
16895         }
16896
16897         pvInfoList[index-1].depth = depth;
16898         pvInfoList[index-1].score = score;
16899         pvInfoList[index-1].time  = 10*time; // centi-sec
16900         if(*sep == '}') *sep = 0; else *--sep = '{';
16901         if(p != text) {
16902             while(*p++ = *sep++)
16903                                 ;
16904             sep = text;
16905         } // squeeze out space between PV and comment, and return both
16906     }
16907     return sep;
16908 }
16909
16910 void
16911 SendToProgram (char *message, ChessProgramState *cps)
16912 {
16913     int count, outCount, error;
16914     char buf[MSG_SIZ];
16915
16916     if (cps->pr == NoProc) return;
16917     Attention(cps);
16918
16919     if (appData.debugMode) {
16920         TimeMark now;
16921         GetTimeMark(&now);
16922         fprintf(debugFP, "%ld >%-6s: %s",
16923                 SubtractTimeMarks(&now, &programStartTime),
16924                 cps->which, message);
16925         if(serverFP)
16926             fprintf(serverFP, "%ld >%-6s: %s",
16927                 SubtractTimeMarks(&now, &programStartTime),
16928                 cps->which, message), fflush(serverFP);
16929     }
16930
16931     count = strlen(message);
16932     outCount = OutputToProcess(cps->pr, message, count, &error);
16933     if (outCount < count && !exiting
16934                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16935       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16936       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16937         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16938             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16939                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16940                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16941                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16942             } else {
16943                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16944                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16945                 gameInfo.result = res;
16946             }
16947             gameInfo.resultDetails = StrSave(buf);
16948         }
16949         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16950         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16951     }
16952 }
16953
16954 void
16955 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16956 {
16957     char *end_str;
16958     char buf[MSG_SIZ];
16959     ChessProgramState *cps = (ChessProgramState *)closure;
16960
16961     if (isr != cps->isr) return; /* Killed intentionally */
16962     if (count <= 0) {
16963         if (count == 0) {
16964             RemoveInputSource(cps->isr);
16965             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16966                     _(cps->which), cps->program);
16967             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16968             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16969                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16970                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16971                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16972                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16973                 } else {
16974                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16975                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16976                     gameInfo.result = res;
16977                 }
16978                 gameInfo.resultDetails = StrSave(buf);
16979             }
16980             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16981             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16982         } else {
16983             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16984                     _(cps->which), cps->program);
16985             RemoveInputSource(cps->isr);
16986
16987             /* [AS] Program is misbehaving badly... kill it */
16988             if( count == -2 ) {
16989                 DestroyChildProcess( cps->pr, 9 );
16990                 cps->pr = NoProc;
16991             }
16992
16993             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16994         }
16995         return;
16996     }
16997
16998     if ((end_str = strchr(message, '\r')) != NULL)
16999       *end_str = NULLCHAR;
17000     if ((end_str = strchr(message, '\n')) != NULL)
17001       *end_str = NULLCHAR;
17002
17003     if (appData.debugMode) {
17004         TimeMark now; int print = 1;
17005         char *quote = ""; char c; int i;
17006
17007         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17008                 char start = message[0];
17009                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17010                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17011                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17012                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17013                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17014                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17015                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17016                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17017                    sscanf(message, "hint: %c", &c)!=1 &&
17018                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17019                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17020                     print = (appData.engineComments >= 2);
17021                 }
17022                 message[0] = start; // restore original message
17023         }
17024         if(print) {
17025                 GetTimeMark(&now);
17026                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17027                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17028                         quote,
17029                         message);
17030                 if(serverFP)
17031                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17032                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17033                         quote,
17034                         message), fflush(serverFP);
17035         }
17036     }
17037
17038     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17039     if (appData.icsEngineAnalyze) {
17040         if (strstr(message, "whisper") != NULL ||
17041              strstr(message, "kibitz") != NULL ||
17042             strstr(message, "tellics") != NULL) return;
17043     }
17044
17045     HandleMachineMove(message, cps);
17046 }
17047
17048
17049 void
17050 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17051 {
17052     char buf[MSG_SIZ];
17053     int seconds;
17054
17055     if( timeControl_2 > 0 ) {
17056         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17057             tc = timeControl_2;
17058         }
17059     }
17060     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17061     inc /= cps->timeOdds;
17062     st  /= cps->timeOdds;
17063
17064     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17065
17066     if (st > 0) {
17067       /* Set exact time per move, normally using st command */
17068       if (cps->stKludge) {
17069         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17070         seconds = st % 60;
17071         if (seconds == 0) {
17072           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17073         } else {
17074           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17075         }
17076       } else {
17077         snprintf(buf, MSG_SIZ, "st %d\n", st);
17078       }
17079     } else {
17080       /* Set conventional or incremental time control, using level command */
17081       if (seconds == 0) {
17082         /* Note old gnuchess bug -- minutes:seconds used to not work.
17083            Fixed in later versions, but still avoid :seconds
17084            when seconds is 0. */
17085         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17086       } else {
17087         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17088                  seconds, inc/1000.);
17089       }
17090     }
17091     SendToProgram(buf, cps);
17092
17093     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17094     /* Orthogonally, limit search to given depth */
17095     if (sd > 0) {
17096       if (cps->sdKludge) {
17097         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17098       } else {
17099         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17100       }
17101       SendToProgram(buf, cps);
17102     }
17103
17104     if(cps->nps >= 0) { /* [HGM] nps */
17105         if(cps->supportsNPS == FALSE)
17106           cps->nps = -1; // don't use if engine explicitly says not supported!
17107         else {
17108           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17109           SendToProgram(buf, cps);
17110         }
17111     }
17112 }
17113
17114 ChessProgramState *
17115 WhitePlayer ()
17116 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17117 {
17118     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17119        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17120         return &second;
17121     return &first;
17122 }
17123
17124 void
17125 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17126 {
17127     char message[MSG_SIZ];
17128     long time, otime;
17129
17130     /* Note: this routine must be called when the clocks are stopped
17131        or when they have *just* been set or switched; otherwise
17132        it will be off by the time since the current tick started.
17133     */
17134     if (machineWhite) {
17135         time = whiteTimeRemaining / 10;
17136         otime = blackTimeRemaining / 10;
17137     } else {
17138         time = blackTimeRemaining / 10;
17139         otime = whiteTimeRemaining / 10;
17140     }
17141     /* [HGM] translate opponent's time by time-odds factor */
17142     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17143
17144     if (time <= 0) time = 1;
17145     if (otime <= 0) otime = 1;
17146
17147     snprintf(message, MSG_SIZ, "time %ld\n", time);
17148     SendToProgram(message, cps);
17149
17150     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17151     SendToProgram(message, cps);
17152 }
17153
17154 char *
17155 EngineDefinedVariant (ChessProgramState *cps, int n)
17156 {   // return name of n-th unknown variant that engine supports
17157     static char buf[MSG_SIZ];
17158     char *p, *s = cps->variants;
17159     if(!s) return NULL;
17160     do { // parse string from variants feature
17161       VariantClass v;
17162         p = strchr(s, ',');
17163         if(p) *p = NULLCHAR;
17164       v = StringToVariant(s);
17165       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17166         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17167             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17168                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17169                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17170                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17171             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17172         }
17173         if(p) *p++ = ',';
17174         if(n < 0) return buf;
17175     } while(s = p);
17176     return NULL;
17177 }
17178
17179 int
17180 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17181 {
17182   char buf[MSG_SIZ];
17183   int len = strlen(name);
17184   int val;
17185
17186   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17187     (*p) += len + 1;
17188     sscanf(*p, "%d", &val);
17189     *loc = (val != 0);
17190     while (**p && **p != ' ')
17191       (*p)++;
17192     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17193     SendToProgram(buf, cps);
17194     return TRUE;
17195   }
17196   return FALSE;
17197 }
17198
17199 int
17200 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17201 {
17202   char buf[MSG_SIZ];
17203   int len = strlen(name);
17204   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17205     (*p) += len + 1;
17206     sscanf(*p, "%d", loc);
17207     while (**p && **p != ' ') (*p)++;
17208     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17209     SendToProgram(buf, cps);
17210     return TRUE;
17211   }
17212   return FALSE;
17213 }
17214
17215 int
17216 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17217 {
17218   char buf[MSG_SIZ];
17219   int len = strlen(name);
17220   if (strncmp((*p), name, len) == 0
17221       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17222     (*p) += len + 2;
17223     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17224     FREE(*loc); *loc = malloc(len);
17225     strncpy(*loc, *p, len);
17226     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17227     while (**p && **p != '\"') (*p)++;
17228     if (**p == '\"') (*p)++;
17229     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17230     SendToProgram(buf, cps);
17231     return TRUE;
17232   }
17233   return FALSE;
17234 }
17235
17236 int
17237 ParseOption (Option *opt, ChessProgramState *cps)
17238 // [HGM] options: process the string that defines an engine option, and determine
17239 // name, type, default value, and allowed value range
17240 {
17241         char *p, *q, buf[MSG_SIZ];
17242         int n, min = (-1)<<31, max = 1<<31, def;
17243
17244         opt->target = &opt->value;   // OK for spin/slider and checkbox
17245         if(p = strstr(opt->name, " -spin ")) {
17246             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17247             if(max < min) max = min; // enforce consistency
17248             if(def < min) def = min;
17249             if(def > max) def = max;
17250             opt->value = def;
17251             opt->min = min;
17252             opt->max = max;
17253             opt->type = Spin;
17254         } else if((p = strstr(opt->name, " -slider "))) {
17255             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17256             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17257             if(max < min) max = min; // enforce consistency
17258             if(def < min) def = min;
17259             if(def > max) def = max;
17260             opt->value = def;
17261             opt->min = min;
17262             opt->max = max;
17263             opt->type = Spin; // Slider;
17264         } else if((p = strstr(opt->name, " -string "))) {
17265             opt->textValue = p+9;
17266             opt->type = TextBox;
17267             opt->target = &opt->textValue;
17268         } else if((p = strstr(opt->name, " -file "))) {
17269             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17270             opt->target = opt->textValue = p+7;
17271             opt->type = FileName; // FileName;
17272             opt->target = &opt->textValue;
17273         } else if((p = strstr(opt->name, " -path "))) {
17274             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17275             opt->target = opt->textValue = p+7;
17276             opt->type = PathName; // PathName;
17277             opt->target = &opt->textValue;
17278         } else if(p = strstr(opt->name, " -check ")) {
17279             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17280             opt->value = (def != 0);
17281             opt->type = CheckBox;
17282         } else if(p = strstr(opt->name, " -combo ")) {
17283             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17284             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17285             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17286             opt->value = n = 0;
17287             while(q = StrStr(q, " /// ")) {
17288                 n++; *q = 0;    // count choices, and null-terminate each of them
17289                 q += 5;
17290                 if(*q == '*') { // remember default, which is marked with * prefix
17291                     q++;
17292                     opt->value = n;
17293                 }
17294                 cps->comboList[cps->comboCnt++] = q;
17295             }
17296             cps->comboList[cps->comboCnt++] = NULL;
17297             opt->max = n + 1;
17298             opt->type = ComboBox;
17299         } else if(p = strstr(opt->name, " -button")) {
17300             opt->type = Button;
17301         } else if(p = strstr(opt->name, " -save")) {
17302             opt->type = SaveButton;
17303         } else return FALSE;
17304         *p = 0; // terminate option name
17305         // now look if the command-line options define a setting for this engine option.
17306         if(cps->optionSettings && cps->optionSettings[0])
17307             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17308         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17309           snprintf(buf, MSG_SIZ, "option %s", p);
17310                 if(p = strstr(buf, ",")) *p = 0;
17311                 if(q = strchr(buf, '=')) switch(opt->type) {
17312                     case ComboBox:
17313                         for(n=0; n<opt->max; n++)
17314                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17315                         break;
17316                     case TextBox:
17317                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17318                         break;
17319                     case Spin:
17320                     case CheckBox:
17321                         opt->value = atoi(q+1);
17322                     default:
17323                         break;
17324                 }
17325                 strcat(buf, "\n");
17326                 SendToProgram(buf, cps);
17327         }
17328         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17329         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17330         return TRUE;
17331 }
17332
17333 void
17334 FeatureDone (ChessProgramState *cps, int val)
17335 {
17336   DelayedEventCallback cb = GetDelayedEvent();
17337   if ((cb == InitBackEnd3 && cps == &first) ||
17338       (cb == SettingsMenuIfReady && cps == &second) ||
17339       (cb == LoadEngine) ||
17340       (cb == TwoMachinesEventIfReady)) {
17341     CancelDelayedEvent();
17342     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17343   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17344   cps->initDone = val;
17345   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17346 }
17347
17348 /* Parse feature command from engine */
17349 void
17350 ParseFeatures (char *args, ChessProgramState *cps)
17351 {
17352   char *p = args;
17353   char *q = NULL;
17354   int val;
17355   char buf[MSG_SIZ];
17356
17357   for (;;) {
17358     while (*p == ' ') p++;
17359     if (*p == NULLCHAR) return;
17360
17361     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17362     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17363     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17364     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17365     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17366     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17367     if (BoolFeature(&p, "reuse", &val, cps)) {
17368       /* Engine can disable reuse, but can't enable it if user said no */
17369       if (!val) cps->reuse = FALSE;
17370       continue;
17371     }
17372     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17373     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17374       if (gameMode == TwoMachinesPlay) {
17375         DisplayTwoMachinesTitle();
17376       } else {
17377         DisplayTitle("");
17378       }
17379       continue;
17380     }
17381     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17382     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17383     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17384     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17385     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17386     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17387     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17388     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17389     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17390     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17391     if (IntFeature(&p, "done", &val, cps)) {
17392       FeatureDone(cps, val);
17393       continue;
17394     }
17395     /* Added by Tord: */
17396     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17397     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17398     /* End of additions by Tord */
17399
17400     /* [HGM] added features: */
17401     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17402     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17403     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17404     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17405     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17406     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17407     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17408     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17409         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17410         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17411         FREE(cps->option[cps->nrOptions].name);
17412         cps->option[cps->nrOptions].name = q; q = NULL;
17413         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17414           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17415             SendToProgram(buf, cps);
17416             continue;
17417         }
17418         if(cps->nrOptions >= MAX_OPTIONS) {
17419             cps->nrOptions--;
17420             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17421             DisplayError(buf, 0);
17422         }
17423         continue;
17424     }
17425     /* End of additions by HGM */
17426
17427     /* unknown feature: complain and skip */
17428     q = p;
17429     while (*q && *q != '=') q++;
17430     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17431     SendToProgram(buf, cps);
17432     p = q;
17433     if (*p == '=') {
17434       p++;
17435       if (*p == '\"') {
17436         p++;
17437         while (*p && *p != '\"') p++;
17438         if (*p == '\"') p++;
17439       } else {
17440         while (*p && *p != ' ') p++;
17441       }
17442     }
17443   }
17444
17445 }
17446
17447 void
17448 PeriodicUpdatesEvent (int newState)
17449 {
17450     if (newState == appData.periodicUpdates)
17451       return;
17452
17453     appData.periodicUpdates=newState;
17454
17455     /* Display type changes, so update it now */
17456 //    DisplayAnalysis();
17457
17458     /* Get the ball rolling again... */
17459     if (newState) {
17460         AnalysisPeriodicEvent(1);
17461         StartAnalysisClock();
17462     }
17463 }
17464
17465 void
17466 PonderNextMoveEvent (int newState)
17467 {
17468     if (newState == appData.ponderNextMove) return;
17469     if (gameMode == EditPosition) EditPositionDone(TRUE);
17470     if (newState) {
17471         SendToProgram("hard\n", &first);
17472         if (gameMode == TwoMachinesPlay) {
17473             SendToProgram("hard\n", &second);
17474         }
17475     } else {
17476         SendToProgram("easy\n", &first);
17477         thinkOutput[0] = NULLCHAR;
17478         if (gameMode == TwoMachinesPlay) {
17479             SendToProgram("easy\n", &second);
17480         }
17481     }
17482     appData.ponderNextMove = newState;
17483 }
17484
17485 void
17486 NewSettingEvent (int option, int *feature, char *command, int value)
17487 {
17488     char buf[MSG_SIZ];
17489
17490     if (gameMode == EditPosition) EditPositionDone(TRUE);
17491     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17492     if(feature == NULL || *feature) SendToProgram(buf, &first);
17493     if (gameMode == TwoMachinesPlay) {
17494         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17495     }
17496 }
17497
17498 void
17499 ShowThinkingEvent ()
17500 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17501 {
17502     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17503     int newState = appData.showThinking
17504         // [HGM] thinking: other features now need thinking output as well
17505         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17506
17507     if (oldState == newState) return;
17508     oldState = newState;
17509     if (gameMode == EditPosition) EditPositionDone(TRUE);
17510     if (oldState) {
17511         SendToProgram("post\n", &first);
17512         if (gameMode == TwoMachinesPlay) {
17513             SendToProgram("post\n", &second);
17514         }
17515     } else {
17516         SendToProgram("nopost\n", &first);
17517         thinkOutput[0] = NULLCHAR;
17518         if (gameMode == TwoMachinesPlay) {
17519             SendToProgram("nopost\n", &second);
17520         }
17521     }
17522 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17523 }
17524
17525 void
17526 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17527 {
17528   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17529   if (pr == NoProc) return;
17530   AskQuestion(title, question, replyPrefix, pr);
17531 }
17532
17533 void
17534 TypeInEvent (char firstChar)
17535 {
17536     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17537         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17538         gameMode == AnalyzeMode || gameMode == EditGame ||
17539         gameMode == EditPosition || gameMode == IcsExamining ||
17540         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17541         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17542                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17543                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17544         gameMode == Training) PopUpMoveDialog(firstChar);
17545 }
17546
17547 void
17548 TypeInDoneEvent (char *move)
17549 {
17550         Board board;
17551         int n, fromX, fromY, toX, toY;
17552         char promoChar;
17553         ChessMove moveType;
17554
17555         // [HGM] FENedit
17556         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17557                 EditPositionPasteFEN(move);
17558                 return;
17559         }
17560         // [HGM] movenum: allow move number to be typed in any mode
17561         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17562           ToNrEvent(2*n-1);
17563           return;
17564         }
17565         // undocumented kludge: allow command-line option to be typed in!
17566         // (potentially fatal, and does not implement the effect of the option.)
17567         // should only be used for options that are values on which future decisions will be made,
17568         // and definitely not on options that would be used during initialization.
17569         if(strstr(move, "!!! -") == move) {
17570             ParseArgsFromString(move+4);
17571             return;
17572         }
17573
17574       if (gameMode != EditGame && currentMove != forwardMostMove &&
17575         gameMode != Training) {
17576         DisplayMoveError(_("Displayed move is not current"));
17577       } else {
17578         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17579           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17580         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17581         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17582           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17583           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17584         } else {
17585           DisplayMoveError(_("Could not parse move"));
17586         }
17587       }
17588 }
17589
17590 void
17591 DisplayMove (int moveNumber)
17592 {
17593     char message[MSG_SIZ];
17594     char res[MSG_SIZ];
17595     char cpThinkOutput[MSG_SIZ];
17596
17597     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17598
17599     if (moveNumber == forwardMostMove - 1 ||
17600         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17601
17602         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17603
17604         if (strchr(cpThinkOutput, '\n')) {
17605             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17606         }
17607     } else {
17608         *cpThinkOutput = NULLCHAR;
17609     }
17610
17611     /* [AS] Hide thinking from human user */
17612     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17613         *cpThinkOutput = NULLCHAR;
17614         if( thinkOutput[0] != NULLCHAR ) {
17615             int i;
17616
17617             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17618                 cpThinkOutput[i] = '.';
17619             }
17620             cpThinkOutput[i] = NULLCHAR;
17621             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17622         }
17623     }
17624
17625     if (moveNumber == forwardMostMove - 1 &&
17626         gameInfo.resultDetails != NULL) {
17627         if (gameInfo.resultDetails[0] == NULLCHAR) {
17628           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17629         } else {
17630           snprintf(res, MSG_SIZ, " {%s} %s",
17631                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17632         }
17633     } else {
17634         res[0] = NULLCHAR;
17635     }
17636
17637     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17638         DisplayMessage(res, cpThinkOutput);
17639     } else {
17640       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17641                 WhiteOnMove(moveNumber) ? " " : ".. ",
17642                 parseList[moveNumber], res);
17643         DisplayMessage(message, cpThinkOutput);
17644     }
17645 }
17646
17647 void
17648 DisplayComment (int moveNumber, char *text)
17649 {
17650     char title[MSG_SIZ];
17651
17652     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17653       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17654     } else {
17655       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17656               WhiteOnMove(moveNumber) ? " " : ".. ",
17657               parseList[moveNumber]);
17658     }
17659     if (text != NULL && (appData.autoDisplayComment || commentUp))
17660         CommentPopUp(title, text);
17661 }
17662
17663 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17664  * might be busy thinking or pondering.  It can be omitted if your
17665  * gnuchess is configured to stop thinking immediately on any user
17666  * input.  However, that gnuchess feature depends on the FIONREAD
17667  * ioctl, which does not work properly on some flavors of Unix.
17668  */
17669 void
17670 Attention (ChessProgramState *cps)
17671 {
17672 #if ATTENTION
17673     if (!cps->useSigint) return;
17674     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17675     switch (gameMode) {
17676       case MachinePlaysWhite:
17677       case MachinePlaysBlack:
17678       case TwoMachinesPlay:
17679       case IcsPlayingWhite:
17680       case IcsPlayingBlack:
17681       case AnalyzeMode:
17682       case AnalyzeFile:
17683         /* Skip if we know it isn't thinking */
17684         if (!cps->maybeThinking) return;
17685         if (appData.debugMode)
17686           fprintf(debugFP, "Interrupting %s\n", cps->which);
17687         InterruptChildProcess(cps->pr);
17688         cps->maybeThinking = FALSE;
17689         break;
17690       default:
17691         break;
17692     }
17693 #endif /*ATTENTION*/
17694 }
17695
17696 int
17697 CheckFlags ()
17698 {
17699     if (whiteTimeRemaining <= 0) {
17700         if (!whiteFlag) {
17701             whiteFlag = TRUE;
17702             if (appData.icsActive) {
17703                 if (appData.autoCallFlag &&
17704                     gameMode == IcsPlayingBlack && !blackFlag) {
17705                   SendToICS(ics_prefix);
17706                   SendToICS("flag\n");
17707                 }
17708             } else {
17709                 if (blackFlag) {
17710                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17711                 } else {
17712                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17713                     if (appData.autoCallFlag) {
17714                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17715                         return TRUE;
17716                     }
17717                 }
17718             }
17719         }
17720     }
17721     if (blackTimeRemaining <= 0) {
17722         if (!blackFlag) {
17723             blackFlag = TRUE;
17724             if (appData.icsActive) {
17725                 if (appData.autoCallFlag &&
17726                     gameMode == IcsPlayingWhite && !whiteFlag) {
17727                   SendToICS(ics_prefix);
17728                   SendToICS("flag\n");
17729                 }
17730             } else {
17731                 if (whiteFlag) {
17732                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17733                 } else {
17734                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17735                     if (appData.autoCallFlag) {
17736                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17737                         return TRUE;
17738                     }
17739                 }
17740             }
17741         }
17742     }
17743     return FALSE;
17744 }
17745
17746 void
17747 CheckTimeControl ()
17748 {
17749     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17750         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17751
17752     /*
17753      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17754      */
17755     if ( !WhiteOnMove(forwardMostMove) ) {
17756         /* White made time control */
17757         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17758         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17759         /* [HGM] time odds: correct new time quota for time odds! */
17760                                             / WhitePlayer()->timeOdds;
17761         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17762     } else {
17763         lastBlack -= blackTimeRemaining;
17764         /* Black made time control */
17765         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17766                                             / WhitePlayer()->other->timeOdds;
17767         lastWhite = whiteTimeRemaining;
17768     }
17769 }
17770
17771 void
17772 DisplayBothClocks ()
17773 {
17774     int wom = gameMode == EditPosition ?
17775       !blackPlaysFirst : WhiteOnMove(currentMove);
17776     DisplayWhiteClock(whiteTimeRemaining, wom);
17777     DisplayBlackClock(blackTimeRemaining, !wom);
17778 }
17779
17780
17781 /* Timekeeping seems to be a portability nightmare.  I think everyone
17782    has ftime(), but I'm really not sure, so I'm including some ifdefs
17783    to use other calls if you don't.  Clocks will be less accurate if
17784    you have neither ftime nor gettimeofday.
17785 */
17786
17787 /* VS 2008 requires the #include outside of the function */
17788 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17789 #include <sys/timeb.h>
17790 #endif
17791
17792 /* Get the current time as a TimeMark */
17793 void
17794 GetTimeMark (TimeMark *tm)
17795 {
17796 #if HAVE_GETTIMEOFDAY
17797
17798     struct timeval timeVal;
17799     struct timezone timeZone;
17800
17801     gettimeofday(&timeVal, &timeZone);
17802     tm->sec = (long) timeVal.tv_sec;
17803     tm->ms = (int) (timeVal.tv_usec / 1000L);
17804
17805 #else /*!HAVE_GETTIMEOFDAY*/
17806 #if HAVE_FTIME
17807
17808 // include <sys/timeb.h> / moved to just above start of function
17809     struct timeb timeB;
17810
17811     ftime(&timeB);
17812     tm->sec = (long) timeB.time;
17813     tm->ms = (int) timeB.millitm;
17814
17815 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17816     tm->sec = (long) time(NULL);
17817     tm->ms = 0;
17818 #endif
17819 #endif
17820 }
17821
17822 /* Return the difference in milliseconds between two
17823    time marks.  We assume the difference will fit in a long!
17824 */
17825 long
17826 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17827 {
17828     return 1000L*(tm2->sec - tm1->sec) +
17829            (long) (tm2->ms - tm1->ms);
17830 }
17831
17832
17833 /*
17834  * Code to manage the game clocks.
17835  *
17836  * In tournament play, black starts the clock and then white makes a move.
17837  * We give the human user a slight advantage if he is playing white---the
17838  * clocks don't run until he makes his first move, so it takes zero time.
17839  * Also, we don't account for network lag, so we could get out of sync
17840  * with GNU Chess's clock -- but then, referees are always right.
17841  */
17842
17843 static TimeMark tickStartTM;
17844 static long intendedTickLength;
17845
17846 long
17847 NextTickLength (long timeRemaining)
17848 {
17849     long nominalTickLength, nextTickLength;
17850
17851     if (timeRemaining > 0L && timeRemaining <= 10000L)
17852       nominalTickLength = 100L;
17853     else
17854       nominalTickLength = 1000L;
17855     nextTickLength = timeRemaining % nominalTickLength;
17856     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17857
17858     return nextTickLength;
17859 }
17860
17861 /* Adjust clock one minute up or down */
17862 void
17863 AdjustClock (Boolean which, int dir)
17864 {
17865     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17866     if(which) blackTimeRemaining += 60000*dir;
17867     else      whiteTimeRemaining += 60000*dir;
17868     DisplayBothClocks();
17869     adjustedClock = TRUE;
17870 }
17871
17872 /* Stop clocks and reset to a fresh time control */
17873 void
17874 ResetClocks ()
17875 {
17876     (void) StopClockTimer();
17877     if (appData.icsActive) {
17878         whiteTimeRemaining = blackTimeRemaining = 0;
17879     } else if (searchTime) {
17880         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17881         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17882     } else { /* [HGM] correct new time quote for time odds */
17883         whiteTC = blackTC = fullTimeControlString;
17884         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17885         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17886     }
17887     if (whiteFlag || blackFlag) {
17888         DisplayTitle("");
17889         whiteFlag = blackFlag = FALSE;
17890     }
17891     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17892     DisplayBothClocks();
17893     adjustedClock = FALSE;
17894 }
17895
17896 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17897
17898 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17899
17900 /* Decrement running clock by amount of time that has passed */
17901 void
17902 DecrementClocks ()
17903 {
17904     long tRemaining;
17905     long lastTickLength, fudge;
17906     TimeMark now;
17907
17908     if (!appData.clockMode) return;
17909     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17910
17911     GetTimeMark(&now);
17912
17913     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17914
17915     /* Fudge if we woke up a little too soon */
17916     fudge = intendedTickLength - lastTickLength;
17917     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17918
17919     if (WhiteOnMove(forwardMostMove)) {
17920         if(whiteNPS >= 0) lastTickLength = 0;
17921          tRemaining = whiteTimeRemaining -= lastTickLength;
17922         if( tRemaining < 0 && !appData.icsActive) {
17923             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17924             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17925                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17926                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17927             }
17928         }
17929         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17930         DisplayWhiteClock(whiteTimeRemaining - fudge,
17931                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17932         timeSuffix = 0;
17933     } else {
17934         if(blackNPS >= 0) lastTickLength = 0;
17935          tRemaining = blackTimeRemaining -= lastTickLength;
17936         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17937             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17938             if(suddenDeath) {
17939                 blackStartMove = forwardMostMove;
17940                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17941             }
17942         }
17943         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17944         DisplayBlackClock(blackTimeRemaining - fudge,
17945                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17946         timeSuffix = 0;
17947     }
17948     if (CheckFlags()) return;
17949
17950     if(twoBoards) { // count down secondary board's clocks as well
17951         activePartnerTime -= lastTickLength;
17952         partnerUp = 1;
17953         if(activePartner == 'W')
17954             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17955         else
17956             DisplayBlackClock(activePartnerTime, TRUE);
17957         partnerUp = 0;
17958     }
17959
17960     tickStartTM = now;
17961     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17962     StartClockTimer(intendedTickLength);
17963
17964     /* if the time remaining has fallen below the alarm threshold, sound the
17965      * alarm. if the alarm has sounded and (due to a takeback or time control
17966      * with increment) the time remaining has increased to a level above the
17967      * threshold, reset the alarm so it can sound again.
17968      */
17969
17970     if (appData.icsActive && appData.icsAlarm) {
17971
17972         /* make sure we are dealing with the user's clock */
17973         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17974                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17975            )) return;
17976
17977         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17978             alarmSounded = FALSE;
17979         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17980             PlayAlarmSound();
17981             alarmSounded = TRUE;
17982         }
17983     }
17984 }
17985
17986
17987 /* A player has just moved, so stop the previously running
17988    clock and (if in clock mode) start the other one.
17989    We redisplay both clocks in case we're in ICS mode, because
17990    ICS gives us an update to both clocks after every move.
17991    Note that this routine is called *after* forwardMostMove
17992    is updated, so the last fractional tick must be subtracted
17993    from the color that is *not* on move now.
17994 */
17995 void
17996 SwitchClocks (int newMoveNr)
17997 {
17998     long lastTickLength;
17999     TimeMark now;
18000     int flagged = FALSE;
18001
18002     GetTimeMark(&now);
18003
18004     if (StopClockTimer() && appData.clockMode) {
18005         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18006         if (!WhiteOnMove(forwardMostMove)) {
18007             if(blackNPS >= 0) lastTickLength = 0;
18008             blackTimeRemaining -= lastTickLength;
18009            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18010 //         if(pvInfoList[forwardMostMove].time == -1)
18011                  pvInfoList[forwardMostMove].time =               // use GUI time
18012                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18013         } else {
18014            if(whiteNPS >= 0) lastTickLength = 0;
18015            whiteTimeRemaining -= lastTickLength;
18016            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18017 //         if(pvInfoList[forwardMostMove].time == -1)
18018                  pvInfoList[forwardMostMove].time =
18019                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18020         }
18021         flagged = CheckFlags();
18022     }
18023     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18024     CheckTimeControl();
18025
18026     if (flagged || !appData.clockMode) return;
18027
18028     switch (gameMode) {
18029       case MachinePlaysBlack:
18030       case MachinePlaysWhite:
18031       case BeginningOfGame:
18032         if (pausing) return;
18033         break;
18034
18035       case EditGame:
18036       case PlayFromGameFile:
18037       case IcsExamining:
18038         return;
18039
18040       default:
18041         break;
18042     }
18043
18044     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18045         if(WhiteOnMove(forwardMostMove))
18046              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18047         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18048     }
18049
18050     tickStartTM = now;
18051     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18052       whiteTimeRemaining : blackTimeRemaining);
18053     StartClockTimer(intendedTickLength);
18054 }
18055
18056
18057 /* Stop both clocks */
18058 void
18059 StopClocks ()
18060 {
18061     long lastTickLength;
18062     TimeMark now;
18063
18064     if (!StopClockTimer()) return;
18065     if (!appData.clockMode) return;
18066
18067     GetTimeMark(&now);
18068
18069     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18070     if (WhiteOnMove(forwardMostMove)) {
18071         if(whiteNPS >= 0) lastTickLength = 0;
18072         whiteTimeRemaining -= lastTickLength;
18073         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18074     } else {
18075         if(blackNPS >= 0) lastTickLength = 0;
18076         blackTimeRemaining -= lastTickLength;
18077         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18078     }
18079     CheckFlags();
18080 }
18081
18082 /* Start clock of player on move.  Time may have been reset, so
18083    if clock is already running, stop and restart it. */
18084 void
18085 StartClocks ()
18086 {
18087     (void) StopClockTimer(); /* in case it was running already */
18088     DisplayBothClocks();
18089     if (CheckFlags()) return;
18090
18091     if (!appData.clockMode) return;
18092     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18093
18094     GetTimeMark(&tickStartTM);
18095     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18096       whiteTimeRemaining : blackTimeRemaining);
18097
18098    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18099     whiteNPS = blackNPS = -1;
18100     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18101        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18102         whiteNPS = first.nps;
18103     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18104        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18105         blackNPS = first.nps;
18106     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18107         whiteNPS = second.nps;
18108     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18109         blackNPS = second.nps;
18110     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18111
18112     StartClockTimer(intendedTickLength);
18113 }
18114
18115 char *
18116 TimeString (long ms)
18117 {
18118     long second, minute, hour, day;
18119     char *sign = "";
18120     static char buf[40], moveTime[8];
18121
18122     if (ms > 0 && ms <= 9900) {
18123       /* convert milliseconds to tenths, rounding up */
18124       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18125
18126       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18127       return buf;
18128     }
18129
18130     /* convert milliseconds to seconds, rounding up */
18131     /* use floating point to avoid strangeness of integer division
18132        with negative dividends on many machines */
18133     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18134
18135     if (second < 0) {
18136         sign = "-";
18137         second = -second;
18138     }
18139
18140     day = second / (60 * 60 * 24);
18141     second = second % (60 * 60 * 24);
18142     hour = second / (60 * 60);
18143     second = second % (60 * 60);
18144     minute = second / 60;
18145     second = second % 60;
18146
18147     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18148     else *moveTime = NULLCHAR;
18149
18150     if (day > 0)
18151       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18152               sign, day, hour, minute, second, moveTime);
18153     else if (hour > 0)
18154       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18155     else
18156       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18157
18158     return buf;
18159 }
18160
18161
18162 /*
18163  * This is necessary because some C libraries aren't ANSI C compliant yet.
18164  */
18165 char *
18166 StrStr (char *string, char *match)
18167 {
18168     int i, length;
18169
18170     length = strlen(match);
18171
18172     for (i = strlen(string) - length; i >= 0; i--, string++)
18173       if (!strncmp(match, string, length))
18174         return string;
18175
18176     return NULL;
18177 }
18178
18179 char *
18180 StrCaseStr (char *string, char *match)
18181 {
18182     int i, j, length;
18183
18184     length = strlen(match);
18185
18186     for (i = strlen(string) - length; i >= 0; i--, string++) {
18187         for (j = 0; j < length; j++) {
18188             if (ToLower(match[j]) != ToLower(string[j]))
18189               break;
18190         }
18191         if (j == length) return string;
18192     }
18193
18194     return NULL;
18195 }
18196
18197 #ifndef _amigados
18198 int
18199 StrCaseCmp (char *s1, char *s2)
18200 {
18201     char c1, c2;
18202
18203     for (;;) {
18204         c1 = ToLower(*s1++);
18205         c2 = ToLower(*s2++);
18206         if (c1 > c2) return 1;
18207         if (c1 < c2) return -1;
18208         if (c1 == NULLCHAR) return 0;
18209     }
18210 }
18211
18212
18213 int
18214 ToLower (int c)
18215 {
18216     return isupper(c) ? tolower(c) : c;
18217 }
18218
18219
18220 int
18221 ToUpper (int c)
18222 {
18223     return islower(c) ? toupper(c) : c;
18224 }
18225 #endif /* !_amigados    */
18226
18227 char *
18228 StrSave (char *s)
18229 {
18230   char *ret;
18231
18232   if ((ret = (char *) malloc(strlen(s) + 1)))
18233     {
18234       safeStrCpy(ret, s, strlen(s)+1);
18235     }
18236   return ret;
18237 }
18238
18239 char *
18240 StrSavePtr (char *s, char **savePtr)
18241 {
18242     if (*savePtr) {
18243         free(*savePtr);
18244     }
18245     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18246       safeStrCpy(*savePtr, s, strlen(s)+1);
18247     }
18248     return(*savePtr);
18249 }
18250
18251 char *
18252 PGNDate ()
18253 {
18254     time_t clock;
18255     struct tm *tm;
18256     char buf[MSG_SIZ];
18257
18258     clock = time((time_t *)NULL);
18259     tm = localtime(&clock);
18260     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18261             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18262     return StrSave(buf);
18263 }
18264
18265
18266 char *
18267 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18268 {
18269     int i, j, fromX, fromY, toX, toY;
18270     int whiteToPlay, haveRights = nrCastlingRights;
18271     char buf[MSG_SIZ];
18272     char *p, *q;
18273     int emptycount;
18274     ChessSquare piece;
18275
18276     whiteToPlay = (gameMode == EditPosition) ?
18277       !blackPlaysFirst : (move % 2 == 0);
18278     p = buf;
18279
18280     /* Piece placement data */
18281     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18282         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18283         emptycount = 0;
18284         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18285             if (boards[move][i][j] == EmptySquare) {
18286                 emptycount++;
18287             } else { ChessSquare piece = boards[move][i][j];
18288                 if (emptycount > 0) {
18289                     if(emptycount<10) /* [HGM] can be >= 10 */
18290                         *p++ = '0' + emptycount;
18291                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18292                     emptycount = 0;
18293                 }
18294                 if(PieceToChar(piece) == '+') {
18295                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18296                     *p++ = '+';
18297                     piece = (ChessSquare)(CHUDEMOTED(piece));
18298                 }
18299                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18300                 if(*p = PieceSuffix(piece)) p++;
18301                 if(p[-1] == '~') {
18302                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18303                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18304                     *p++ = '~';
18305                 }
18306             }
18307         }
18308         if (emptycount > 0) {
18309             if(emptycount<10) /* [HGM] can be >= 10 */
18310                 *p++ = '0' + emptycount;
18311             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18312             emptycount = 0;
18313         }
18314         *p++ = '/';
18315     }
18316     *(p - 1) = ' ';
18317
18318     /* [HGM] print Crazyhouse or Shogi holdings */
18319     if( gameInfo.holdingsWidth ) {
18320         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18321         q = p;
18322         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18323             piece = boards[move][i][BOARD_WIDTH-1];
18324             if( piece != EmptySquare )
18325               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18326                   *p++ = PieceToChar(piece);
18327         }
18328         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18329             piece = boards[move][BOARD_HEIGHT-i-1][0];
18330             if( piece != EmptySquare )
18331               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18332                   *p++ = PieceToChar(piece);
18333         }
18334
18335         if( q == p ) *p++ = '-';
18336         *p++ = ']';
18337         *p++ = ' ';
18338     }
18339
18340     /* Active color */
18341     *p++ = whiteToPlay ? 'w' : 'b';
18342     *p++ = ' ';
18343
18344   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18345     haveRights = 0; q = p;
18346     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18347       piece = boards[move][0][i];
18348       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18349         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18350       }
18351     }
18352     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18353       piece = boards[move][BOARD_HEIGHT-1][i];
18354       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18355         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18356       }
18357     }
18358     if(p == q) *p++ = '-';
18359     *p++ = ' ';
18360   }
18361
18362   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18363     while(*p++ = *q++)
18364                       ;
18365     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18366   } else {
18367   if(haveRights) {
18368      int handW=0, handB=0;
18369      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18370         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18371         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18372      }
18373      q = p;
18374      if(appData.fischerCastling) {
18375         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18376            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18377                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18378         } else {
18379        /* [HGM] write directly from rights */
18380            if(boards[move][CASTLING][2] != NoRights &&
18381               boards[move][CASTLING][0] != NoRights   )
18382                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18383            if(boards[move][CASTLING][2] != NoRights &&
18384               boards[move][CASTLING][1] != NoRights   )
18385                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18386         }
18387         if(handB) {
18388            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18389                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18390         } else {
18391            if(boards[move][CASTLING][5] != NoRights &&
18392               boards[move][CASTLING][3] != NoRights   )
18393                 *p++ = boards[move][CASTLING][3] + AAA;
18394            if(boards[move][CASTLING][5] != NoRights &&
18395               boards[move][CASTLING][4] != NoRights   )
18396                 *p++ = boards[move][CASTLING][4] + AAA;
18397         }
18398      } else {
18399
18400         /* [HGM] write true castling rights */
18401         if( nrCastlingRights == 6 ) {
18402             int q, k=0;
18403             if(boards[move][CASTLING][0] != NoRights &&
18404                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18405             q = (boards[move][CASTLING][1] != NoRights &&
18406                  boards[move][CASTLING][2] != NoRights  );
18407             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18408                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18409                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18410                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18411             }
18412             if(q) *p++ = 'Q';
18413             k = 0;
18414             if(boards[move][CASTLING][3] != NoRights &&
18415                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18416             q = (boards[move][CASTLING][4] != NoRights &&
18417                  boards[move][CASTLING][5] != NoRights  );
18418             if(handB) {
18419                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18420                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18421                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18422             }
18423             if(q) *p++ = 'q';
18424         }
18425      }
18426      if (q == p) *p++ = '-'; /* No castling rights */
18427      *p++ = ' ';
18428   }
18429
18430   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18431      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18432      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18433     /* En passant target square */
18434     if (move > backwardMostMove) {
18435         fromX = moveList[move - 1][0] - AAA;
18436         fromY = moveList[move - 1][1] - ONE;
18437         toX = moveList[move - 1][2] - AAA;
18438         toY = moveList[move - 1][3] - ONE;
18439         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18440             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18441             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18442             fromX == toX) {
18443             /* 2-square pawn move just happened */
18444             *p++ = toX + AAA;
18445             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18446         } else {
18447             *p++ = '-';
18448         }
18449     } else if(move == backwardMostMove) {
18450         // [HGM] perhaps we should always do it like this, and forget the above?
18451         if((signed char)boards[move][EP_STATUS] >= 0) {
18452             *p++ = boards[move][EP_STATUS] + AAA;
18453             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18454         } else {
18455             *p++ = '-';
18456         }
18457     } else {
18458         *p++ = '-';
18459     }
18460     *p++ = ' ';
18461   }
18462   }
18463
18464     if(moveCounts)
18465     {   int i = 0, j=move;
18466
18467         /* [HGM] find reversible plies */
18468         if (appData.debugMode) { int k;
18469             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18470             for(k=backwardMostMove; k<=forwardMostMove; k++)
18471                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18472
18473         }
18474
18475         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18476         if( j == backwardMostMove ) i += initialRulePlies;
18477         sprintf(p, "%d ", i);
18478         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18479
18480         /* Fullmove number */
18481         sprintf(p, "%d", (move / 2) + 1);
18482     } else *--p = NULLCHAR;
18483
18484     return StrSave(buf);
18485 }
18486
18487 Boolean
18488 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18489 {
18490     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18491     char *p, c;
18492     int emptycount, virgin[BOARD_FILES];
18493     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18494
18495     p = fen;
18496
18497     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18498
18499     /* Piece placement data */
18500     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18501         j = 0;
18502         for (;;) {
18503             if (*p == '/' || *p == ' ' || *p == '[' ) {
18504                 if(j > w) w = j;
18505                 emptycount = gameInfo.boardWidth - j;
18506                 while (emptycount--)
18507                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18508                 if (*p == '/') p++;
18509                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18510                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18511                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18512                     }
18513                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18514                 }
18515                 break;
18516 #if(BOARD_FILES >= 10)*0
18517             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18518                 p++; emptycount=10;
18519                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18520                 while (emptycount--)
18521                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18522 #endif
18523             } else if (*p == '*') {
18524                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18525             } else if (isdigit(*p)) {
18526                 emptycount = *p++ - '0';
18527                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18528                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18529                 while (emptycount--)
18530                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18531             } else if (*p == '<') {
18532                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18533                 else if (i != 0 || !shuffle) return FALSE;
18534                 p++;
18535             } else if (shuffle && *p == '>') {
18536                 p++; // for now ignore closing shuffle range, and assume rank-end
18537             } else if (*p == '?') {
18538                 if (j >= gameInfo.boardWidth) return FALSE;
18539                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18540                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18541             } else if (*p == '+' || isalpha(*p)) {
18542                 char *q, *s = SUFFIXES;
18543                 if (j >= gameInfo.boardWidth) return FALSE;
18544                 if(*p=='+') {
18545                     char c = *++p;
18546                     if(q = strchr(s, p[1])) p++;
18547                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18548                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18549                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18550                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18551                 } else {
18552                     char c = *p++;
18553                     if(q = strchr(s, *p)) p++;
18554                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18555                 }
18556
18557                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18558                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18559                     piece = (ChessSquare) (PROMOTED(piece));
18560                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18561                     p++;
18562                 }
18563                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18564                 if(piece == king) wKingRank = i;
18565                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18566             } else {
18567                 return FALSE;
18568             }
18569         }
18570     }
18571     while (*p == '/' || *p == ' ') p++;
18572
18573     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18574
18575     /* [HGM] by default clear Crazyhouse holdings, if present */
18576     if(gameInfo.holdingsWidth) {
18577        for(i=0; i<BOARD_HEIGHT; i++) {
18578            board[i][0]             = EmptySquare; /* black holdings */
18579            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18580            board[i][1]             = (ChessSquare) 0; /* black counts */
18581            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18582        }
18583     }
18584
18585     /* [HGM] look for Crazyhouse holdings here */
18586     while(*p==' ') p++;
18587     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18588         int swap=0, wcnt=0, bcnt=0;
18589         if(*p == '[') p++;
18590         if(*p == '<') swap++, p++;
18591         if(*p == '-' ) p++; /* empty holdings */ else {
18592             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18593             /* if we would allow FEN reading to set board size, we would   */
18594             /* have to add holdings and shift the board read so far here   */
18595             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18596                 p++;
18597                 if((int) piece >= (int) BlackPawn ) {
18598                     i = (int)piece - (int)BlackPawn;
18599                     i = PieceToNumber((ChessSquare)i);
18600                     if( i >= gameInfo.holdingsSize ) return FALSE;
18601                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18602                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18603                     bcnt++;
18604                 } else {
18605                     i = (int)piece - (int)WhitePawn;
18606                     i = PieceToNumber((ChessSquare)i);
18607                     if( i >= gameInfo.holdingsSize ) return FALSE;
18608                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18609                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18610                     wcnt++;
18611                 }
18612             }
18613             if(subst) { // substitute back-rank question marks by holdings pieces
18614                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18615                     int k, m, n = bcnt + 1;
18616                     if(board[0][j] == ClearBoard) {
18617                         if(!wcnt) return FALSE;
18618                         n = rand() % wcnt;
18619                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18620                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18621                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18622                             break;
18623                         }
18624                     }
18625                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18626                         if(!bcnt) return FALSE;
18627                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18628                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18629                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18630                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18631                             break;
18632                         }
18633                     }
18634                 }
18635                 subst = 0;
18636             }
18637         }
18638         if(*p == ']') p++;
18639     }
18640
18641     if(subst) return FALSE; // substitution requested, but no holdings
18642
18643     while(*p == ' ') p++;
18644
18645     /* Active color */
18646     c = *p++;
18647     if(appData.colorNickNames) {
18648       if( c == appData.colorNickNames[0] ) c = 'w'; else
18649       if( c == appData.colorNickNames[1] ) c = 'b';
18650     }
18651     switch (c) {
18652       case 'w':
18653         *blackPlaysFirst = FALSE;
18654         break;
18655       case 'b':
18656         *blackPlaysFirst = TRUE;
18657         break;
18658       default:
18659         return FALSE;
18660     }
18661
18662     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18663     /* return the extra info in global variiables             */
18664
18665     while(*p==' ') p++;
18666
18667     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18668         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18669         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18670     }
18671
18672     /* set defaults in case FEN is incomplete */
18673     board[EP_STATUS] = EP_UNKNOWN;
18674     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18675     for(i=0; i<nrCastlingRights; i++ ) {
18676         board[CASTLING][i] =
18677             appData.fischerCastling ? NoRights : initialRights[i];
18678     }   /* assume possible unless obviously impossible */
18679     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18680     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18681     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18682                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18683     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18684     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18685     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18686                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18687     FENrulePlies = 0;
18688
18689     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18690       char *q = p;
18691       int w=0, b=0;
18692       while(isalpha(*p)) {
18693         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18694         if(islower(*p)) b |= 1 << (*p++ - 'a');
18695       }
18696       if(*p == '-') p++;
18697       if(p != q) {
18698         board[TOUCHED_W] = ~w;
18699         board[TOUCHED_B] = ~b;
18700         while(*p == ' ') p++;
18701       }
18702     } else
18703
18704     if(nrCastlingRights) {
18705       int fischer = 0;
18706       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18707       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18708           /* castling indicator present, so default becomes no castlings */
18709           for(i=0; i<nrCastlingRights; i++ ) {
18710                  board[CASTLING][i] = NoRights;
18711           }
18712       }
18713       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18714              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18715              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18716              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18717         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18718
18719         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18720             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18721             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18722         }
18723         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18724             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18725         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18726                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18727         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18728                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18729         switch(c) {
18730           case'K':
18731               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18732               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18733               board[CASTLING][2] = whiteKingFile;
18734               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18735               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18736               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18737               break;
18738           case'Q':
18739               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18740               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18741               board[CASTLING][2] = whiteKingFile;
18742               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18743               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18744               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18745               break;
18746           case'k':
18747               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18748               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18749               board[CASTLING][5] = blackKingFile;
18750               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18751               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18752               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18753               break;
18754           case'q':
18755               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18756               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18757               board[CASTLING][5] = blackKingFile;
18758               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18759               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18760               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18761           case '-':
18762               break;
18763           default: /* FRC castlings */
18764               if(c >= 'a') { /* black rights */
18765                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18766                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18767                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18768                   if(i == BOARD_RGHT) break;
18769                   board[CASTLING][5] = i;
18770                   c -= AAA;
18771                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18772                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18773                   if(c > i)
18774                       board[CASTLING][3] = c;
18775                   else
18776                       board[CASTLING][4] = c;
18777               } else { /* white rights */
18778                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18779                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18780                     if(board[0][i] == WhiteKing) break;
18781                   if(i == BOARD_RGHT) break;
18782                   board[CASTLING][2] = i;
18783                   c -= AAA - 'a' + 'A';
18784                   if(board[0][c] >= WhiteKing) break;
18785                   if(c > i)
18786                       board[CASTLING][0] = c;
18787                   else
18788                       board[CASTLING][1] = c;
18789               }
18790         }
18791       }
18792       for(i=0; i<nrCastlingRights; i++)
18793         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18794       if(gameInfo.variant == VariantSChess)
18795         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18796       if(fischer && shuffle) appData.fischerCastling = TRUE;
18797     if (appData.debugMode) {
18798         fprintf(debugFP, "FEN castling rights:");
18799         for(i=0; i<nrCastlingRights; i++)
18800         fprintf(debugFP, " %d", board[CASTLING][i]);
18801         fprintf(debugFP, "\n");
18802     }
18803
18804       while(*p==' ') p++;
18805     }
18806
18807     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18808
18809     /* read e.p. field in games that know e.p. capture */
18810     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18811        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18812        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18813       if(*p=='-') {
18814         p++; board[EP_STATUS] = EP_NONE;
18815       } else {
18816          char c = *p++ - AAA;
18817
18818          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18819          if(*p >= '0' && *p <='9') p++;
18820          board[EP_STATUS] = c;
18821       }
18822     }
18823
18824
18825     if(sscanf(p, "%d", &i) == 1) {
18826         FENrulePlies = i; /* 50-move ply counter */
18827         /* (The move number is still ignored)    */
18828     }
18829
18830     return TRUE;
18831 }
18832
18833 void
18834 EditPositionPasteFEN (char *fen)
18835 {
18836   if (fen != NULL) {
18837     Board initial_position;
18838
18839     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18840       DisplayError(_("Bad FEN position in clipboard"), 0);
18841       return ;
18842     } else {
18843       int savedBlackPlaysFirst = blackPlaysFirst;
18844       EditPositionEvent();
18845       blackPlaysFirst = savedBlackPlaysFirst;
18846       CopyBoard(boards[0], initial_position);
18847       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18848       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18849       DisplayBothClocks();
18850       DrawPosition(FALSE, boards[currentMove]);
18851     }
18852   }
18853 }
18854
18855 static char cseq[12] = "\\   ";
18856
18857 Boolean
18858 set_cont_sequence (char *new_seq)
18859 {
18860     int len;
18861     Boolean ret;
18862
18863     // handle bad attempts to set the sequence
18864         if (!new_seq)
18865                 return 0; // acceptable error - no debug
18866
18867     len = strlen(new_seq);
18868     ret = (len > 0) && (len < sizeof(cseq));
18869     if (ret)
18870       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18871     else if (appData.debugMode)
18872       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18873     return ret;
18874 }
18875
18876 /*
18877     reformat a source message so words don't cross the width boundary.  internal
18878     newlines are not removed.  returns the wrapped size (no null character unless
18879     included in source message).  If dest is NULL, only calculate the size required
18880     for the dest buffer.  lp argument indicats line position upon entry, and it's
18881     passed back upon exit.
18882 */
18883 int
18884 wrap (char *dest, char *src, int count, int width, int *lp)
18885 {
18886     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18887
18888     cseq_len = strlen(cseq);
18889     old_line = line = *lp;
18890     ansi = len = clen = 0;
18891
18892     for (i=0; i < count; i++)
18893     {
18894         if (src[i] == '\033')
18895             ansi = 1;
18896
18897         // if we hit the width, back up
18898         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18899         {
18900             // store i & len in case the word is too long
18901             old_i = i, old_len = len;
18902
18903             // find the end of the last word
18904             while (i && src[i] != ' ' && src[i] != '\n')
18905             {
18906                 i--;
18907                 len--;
18908             }
18909
18910             // word too long?  restore i & len before splitting it
18911             if ((old_i-i+clen) >= width)
18912             {
18913                 i = old_i;
18914                 len = old_len;
18915             }
18916
18917             // extra space?
18918             if (i && src[i-1] == ' ')
18919                 len--;
18920
18921             if (src[i] != ' ' && src[i] != '\n')
18922             {
18923                 i--;
18924                 if (len)
18925                     len--;
18926             }
18927
18928             // now append the newline and continuation sequence
18929             if (dest)
18930                 dest[len] = '\n';
18931             len++;
18932             if (dest)
18933                 strncpy(dest+len, cseq, cseq_len);
18934             len += cseq_len;
18935             line = cseq_len;
18936             clen = cseq_len;
18937             continue;
18938         }
18939
18940         if (dest)
18941             dest[len] = src[i];
18942         len++;
18943         if (!ansi)
18944             line++;
18945         if (src[i] == '\n')
18946             line = 0;
18947         if (src[i] == 'm')
18948             ansi = 0;
18949     }
18950     if (dest && appData.debugMode)
18951     {
18952         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18953             count, width, line, len, *lp);
18954         show_bytes(debugFP, src, count);
18955         fprintf(debugFP, "\ndest: ");
18956         show_bytes(debugFP, dest, len);
18957         fprintf(debugFP, "\n");
18958     }
18959     *lp = dest ? line : old_line;
18960
18961     return len;
18962 }
18963
18964 // [HGM] vari: routines for shelving variations
18965 Boolean modeRestore = FALSE;
18966
18967 void
18968 PushInner (int firstMove, int lastMove)
18969 {
18970         int i, j, nrMoves = lastMove - firstMove;
18971
18972         // push current tail of game on stack
18973         savedResult[storedGames] = gameInfo.result;
18974         savedDetails[storedGames] = gameInfo.resultDetails;
18975         gameInfo.resultDetails = NULL;
18976         savedFirst[storedGames] = firstMove;
18977         savedLast [storedGames] = lastMove;
18978         savedFramePtr[storedGames] = framePtr;
18979         framePtr -= nrMoves; // reserve space for the boards
18980         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18981             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18982             for(j=0; j<MOVE_LEN; j++)
18983                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18984             for(j=0; j<2*MOVE_LEN; j++)
18985                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18986             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18987             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18988             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18989             pvInfoList[firstMove+i-1].depth = 0;
18990             commentList[framePtr+i] = commentList[firstMove+i];
18991             commentList[firstMove+i] = NULL;
18992         }
18993
18994         storedGames++;
18995         forwardMostMove = firstMove; // truncate game so we can start variation
18996 }
18997
18998 void
18999 PushTail (int firstMove, int lastMove)
19000 {
19001         if(appData.icsActive) { // only in local mode
19002                 forwardMostMove = currentMove; // mimic old ICS behavior
19003                 return;
19004         }
19005         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19006
19007         PushInner(firstMove, lastMove);
19008         if(storedGames == 1) GreyRevert(FALSE);
19009         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19010 }
19011
19012 void
19013 PopInner (Boolean annotate)
19014 {
19015         int i, j, nrMoves;
19016         char buf[8000], moveBuf[20];
19017
19018         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19019         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19020         nrMoves = savedLast[storedGames] - currentMove;
19021         if(annotate) {
19022                 int cnt = 10;
19023                 if(!WhiteOnMove(currentMove))
19024                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19025                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19026                 for(i=currentMove; i<forwardMostMove; i++) {
19027                         if(WhiteOnMove(i))
19028                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19029                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19030                         strcat(buf, moveBuf);
19031                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19032                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19033                 }
19034                 strcat(buf, ")");
19035         }
19036         for(i=1; i<=nrMoves; i++) { // copy last variation back
19037             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19038             for(j=0; j<MOVE_LEN; j++)
19039                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19040             for(j=0; j<2*MOVE_LEN; j++)
19041                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19042             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19043             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19044             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19045             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19046             commentList[currentMove+i] = commentList[framePtr+i];
19047             commentList[framePtr+i] = NULL;
19048         }
19049         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19050         framePtr = savedFramePtr[storedGames];
19051         gameInfo.result = savedResult[storedGames];
19052         if(gameInfo.resultDetails != NULL) {
19053             free(gameInfo.resultDetails);
19054       }
19055         gameInfo.resultDetails = savedDetails[storedGames];
19056         forwardMostMove = currentMove + nrMoves;
19057 }
19058
19059 Boolean
19060 PopTail (Boolean annotate)
19061 {
19062         if(appData.icsActive) return FALSE; // only in local mode
19063         if(!storedGames) return FALSE; // sanity
19064         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19065
19066         PopInner(annotate);
19067         if(currentMove < forwardMostMove) ForwardEvent(); else
19068         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19069
19070         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19071         return TRUE;
19072 }
19073
19074 void
19075 CleanupTail ()
19076 {       // remove all shelved variations
19077         int i;
19078         for(i=0; i<storedGames; i++) {
19079             if(savedDetails[i])
19080                 free(savedDetails[i]);
19081             savedDetails[i] = NULL;
19082         }
19083         for(i=framePtr; i<MAX_MOVES; i++) {
19084                 if(commentList[i]) free(commentList[i]);
19085                 commentList[i] = NULL;
19086         }
19087         framePtr = MAX_MOVES-1;
19088         storedGames = 0;
19089 }
19090
19091 void
19092 LoadVariation (int index, char *text)
19093 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19094         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19095         int level = 0, move;
19096
19097         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19098         // first find outermost bracketing variation
19099         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19100             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19101                 if(*p == '{') wait = '}'; else
19102                 if(*p == '[') wait = ']'; else
19103                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19104                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19105             }
19106             if(*p == wait) wait = NULLCHAR; // closing ]} found
19107             p++;
19108         }
19109         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19110         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19111         end[1] = NULLCHAR; // clip off comment beyond variation
19112         ToNrEvent(currentMove-1);
19113         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19114         // kludge: use ParsePV() to append variation to game
19115         move = currentMove;
19116         ParsePV(start, TRUE, TRUE);
19117         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19118         ClearPremoveHighlights();
19119         CommentPopDown();
19120         ToNrEvent(currentMove+1);
19121 }
19122
19123 int transparency[2];
19124
19125 void
19126 LoadTheme ()
19127 {
19128 #define BUF_SIZ (2*MSG_SIZ)
19129     char *p, *q, buf[BUF_SIZ];
19130     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19131         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19132         ParseArgsFromString(buf);
19133         ActivateTheme(TRUE); // also redo colors
19134         return;
19135     }
19136     p = nickName;
19137     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19138     {
19139         int len;
19140         q = appData.themeNames;
19141         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19142       if(appData.useBitmaps) {
19143         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19144                 Shorten(appData.liteBackTextureFile));
19145         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19146                 Shorten(appData.darkBackTextureFile),
19147                 appData.liteBackTextureMode,
19148                 appData.darkBackTextureMode );
19149       } else {
19150         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19151       }
19152       if(!appData.useBitmaps || transparency[0]) {
19153         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19154       }
19155       if(!appData.useBitmaps || transparency[1]) {
19156         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19157       }
19158       if(appData.useBorder) {
19159         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19160                 appData.border);
19161       } else {
19162         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19163       }
19164       if(appData.useFont) {
19165         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19166                 appData.renderPiecesWithFont,
19167                 appData.fontToPieceTable,
19168                 Col2Text(9),    // appData.fontBackColorWhite
19169                 Col2Text(10) ); // appData.fontForeColorBlack
19170       } else {
19171         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19172         if(appData.pieceDirectory[0]) {
19173           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19174           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19175             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19176         }
19177         if(!appData.pieceDirectory[0] || !appData.trueColors)
19178           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19179                 Col2Text(0),   // whitePieceColor
19180                 Col2Text(1) ); // blackPieceColor
19181       }
19182       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19183                 Col2Text(4),   // highlightSquareColor
19184                 Col2Text(5) ); // premoveHighlightColor
19185         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19186         if(insert != q) insert[-1] = NULLCHAR;
19187         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19188         if(q)   free(q);
19189     }
19190     ActivateTheme(FALSE);
19191 }